diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 0ebb7f087176..6328d227e43f 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -5,13 +5,17 @@ inputs: required: false default: '17' description: 'The Java version to compile and test with' + java-distribution: + required: false + default: 'liberica' + description: 'The Java distribution to use for the build' java-toolchain: required: false - default: false + default: 'false' description: 'Whether a Java toolchain should be used' publish: required: false - default: false + default: 'false' description: 'Whether to publish artifacts ready for deployment to Artifactory' develocity-access-key: required: false @@ -29,21 +33,19 @@ runs: - name: Prepare Gradle Build uses: ./.github/actions/prepare-gradle-build with: + develocity-access-key: ${{ inputs.develocity-access-key }} java-version: ${{ inputs.java-version }} + java-distribution: ${{ inputs.java-distribution }} java-toolchain: ${{ inputs.java-toolchain }} - name: Build id: build if: ${{ inputs.publish == 'false' }} shell: bash - env: - DEVELOCITY_ACCESS_KEY: ${{ inputs.develocity-access-key }} run: ./gradlew build - name: Publish id: publish if: ${{ inputs.publish == 'true' }} shell: bash - env: - DEVELOCITY_ACCESS_KEY: ${{ inputs.develocity-access-key }} run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository ${{ !startsWith(github.event.head_commit.message, 'Next development version') && 'build' || '' }} publishAllPublicationsToDeploymentRepository - name: Read Version From gradle.properties id: read-version diff --git a/.github/actions/create-github-release/action.yml b/.github/actions/create-github-release/action.yml index d30dacef4c83..d5cc67fb9be3 100644 --- a/.github/actions/create-github-release/action.yml +++ b/.github/actions/create-github-release/action.yml @@ -7,11 +7,15 @@ inputs: token: description: Token to use for authentication with GitHub required: true + pre-release: + description: Whether the release is a pre-release (a milestone or release candidate) + required: false + default: 'false' runs: using: composite steps: - name: Generate Changelog - uses: spring-io/github-changelog-generator@052892c62af51f8af87a9da6de55e70864b7df12 + uses: spring-io/github-changelog-generator@185319ad7eaa75b0e8e72e4b6db19c8b2cb8c4c1 #v0.0.11 with: milestone: ${{ inputs.milestone }} token: ${{ inputs.token }} @@ -20,4 +24,4 @@ runs: env: GITHUB_TOKEN: ${{ inputs.token }} shell: bash - run: gh release create ${{ format('v{0}', inputs.milestone) }} --notes-file changelog.md + run: gh release create ${{ format('v{0}', inputs.milestone) }} --notes-file changelog.md ${{ inputs.pre-release == 'true' && '--prerelease' || '' }} diff --git a/.github/actions/prepare-gradle-build/action.yml b/.github/actions/prepare-gradle-build/action.yml index dbcd610b0653..29f80d71663d 100644 --- a/.github/actions/prepare-gradle-build/action.yml +++ b/.github/actions/prepare-gradle-build/action.yml @@ -5,10 +5,17 @@ inputs: required: false default: '17' description: 'The Java version to use for the build' + java-distribution: + required: false + default: 'liberica' + description: 'The Java distribution to use for the build' java-toolchain: required: false - default: false + default: 'false' description: 'Whether a Java toolchain should be used' + develocity-access-key: + required: false + description: 'The access key for authentication with ge.spring.io' runs: using: composite steps: @@ -20,14 +27,15 @@ runs: - name: Set Up Java uses: actions/setup-java@v4 with: - distribution: 'liberica' + distribution: ${{ inputs.java-distribution }} java-version: | ${{ inputs.java-version }} ${{ inputs.java-toolchain == 'true' && '17' || '' }} - name: Set Up Gradle - uses: gradle/actions/setup-gradle@db19848a5fa7950289d3668fb053140cf3028d43 # v3.3.2 + uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0 with: cache-read-only: false + develocity-access-key: ${{ inputs.develocity-access-key }} - name: Configure Gradle Properties shell: bash run: | diff --git a/.github/actions/print-jvm-thread-dumps/action.yml b/.github/actions/print-jvm-thread-dumps/action.yml index bab22e54897a..9b0905b77725 100644 --- a/.github/actions/print-jvm-thread-dumps/action.yml +++ b/.github/actions/print-jvm-thread-dumps/action.yml @@ -7,7 +7,7 @@ runs: shell: bash run: | for jvm_pid in $(jps -q -J-XX:+PerfDisableSharedMem); do - jcmd $java_pid Thread.print + jcmd $jvm_pid Thread.print done - if: ${{ runner.os == 'Windows' }} shell: powershell diff --git a/.github/actions/publish-gradle-plugin/action.yml b/.github/actions/publish-gradle-plugin/action.yml index ee61f9feb9ec..ccc5b1bab305 100644 --- a/.github/actions/publish-gradle-plugin/action.yml +++ b/.github/actions/publish-gradle-plugin/action.yml @@ -21,7 +21,7 @@ runs: using: composite steps: - name: Set Up JFrog CLI - uses: jfrog/setup-jfrog-cli@727b480bafd0d8adbdfdb2257a7d7c2e08eb1779 # v4.0.2 + uses: jfrog/setup-jfrog-cli@8bab65dc312163b065ac5b03de6f6a5bdd1bec41 # v4.1.3 env: JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }} - name: Download Artifacts diff --git a/.github/actions/publish-to-sdkman/action.yml b/.github/actions/publish-to-sdkman/action.yml index 2d23b18232bc..7458c863ac57 100644 --- a/.github/actions/publish-to-sdkman/action.yml +++ b/.github/actions/publish-to-sdkman/action.yml @@ -29,7 +29,7 @@ runs: - shell: bash if: ${{ inputs.make-default == 'true' }} run: > - curl -X POST \ + curl -X PUT \ -H "Consumer-Key: ${{ inputs.sdkman-consumer-key }}" \ -H "Consumer-Token: ${{ inputs.sdkman-consumer-token }}" \ -H "Content-Type: application/json" \ diff --git a/.github/actions/sync-to-maven-central/action.yml b/.github/actions/sync-to-maven-central/action.yml index ef74ce2f6cf9..8ebef462a1cc 100644 --- a/.github/actions/sync-to-maven-central/action.yml +++ b/.github/actions/sync-to-maven-central/action.yml @@ -20,7 +20,7 @@ runs: using: composite steps: - name: Set Up JFrog CLI - uses: jfrog/setup-jfrog-cli@727b480bafd0d8adbdfdb2257a7d7c2e08eb1779 # v4.0.2 + uses: jfrog/setup-jfrog-cli@8bab65dc312163b065ac5b03de6f6a5bdd1bec41 # v4.1.3 env: JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }} - name: Download Release Artifacts diff --git a/.github/actions/update-homebrew-tap/action.yml b/.github/actions/update-homebrew-tap/action.yml new file mode 100644 index 000000000000..52b20e0990a8 --- /dev/null +++ b/.github/actions/update-homebrew-tap/action.yml @@ -0,0 +1,32 @@ +name: Update Homebrew Tap +description: Updates the Homebrew Tap for the Spring Boot CLI +inputs: + spring-boot-version: + description: 'The version to publish' + required: true + token: + description: 'Token to use for GitHub authentication' + required: true +runs: + using: composite + steps: + - name: Check Out Homebrew Tap Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + path: updated-homebrew-tap-repo + repository: spring-io/homebrew-tap + token: ${{ inputs.token }} + - name: Update Homebrew Tap + shell: bash + run: | + pushd updated-homebrew-tap-repo > /dev/null + curl https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-cli/${{ inputs.spring-boot-version }}/spring-boot-cli-${{ inputs.spring-boot-version }}-homebrew.rb --output spring-boot-cli-${{ inputs.spring-boot-version }}-homebrew.rb + rm spring-boot.rb + mv spring-boot-cli-*.rb spring-boot.rb + git config user.name "Spring Builds" > /dev/null + git config user.email "spring-builds@users.noreply.github.com" > /dev/null + git add spring-boot.rb > /dev/null + git commit -m "Upgrade to Spring Boot ${{ inputs.spring-boot-version }}" > /dev/null + git push + echo "DONE" + popd > /dev/null diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5ace4600a1f2..187fe1cd726c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,3 +4,5 @@ updates: directory: "/" schedule: interval: "weekly" + labels: + - "type: task" diff --git a/.github/workflows/build-and-deploy-snapshot.yml b/.github/workflows/build-and-deploy-snapshot.yml index 787addc332eb..5984b036652a 100644 --- a/.github/workflows/build-and-deploy-snapshot.yml +++ b/.github/workflows/build-and-deploy-snapshot.yml @@ -2,7 +2,7 @@ name: Build and Deploy Snapshot on: push: branches: - - 3.1.x + - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} jobs: @@ -25,13 +25,11 @@ jobs: uri: 'https://repo.spring.io' username: ${{ secrets.ARTIFACTORY_USERNAME }} password: ${{ secrets.ARTIFACTORY_PASSWORD }} - build-name: ${{ format('spring-boot-{0}', github.ref_name)}} + build-name: 'spring-boot-3.4.x' repository: 'libs-snapshot-local' folder: 'deployment-repository' signing-key: ${{ secrets.GPG_PRIVATE_KEY }} signing-passphrase: ${{ secrets.GPG_PASSPHRASE }} - artifact-properties: | - /**/spring-boot-docs-*.zip::zip.type=docs,zip.deployed=false - name: Send Notification uses: ./.github/actions/send-notification if: always() @@ -42,6 +40,17 @@ jobs: run-name: ${{ format('{0} | Linux | Java 17', github.ref_name) }} outputs: version: ${{ steps.build-and-publish.outputs.version }} + trigger-docs-build: + name: Trigger Docs Build + runs-on: ubuntu-latest + needs: build-and-deploy-snapshot + permissions: + actions: write + steps: + - name: Run Deploy Docs Workflow + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh workflow run deploy-docs.yml --repo spring-projects/spring-boot -r docs-build -f build-refname=${{ github.ref_name }} -f build-version=${{ needs.build-and-deploy-snapshot.outputs.version }} verify: name: Verify needs: build-and-deploy-snapshot diff --git a/.github/workflows/build-pull-request.yml b/.github/workflows/build-pull-request.yml index 2573dbea2e4c..cf40231b357d 100644 --- a/.github/workflows/build-pull-request.yml +++ b/.github/workflows/build-pull-request.yml @@ -23,9 +23,9 @@ jobs: - name: Check Out uses: actions/checkout@v4 - name: Validate Gradle Wrapper - uses: gradle/actions/wrapper-validation@db19848a5fa7950289d3668fb053140cf3028d43 #v3.3.2 + uses: gradle/actions/wrapper-validation@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0 - name: Set Up Gradle - uses: gradle/actions/setup-gradle@db19848a5fa7950289d3668fb053140cf3028d43 # v3.3.2 + uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0 - name: Build env: CI: 'true' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea93c4539933..feee333bb63b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: push: branches: - - 3.1.x + - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} jobs: @@ -22,6 +22,8 @@ jobs: toolchain: false - version: 21 toolchain: true + - version: 22 + toolchain: true exclude: - os: name: Linux @@ -41,6 +43,7 @@ jobs: uses: ./.github/actions/build with: java-version: ${{ matrix.java.version }} + java-distribution: ${{ matrix.java.distribution || 'liberica' }} java-toolchain: ${{ matrix.java.toolchain }} develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} - name: Send Notification diff --git a/.github/workflows/release-milestone.yml b/.github/workflows/release-milestone.yml new file mode 100644 index 000000000000..838aeb1074b6 --- /dev/null +++ b/.github/workflows/release-milestone.yml @@ -0,0 +1,75 @@ +name: Release Milestone +on: + push: + tags: + - v3.4.0-M[0-9] + - v3.4.0-RC[0-9] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} +jobs: + build-and-stage-release: + name: Build and Stage Release + runs-on: ubuntu-latest + if: ${{ github.repository == 'spring-projects/spring-boot' }} + steps: + - name: Check Out Code + uses: actions/checkout@v4 + - name: Build and Publish + id: build-and-publish + uses: ./.github/actions/build + with: + develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} + publish: true + - name: Stage Release + uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1 + with: + uri: 'https://repo.spring.io' + username: ${{ secrets.ARTIFACTORY_USERNAME }} + password: ${{ secrets.ARTIFACTORY_PASSWORD }} + build-name: ${{ format('spring-boot-{0}', steps.build-and-publish.outputs.version)}} + repository: 'libs-staging-local' + folder: 'deployment-repository' + signing-key: ${{ secrets.GPG_PRIVATE_KEY }} + signing-passphrase: ${{ secrets.GPG_PASSPHRASE }} + outputs: + version: ${{ steps.build-and-publish.outputs.version }} + verify: + name: Verify + needs: build-and-stage-release + uses: ./.github/workflows/verify.yml + with: + staging: true + version: ${{ needs.build-and-stage-release.outputs.version }} + secrets: + google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }} + repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }} + repository-username: ${{ secrets.ARTIFACTORY_USERNAME }} + token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} + promote-release: + name: Promote Release + needs: + - build-and-stage-release + - verify + runs-on: ubuntu-latest + steps: + - name: Set up JFrog CLI + uses: jfrog/setup-jfrog-cli@8bab65dc312163b065ac5b03de6f6a5bdd1bec41 # v4.1.3 + env: + JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} + - name: Promote build + run: jfrog rt build-promote ${{ format('spring-boot-{0}', needs.build-and-stage-release.outputs.version)}} ${{ github.run_number }} libs-milestone-local + create-github-release: + name: Create GitHub Release + needs: + - build-and-stage-release + - promote-release + runs-on: ubuntu-latest + steps: + - name: Check Out Code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Create GitHub Release + uses: ./.github/actions/create-github-release + with: + milestone: ${{ needs.build-and-stage-release.outputs.version }} + token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} + pre-release: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cd7eca367b9d..41bf496bef30 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,14 +2,14 @@ name: Release on: push: tags: - - v3.1.[0-9]+ + - v3.4.[0-9]+ concurrency: group: ${{ github.workflow }}-${{ github.ref }} jobs: build-and-stage-release: - if: ${{ github.repository == 'spring-projects/spring-boot' }} name: Build and Stage Release runs-on: ubuntu-latest + if: ${{ github.repository == 'spring-projects/spring-boot' }} steps: - name: Check Out Code uses: actions/checkout@v4 @@ -22,16 +22,14 @@ jobs: - name: Stage Release uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1 with: - build-name: ${{ format('spring-boot-{0}', steps.build-and-publish.outputs.version)}} - folder: 'deployment-repository' + uri: 'https://repo.spring.io' + username: ${{ secrets.ARTIFACTORY_USERNAME }} password: ${{ secrets.ARTIFACTORY_PASSWORD }} + build-name: ${{ format('spring-boot-{0}', steps.build-and-publish.outputs.version)}} repository: 'libs-staging-local' + folder: 'deployment-repository' signing-key: ${{ secrets.GPG_PRIVATE_KEY }} signing-passphrase: ${{ secrets.GPG_PASSPHRASE }} - uri: 'https://repo.spring.io' - username: ${{ secrets.ARTIFACTORY_USERNAME }} - artifact-properties: | - /**/spring-boot-docs-*.zip::zip.type=docs,zip.deployed=false outputs: version: ${{ steps.build-and-publish.outputs.version }} verify: @@ -71,7 +69,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up JFrog CLI - uses: jfrog/setup-jfrog-cli@727b480bafd0d8adbdfdb2257a7d7c2e08eb1779 # v4.0.2 + uses: jfrog/setup-jfrog-cli@8bab65dc312163b065ac5b03de6f6a5bdd1bec41 # v4.1.3 env: JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} - name: Promote build @@ -104,9 +102,24 @@ jobs: - name: Publish to SDKMAN! uses: ./.github/actions/publish-to-sdkman with: + make-default: true sdkman-consumer-key: ${{ secrets.SDKMAN_CONSUMER_KEY }} sdkman-consumer-token: ${{ secrets.SDKMAN_CONSUMER_TOKEN }} spring-boot-version: ${{ needs.build-and-stage-release.outputs.version }} + update-homebrew-tap: + name: Update Homebrew Tap + needs: + - build-and-stage-release + - sync-to-maven-central + runs-on: ubuntu-latest + steps: + - name: Check Out Code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Update Homebrew Tap + uses: ./.github/actions/update-homebrew-tap + with: + spring-boot-version: ${{ needs.build-and-stage-release.outputs.version }} + token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} create-github-release: name: Create GitHub Release needs: @@ -114,6 +127,7 @@ jobs: - promote-release - publish-gradle-plugin - publish-to-sdkman + - update-homebrew-tap runs-on: ubuntu-latest steps: - name: Check Out Code diff --git a/.github/workflows/run-system-tests.yml b/.github/workflows/run-system-tests.yml index bd3cc5365df0..0e18c07cd4c4 100644 --- a/.github/workflows/run-system-tests.yml +++ b/.github/workflows/run-system-tests.yml @@ -2,7 +2,7 @@ name: Run System Tests on: push: branches: - - 3.1.x + - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} jobs: @@ -23,18 +23,13 @@ jobs: - name: Prepare Gradle Build uses: ./.github/actions/prepare-gradle-build with: + develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} java-version: ${{ matrix.java.version }} java-toolchain: ${{ matrix.java.toolchain }} - name: Run System Tests id: run-system-tests shell: bash - env: - GRADLE_ENTERPRISE_URL: 'https://ge.spring.io' - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} - GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }} - GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} - run: | - ./gradlew systemTest + run: ./gradlew systemTest - name: Send Notification uses: ./.github/actions/send-notification if: always() diff --git a/.github/workflows/trigger-docs-build.yml b/.github/workflows/trigger-docs-build.yml new file mode 100644 index 000000000000..4fe7f96e569b --- /dev/null +++ b/.github/workflows/trigger-docs-build.yml @@ -0,0 +1,29 @@ +name: Trigger Docs Build +on: + push: + branches: main + paths: [ 'antora/*' ] + workflow_dispatch: + inputs: + build-refname: + description: Enter git refname to build (e.g., 1.0.x). + required: false + build-version: + description: Enter the version being build (e.g. 1.0.3-SNAPSHOT) + required: false +permissions: + actions: write +jobs: + trigger-docs-build: + name: Trigger Docs Build + runs-on: ubuntu-latest + if: github.repository_owner == 'spring-projects' + steps: + - name: Check Out + uses: actions/checkout@v4 + with: + ref: docs-build + - name: Trigger Workflow + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh workflow run deploy-docs.yml -r docs-build -f build-refname=${{ github.event.inputs.build-refname }} -f build-version=${{ github.event.inputs.build-version }} diff --git a/.github/workflows/validate-gradle-wrapper.yml b/.github/workflows/validate-gradle-wrapper.yml index 9569f38789b9..7a473b3afe72 100644 --- a/.github/workflows/validate-gradle-wrapper.yml +++ b/.github/workflows/validate-gradle-wrapper.yml @@ -8,4 +8,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: gradle/actions/wrapper-validation@db19848a5fa7950289d3668fb053140cf3028d43 #v3.3.2 + - uses: gradle/actions/wrapper-validation@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0 diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 54bf05234624..9f401f7bea99 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -42,7 +42,7 @@ jobs: - name: Set Up Homebrew uses: Homebrew/actions/setup-homebrew@7657c9512f50e1c35b640971116425935bab3eea - name: Set Up Gradle - uses: gradle/actions/setup-gradle@db19848a5fa7950289d3668fb053140cf3028d43 #v3.3.2 + uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0 with: cache-read-only: false - name: Configure Gradle Properties diff --git a/.gitignore b/.gitignore index 2a812bc5079c..1198c2da875d 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ secrets.yml .gradletasknamecache .sts4-cache .git-hooks/ +node_modules diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 7f1766dd1b68..df1bf12fd2db 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -2,6 +2,16 @@ - \ No newline at end of file + diff --git a/CODE_OF_CONDUCT.adoc b/CODE_OF_CONDUCT.adoc deleted file mode 100644 index 18c62c93ec8c..000000000000 --- a/CODE_OF_CONDUCT.adoc +++ /dev/null @@ -1,44 +0,0 @@ -= Contributor Code of Conduct - -As contributors and maintainers of this project, and in the interest of fostering an open -and welcoming community, we pledge to respect all people who contribute through reporting -issues, posting feature requests, updating documentation, submitting pull requests or -patches, and other activities. - -We are committed to making participation in this project a harassment-free experience for -everyone, regardless of level of experience, gender, gender identity and expression, -sexual orientation, disability, personal appearance, body size, race, ethnicity, age, -religion, or nationality. - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery -* Personal attacks -* Trolling or insulting/derogatory comments -* Public or private harassment -* Publishing other's private information, such as physical or electronic addresses, - without explicit permission -* Other unethical or unprofessional conduct - -Project maintainers have the right and responsibility to remove, edit, or reject comments, -commits, code, wiki edits, issues, and other contributions that are not aligned to this -Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors -that they deem inappropriate, threatening, offensive, or harmful. - -By adopting this Code of Conduct, project maintainers commit themselves to fairly and -consistently applying these principles to every aspect of managing this project. Project -maintainers who do not follow or enforce the Code of Conduct may be permanently removed -from the project team. - -This Code of Conduct applies both within project spaces and in public spaces when an -individual is representing the project or its community. - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by -contacting a project maintainer at code-of-conduct@spring.io. All complaints will -be reviewed and investigated and will result in a response that is deemed necessary and -appropriate to the circumstances. Maintainers are obligated to maintain confidentiality -with regard to the reporter of an incident. - -This Code of Conduct is adapted from the -https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at -https://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/] diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc index d2541b7a7ed5..fba0d2aa2808 100755 --- a/CONTRIBUTING.adoc +++ b/CONTRIBUTING.adoc @@ -5,12 +5,14 @@ Spring Boot is released under the Apache 2.0 license. If you would like to contr == Code of Conduct -This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of conduct]. + +This project adheres to the Contributor Covenant https://github.com/spring-projects/spring-boot?tab=coc-ov-file#contributor-code-of-conduct[code of conduct]. By participating, you are expected to uphold this code. Please report unacceptable behavior to code-of-conduct@spring.io. == Using GitHub Issues + We use GitHub issues to track bugs and enhancements. If you have a general usage question please ask on https://stackoverflow.com[Stack Overflow]. The Spring Boot team and the broader community monitor the https://stackoverflow.com/tags/spring-boot[`spring-boot`] tag. @@ -21,12 +23,14 @@ Ideally, that would include a small sample project that reproduces the problem. == Reporting Security Vulnerabilities + If you think you have found a security vulnerability in Spring Boot please *DO NOT* disclose it publicly until we've had a chance to fix it. Please don't report security vulnerabilities using GitHub issues, instead head over to https://spring.io/security-policy and learn how to disclose them responsibly. == Sign the Contributor License Agreement + Before we accept a non-trivial patch or pull request we will need you to https://cla.pivotal.io/sign/spring[sign the Contributor License Agreement]. Signing the contributor's agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. Active contributors might be asked to join the core team, and given the ability to merge pull requests. @@ -34,6 +38,7 @@ Active contributors might be asked to join the core team, and given the ability == Code Conventions and Housekeeping + None of these is essential for a pull request, but they will all help. They can also be added after the original pull request but before a merge. @@ -55,4 +60,5 @@ added after the original pull request but before a merge. == Working with the Code + For information on editing, building, and testing the code, see the https://github.com/spring-projects/spring-boot/wiki/Working-with-the-Code[Working with the Code] page on the project wiki. diff --git a/README.adoc b/README.adoc index a9bc69b52994..bbbbc664d51e 100755 --- a/README.adoc +++ b/README.adoc @@ -1,5 +1,6 @@ -= Spring Boot image:https://github.com/spring-projects/spring-boot/actions/workflows/build-and-deploy-snapshot.yml/badge.svg?branch=3.1.x["Build Status", link="https://github.com/spring-projects/spring-boot/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3A3.1.x"] image:https://badges.gitter.im/Join Chat.svg["Chat",link="https://gitter.im/spring-projects/spring-boot?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] image:https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Develocity", link="https://ge.spring.io/scans?&search.rootProjectNames=Spring%20Boot%20Build&search.rootProjectNames=spring-boot-build"] -:docs: https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference += Spring Boot image:https://github.com/spring-projects/spring-boot/actions/workflows/build-and-deploy-snapshot.yml/badge.svg?branch=main["Build Status", link="https://github.com/spring-projects/spring-boot/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3Amain"] image:https://badges.gitter.im/Join Chat.svg["Chat",link="https://gitter.im/spring-projects/spring-boot?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] image:https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Develocity", link="https://ge.spring.io/scans?&search.rootProjectNames=Spring%20Boot%20Build&search.rootProjectNames=spring-boot-build"] + +:docs: https://docs.spring.io/spring-boot :github: https://github.com/spring-projects/spring-boot Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss. @@ -18,38 +19,40 @@ Our primary goals are: == Installation and Getting Started -The {docs}/html/[reference documentation] includes detailed {docs}/html/getting-started.html#getting-started-installing-spring-boot[installation instructions] as well as a comprehensive {docs}/html/getting-started.html#getting-started-first-application[``getting started``] guide. + +The {docs}[reference documentation] includes detailed {docs}/installing.html[installation instructions] as well as a comprehensive {docs}/tutorial/first-application/index.html[``getting started``] guide. Here is a quick teaser of a complete Spring Boot application in Java: -[source,java,indent=0] +[source,java] ---- - import org.springframework.boot.*; - import org.springframework.boot.autoconfigure.*; - import org.springframework.web.bind.annotation.*; - - @RestController - @SpringBootApplication - public class Example { +import org.springframework.boot.*; +import org.springframework.boot.autoconfigure.*; +import org.springframework.web.bind.annotation.*; - @RequestMapping("/") - String home() { - return "Hello World!"; - } +@RestController +@SpringBootApplication +public class Example { - public static void main(String[] args) { - SpringApplication.run(Example.class, args); - } + @RequestMapping("/") + String home() { + return "Hello World!"; + } + public static void main(String[] args) { + SpringApplication.run(Example.class, args); } + +} ---- == Getting Help + Are you having trouble with Spring Boot? We want to help! -* Check the {docs}/html/[reference documentation], especially the {docs}/html/howto.html#howto[How-to's] -- they provide solutions to the most common questions. +* Check the {docs}/[reference documentation], especially the {docs}/how-to/index.html[How-to's] -- they provide solutions to the most common questions. * Learn the Spring basics -- Spring Boot builds on many other Spring projects; check the https://spring.io[spring.io] website for a wealth of reference documentation. If you are new to Spring, try one of the https://spring.io/guides[guides]. * If you are upgrading, read the {github}/wiki[release notes] for upgrade instructions and "new and noteworthy" features. @@ -60,6 +63,7 @@ Are you having trouble with Spring Boot? We want to help! == Reporting Issues + Spring Boot uses GitHub's integrated issue tracking system to record bugs and feature requests. If you want to raise an issue, please follow the recommendations below: @@ -74,31 +78,34 @@ We like to know the Spring Boot version, operating system, and JVM version you'r == Building from Source + You don't need to build from source to use Spring Boot (binaries in https://repo.spring.io[repo.spring.io]), but if you want to try out the latest and greatest, Spring Boot can be built and published to your local Maven cache using the https://docs.gradle.org/current/userguide/gradle_wrapper.html[Gradle wrapper]. You also need JDK 17. -[indent=0] +[source,shell] ---- - $ ./gradlew publishToMavenLocal +$ ./gradlew publishToMavenLocal ---- This will build all of the jars and documentation and publish them to your local Maven cache. It won't run any of the tests. If you want to build everything, use the `build` task: -[indent=0] +[source,shell] ---- - $ ./gradlew build +$ ./gradlew build ---- == Modules + There are several modules in Spring Boot. Here is a quick overview: === spring-boot + The main library providing features that support the other parts of Spring Boot. These include: * The `SpringApplication` class, providing static convenience methods that can be used to write a stand-alone Spring Application. @@ -110,6 +117,7 @@ The main library providing features that support the other parts of Spring Boot. === spring-boot-autoconfigure + Spring Boot can configure large parts of typical applications based on the content of their classpath. A single `@EnableAutoConfiguration` annotation triggers auto-configuration of the Spring context. @@ -119,6 +127,7 @@ Auto-configuration will always back away as the user starts to define their own === spring-boot-starters + Starters are a set of convenient dependency descriptors that you can include in your application. You get a one-stop shop for all the Spring and related technology you need without having to hunt through sample code and copy-paste loads of dependency descriptors. For example, if you want to get started using Spring and JPA for database access, include the `spring-boot-starter-data-jpa` dependency in your project, and you are good to go. @@ -126,6 +135,7 @@ For example, if you want to get started using Spring and JPA for database access === spring-boot-actuator + Actuator endpoints let you monitor and interact with your application. Spring Boot Actuator provides the infrastructure required for actuator endpoints. It contains annotation support for actuator endpoints. @@ -134,6 +144,7 @@ This module provides many endpoints, including the `HealthEndpoint`, `Environmen === spring-boot-actuator-autoconfigure + This provides auto-configuration for actuator endpoints based on the content of the classpath and a set of properties. For instance, if Micrometer is on the classpath, it will auto-configure the `MetricsEndpoint`. It contains configuration to expose endpoints over HTTP or JMX. @@ -142,29 +153,34 @@ Just like Spring Boot AutoConfigure, this will back away as the user starts to d === spring-boot-test + This module contains core items and annotations that can be helpful when testing your application. === spring-boot-test-autoconfigure + Like other Spring Boot auto-configuration modules, spring-boot-test-autoconfigure provides auto-configuration for tests based on the classpath. It includes many annotations that can automatically configure a slice of your application that needs to be tested. === spring-boot-loader + Spring Boot Loader provides the secret sauce that allows you to build a single jar file that can be launched using `java -jar`. Generally, you will not need to use `spring-boot-loader` directly but work with the link:spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin[Gradle] or link:spring-boot-project/spring-boot-tools/spring-boot-maven-plugin[Maven] plugin instead. === spring-boot-devtools + The spring-boot-devtools module provides additional development-time features, such as automatic restarts, for a smoother application development experience. Developer tools are automatically disabled when running a fully packaged application. == Guides + The https://spring.io/[spring.io] site contains several guides that show how to use Spring Boot step-by-step: * https://spring.io/guides/gs/spring-boot/[Building an Application with Spring Boot] is an introductory guide that shows you how to create an application, run it, and add some management services. @@ -174,4 +190,5 @@ The https://spring.io/[spring.io] site contains several guides that show how to == License + Spring Boot is Open Source software released under the https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license]. diff --git a/SUPPORT.adoc b/SUPPORT.adoc index 898f5caa8a16..23a8b8bbe549 100755 --- a/SUPPORT.adoc +++ b/SUPPORT.adoc @@ -1,6 +1,9 @@ = Getting support for Spring Boot + + == GitHub issues + We choose not to use GitHub issues for general usage questions and support, preferring to use issues solely for the tracking of bugs and enhancements. If you have a general usage question please do not open a GitHub issue, but use one of the other channels @@ -10,17 +13,26 @@ If you are reporting a bug, please help to speed up problem diagnosis by providi much information as possible. Ideally, that would include a small sample project that reproduces the problem. + + == Stack Overflow + The Spring Boot community monitors the https://stackoverflow.com/tags/spring-boot[`spring-boot`] tag on Stack Overflow. Before asking a question, please familiarize yourself with Stack Overflow's https://stackoverflow.com/help/how-to-ask[advice on how to ask a good question]. + + == Gitter + If you want to discuss something or have a question that isn't suited to Stack Overflow, the Spring Boot community chat in the https://gitter.im/spring-projects/spring-boot[#spring-boot room on Gitter]. + + == VMware Open Source Software Support + If you are interested in more dedicated support, VMware provides https://spring.io/support[premium support] for Spring Boot. diff --git a/antora/package-lock.json b/antora/package-lock.json new file mode 100644 index 000000000000..c3d2ead92458 --- /dev/null +++ b/antora/package-lock.json @@ -0,0 +1,2968 @@ +{ + "name": "antora", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@antora/atlas-extension": "1.0.0-alpha.2", + "@antora/cli": "3.2.0-alpha.4", + "@antora/site-generator": "3.2.0-alpha.4", + "@asciidoctor/tabs": "1.0.0-beta.6", + "@springio/antora-extensions": "1.11.1", + "@springio/antora-xref-extension": "1.0.0-alpha.3", + "@springio/antora-zip-contents-collector-extension": "1.0.0-alpha.8", + "@springio/asciidoctor-extensions": "1.0.0-alpha.10" + } + }, + "node_modules/@antora/asciidoc-loader": { + "version": "3.2.0-alpha.4", + "resolved": "https://registry.npmjs.org/@antora/asciidoc-loader/-/asciidoc-loader-3.2.0-alpha.4.tgz", + "integrity": "sha512-FRNq3ErMFMJPHxYQxHyuMdX4YULs9aXc+njmAoMGbyO9SNAYCwzirOBXVQegefcGDn85Y/3zLU6BanZNpxCaXQ==", + "dependencies": { + "@antora/logger": "3.2.0-alpha.4", + "@antora/user-require-helper": "~2.0", + "@asciidoctor/core": "~2.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/atlas-extension": { + "version": "1.0.0-alpha.2", + "resolved": "https://registry.npmjs.org/@antora/atlas-extension/-/atlas-extension-1.0.0-alpha.2.tgz", + "integrity": "sha512-tOQy3eQjvoYGV3UnDaOjkaCehbWSpjQWRdCCYXx8c2Do4rysclOVVN4t4AsfeOHK+BoWlKqa7mldb1DCYOBQTw==", + "dependencies": { + "@antora/expand-path-helper": "~2.0", + "cache-directory": "~2.0", + "node-gzip": "~1.1", + "simple-get": "~4.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/cli": { + "version": "3.2.0-alpha.4", + "resolved": "https://registry.npmjs.org/@antora/cli/-/cli-3.2.0-alpha.4.tgz", + "integrity": "sha512-tRTdO1Cp5hmV4sZZbD/Y0bZ+fQSCcESc1Y8txmCG+25lFC8PefjKC0mgWOq25RAjNxlUZ390DU35NNR9McjUsA==", + "dependencies": { + "@antora/logger": "3.2.0-alpha.4", + "@antora/playbook-builder": "3.2.0-alpha.4", + "@antora/user-require-helper": "~2.0", + "commander": "~10.0" + }, + "bin": { + "antora": "bin/antora" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/content-aggregator": { + "version": "3.2.0-alpha.4", + "resolved": "https://registry.npmjs.org/@antora/content-aggregator/-/content-aggregator-3.2.0-alpha.4.tgz", + "integrity": "sha512-+Y6WybHnNN7bw/MFUPL8ca6SiNqT2AUZCI1NRhwYym2JD6dBIwGedNEh76a7MGTObQXKjlBrmm025FHBWg4j5Q==", + "dependencies": { + "@antora/expand-path-helper": "~2.0", + "@antora/logger": "3.2.0-alpha.4", + "@antora/user-require-helper": "~2.0", + "braces": "~3.0", + "cache-directory": "~2.0", + "glob-stream": "~7.0", + "hpagent": "~1.2", + "isomorphic-git": "~1.25", + "js-yaml": "~4.1", + "multi-progress": "~4.0", + "picomatch": "~2.3", + "progress": "~2.0", + "should-proxy": "~1.0", + "simple-get": "~4.0", + "vinyl": "~2.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/content-classifier": { + "version": "3.2.0-alpha.4", + "resolved": "https://registry.npmjs.org/@antora/content-classifier/-/content-classifier-3.2.0-alpha.4.tgz", + "integrity": "sha512-XN5JzSum/nxv1fEb7j8vFG1FLaEnBXnPxzY+hC1/pGODXVVlFVyRoxR35fx91oJ8TgVIHI+bLvymsF/MJYYmbQ==", + "dependencies": { + "@antora/asciidoc-loader": "3.2.0-alpha.4", + "@antora/logger": "3.2.0-alpha.4", + "mime-types": "~2.1", + "vinyl": "~2.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/document-converter": { + "version": "3.2.0-alpha.4", + "resolved": "https://registry.npmjs.org/@antora/document-converter/-/document-converter-3.2.0-alpha.4.tgz", + "integrity": "sha512-Wbh76FELpHBfqvnKiAPvXtxkTeGP0Fk/2nZBkmTTWbpBSs98o7YfNWnVQ9Ky86jdXGmxM+LMNFoXKVIzNbpd3g==", + "dependencies": { + "@antora/asciidoc-loader": "3.2.0-alpha.4" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/expand-path-helper": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@antora/expand-path-helper/-/expand-path-helper-2.0.0.tgz", + "integrity": "sha512-CSMBGC+tI21VS2kGW3PV7T2kQTM5eT3f2GTPVLttwaNYbNxDve08en/huzszHJfxo11CcEs26Ostr0F2c1QqeA==", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@antora/file-publisher": { + "version": "3.2.0-alpha.4", + "resolved": "https://registry.npmjs.org/@antora/file-publisher/-/file-publisher-3.2.0-alpha.4.tgz", + "integrity": "sha512-DqH5RpdcshVhA4Xq2JQ2M7Rk3IhrOtV5ivI+oXU4yQlQW7IqchJnCmsOa885xPo8f5v2fpXRaZ5iyvRBUMaH2A==", + "dependencies": { + "@antora/expand-path-helper": "~2.0", + "@antora/user-require-helper": "~2.0", + "@vscode/gulp-vinyl-zip": "~2.5", + "vinyl": "~2.2", + "vinyl-fs": "~3.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/logger": { + "version": "3.2.0-alpha.4", + "resolved": "https://registry.npmjs.org/@antora/logger/-/logger-3.2.0-alpha.4.tgz", + "integrity": "sha512-ph+vIUVvZQHLA3EreBaViAB01IYzq0yjdcUSp5CVcqxU9+CnuuBKDvix6Pll7LJwgFJ8i3UX4mVVW1lI3h2tYg==", + "dependencies": { + "@antora/expand-path-helper": "~2.0", + "pino": "~8.14", + "pino-pretty": "~10.0", + "sonic-boom": "~3.3" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/navigation-builder": { + "version": "3.2.0-alpha.4", + "resolved": "https://registry.npmjs.org/@antora/navigation-builder/-/navigation-builder-3.2.0-alpha.4.tgz", + "integrity": "sha512-qoF57QOIi2RvmqSYuaetA2IRoHizPXIs5kUKmk/uqiMq6akWaklSI9QHPhq6VsNgLdWaUomQ+gJCvnhjQQkw5w==", + "dependencies": { + "@antora/asciidoc-loader": "3.2.0-alpha.4" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/page-composer": { + "version": "3.2.0-alpha.4", + "resolved": "https://registry.npmjs.org/@antora/page-composer/-/page-composer-3.2.0-alpha.4.tgz", + "integrity": "sha512-LAbNdUYomqx9iCT+mP1bF17U5vIoBObD0VAtjF6IMD+b5xyDN1O82rZgHhDByn8R6es0oA6DrkQMwPH+oxR7fQ==", + "dependencies": { + "@antora/logger": "3.2.0-alpha.4", + "handlebars": "~4.7", + "require-from-string": "~2.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/playbook-builder": { + "version": "3.2.0-alpha.4", + "resolved": "https://registry.npmjs.org/@antora/playbook-builder/-/playbook-builder-3.2.0-alpha.4.tgz", + "integrity": "sha512-79ERFWrOAaxr1iEW8qS7rMpjyYD9Lwt53Y18qIGLf0jtqgIVmmgJtaSR1qwrO/rYd2GIqWpm+s12NWzqJLZAog==", + "dependencies": { + "@iarna/toml": "~2.2", + "convict": "~6.2", + "js-yaml": "~4.1", + "json5": "~2.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/redirect-producer": { + "version": "3.2.0-alpha.4", + "resolved": "https://registry.npmjs.org/@antora/redirect-producer/-/redirect-producer-3.2.0-alpha.4.tgz", + "integrity": "sha512-BMm0l6jGdKN7r5xCP8cQmHy+owTwT0pXlsx1ZmTXZiq66Ec0H6ykKNQhx7scezbytlg18bwXUYNAtEQg/6c2AA==", + "dependencies": { + "vinyl": "~2.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/site-generator": { + "version": "3.2.0-alpha.4", + "resolved": "https://registry.npmjs.org/@antora/site-generator/-/site-generator-3.2.0-alpha.4.tgz", + "integrity": "sha512-QYaq9TMyPLHnUnyiO4AzRnU7igGE6Kc41j9ff8ijrGEK/YqxRmDTG74r8VdgdtotpSjcnXTQPJ46neJKExcKvg==", + "dependencies": { + "@antora/asciidoc-loader": "3.2.0-alpha.4", + "@antora/content-aggregator": "3.2.0-alpha.4", + "@antora/content-classifier": "3.2.0-alpha.4", + "@antora/document-converter": "3.2.0-alpha.4", + "@antora/file-publisher": "3.2.0-alpha.4", + "@antora/logger": "3.2.0-alpha.4", + "@antora/navigation-builder": "3.2.0-alpha.4", + "@antora/page-composer": "3.2.0-alpha.4", + "@antora/playbook-builder": "3.2.0-alpha.4", + "@antora/redirect-producer": "3.2.0-alpha.4", + "@antora/site-mapper": "3.2.0-alpha.4", + "@antora/site-publisher": "3.2.0-alpha.4", + "@antora/ui-loader": "3.2.0-alpha.4", + "@antora/user-require-helper": "~2.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/site-mapper": { + "version": "3.2.0-alpha.4", + "resolved": "https://registry.npmjs.org/@antora/site-mapper/-/site-mapper-3.2.0-alpha.4.tgz", + "integrity": "sha512-9SD2HOxqYjNQ88qg4QDVbIvSyd3aYeVAUwdA50eRvWLgnToTwDorjt/nfZnbRXGNszWil9nOZ+F8+LV2BkPpTw==", + "dependencies": { + "@antora/content-classifier": "3.2.0-alpha.4", + "vinyl": "~2.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/site-publisher": { + "version": "3.2.0-alpha.4", + "resolved": "https://registry.npmjs.org/@antora/site-publisher/-/site-publisher-3.2.0-alpha.4.tgz", + "integrity": "sha512-GiakkrGR/eTjh7o/ZISoYDUcDSXn/zodXTiX++fqHSrzscWTOcId4IC3Lj8oRDmISrh7U3la6Ydtld4xMbtSsQ==", + "dependencies": { + "@antora/file-publisher": "3.2.0-alpha.4" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/ui-loader": { + "version": "3.2.0-alpha.4", + "resolved": "https://registry.npmjs.org/@antora/ui-loader/-/ui-loader-3.2.0-alpha.4.tgz", + "integrity": "sha512-I7srOOR/tsORa+L+xIkPCVR365yQKO1JEylDkQbaMhbuPFhTmRV4mQXgUeLsfprtVXiSoaFw960SrWd77TX/dA==", + "dependencies": { + "@antora/expand-path-helper": "~2.0", + "@vscode/gulp-vinyl-zip": "~2.5", + "braces": "~3.0", + "cache-directory": "~2.0", + "glob-stream": "~7.0", + "hpagent": "~1.2", + "js-yaml": "~4.1", + "picomatch": "~2.3", + "should-proxy": "~1.0", + "simple-get": "~4.0", + "vinyl": "~2.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@antora/user-require-helper": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@antora/user-require-helper/-/user-require-helper-2.0.0.tgz", + "integrity": "sha512-5fMfBZfw4zLoFdDAPMQX6Frik90uvfD8rXOA4UpXPOUikkX4uT1Rk6m0/4oi8oS3fcjiIl0k/7Nc+eTxW5TcQQ==", + "dependencies": { + "@antora/expand-path-helper": "~2.0" + }, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@asciidoctor/core": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@asciidoctor/core/-/core-2.2.7.tgz", + "integrity": "sha512-63cfnV606vXNUnh/zcuUi5e3tY5qTzaYY5pGP4p9sRk8CcCmX4Z8OfU0BkfM8/k2Y7Cz/jZqxL+vzHjrLQa8tw==", + "dependencies": { + "asciidoctor-opal-runtime": "0.3.3", + "unxhr": "1.0.1" + }, + "engines": { + "node": ">=8.11", + "npm": ">=5.0.0", + "yarn": ">=1.1.0" + } + }, + "node_modules/@asciidoctor/tabs": { + "version": "1.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@asciidoctor/tabs/-/tabs-1.0.0-beta.6.tgz", + "integrity": "sha512-gGZnW7UfRXnbiyKNd9PpGKtSuD8+DsqaaTSbQ1dHVkZ76NaolLhdQg8RW6/xqN3pX1vWZEcF4e81+Oe9rNRWxg==", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", + "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" + }, + "node_modules/@springio/antora-extensions": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@springio/antora-extensions/-/antora-extensions-1.11.1.tgz", + "integrity": "sha512-mS5w7Nq1AGUEmOqhohRUG6qIBkYaG+ApKshqbb+e+Slg8ZnPsjrNeAJumXwLsv1CrEFJRWdxq6owXiK/21Rzyw==", + "dependencies": { + "@antora/expand-path-helper": "~2.0", + "archiver": "^5.3.1", + "asciinema-player": "^3.6.1", + "cache-directory": "~2.0", + "ci": "^2.3.0", + "decompress": "4.2.1", + "fast-xml-parser": "latest", + "handlebars": "latest" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@springio/antora-xref-extension": { + "version": "1.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@springio/antora-xref-extension/-/antora-xref-extension-1.0.0-alpha.3.tgz", + "integrity": "sha512-6NJqrHrnwnfkBcQHDABxVNaCP74MBp3iCayaxTeXL90BVo93Pqo4bQhzrISSrBXpb5rXqZWb3DPuFMCXSeWolg==", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@springio/antora-zip-contents-collector-extension": { + "version": "1.0.0-alpha.8", + "resolved": "https://registry.npmjs.org/@springio/antora-zip-contents-collector-extension/-/antora-zip-contents-collector-extension-1.0.0-alpha.8.tgz", + "integrity": "sha512-pp1hozg/UGQpkrJ17NImrcRd5b8hxIsLXHDYeBBR/vtzR7uiokxA1JxtL6PTfPAdjnrYf+2ApXdCgzLdNI7Rgg==", + "dependencies": { + "@antora/expand-path-helper": "~2.0", + "cache-directory": "~2.0", + "glob-stream": "~7.0", + "isomorphic-git": "~1.21", + "js-yaml": "~4.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@springio/antora-zip-contents-collector-extension/node_modules/isomorphic-git": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.21.0.tgz", + "integrity": "sha512-ZqCAUM63CYepA3fB8H7NVyPSiOkgzIbQ7T+QPrm9xtYgQypN9JUJ5uLMjB5iTfomdJf3mdm6aSxjZwnT6ubvEA==", + "dependencies": { + "async-lock": "^1.1.0", + "clean-git-ref": "^2.0.1", + "crc-32": "^1.2.0", + "diff3": "0.0.3", + "ignore": "^5.1.4", + "minimisted": "^2.0.0", + "pako": "^1.0.10", + "pify": "^4.0.1", + "readable-stream": "^3.4.0", + "sha.js": "^2.4.9", + "simple-get": "^4.0.1" + }, + "bin": { + "isogit": "cli.cjs" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@springio/antora-zip-contents-collector-extension/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@springio/asciidoctor-extensions": { + "version": "1.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/@springio/asciidoctor-extensions/-/asciidoctor-extensions-1.0.0-alpha.10.tgz", + "integrity": "sha512-3+LYhKYsTZKlUq3M99L5W67x+wI6TGlFkD23ZcjKP6undjy3xf7xZL7Ndmslf8trQ24V+QkaCmFtF/2JQY9KwA==", + "dependencies": { + "js-yaml": "~4.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@vscode/gulp-vinyl-zip": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@vscode/gulp-vinyl-zip/-/gulp-vinyl-zip-2.5.0.tgz", + "integrity": "sha512-PP/xkOoLBSY3V04HmzRxF+NOxkRJ/m2D0YwWpfx1FCFv5G8+sZUGPvxX+LRgdJ5vQcR1RHck5x1IkHi75Qjdbw==", + "dependencies": { + "queue": "^4.2.1", + "through": "^2.3.8", + "through2": "^2.0.3", + "vinyl": "^2.0.2", + "vinyl-fs": "^3.0.3", + "yauzl": "^2.2.1", + "yazl": "^2.2.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha512-WLbYiXzD3y/ATLZFufV/rZvWdZOs+Z/+5v1rBZ463Jn398pa6kcde27cvozYnBoxXblGZTFfoPpsaEw0orU5BA==", + "dependencies": { + "buffer-equal": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/asciidoctor-opal-runtime": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/asciidoctor-opal-runtime/-/asciidoctor-opal-runtime-0.3.3.tgz", + "integrity": "sha512-/CEVNiOia8E5BMO9FLooo+Kv18K4+4JBFRJp8vUy/N5dMRAg+fRNV4HA+o6aoSC79jVU/aT5XvUpxSxSsTS8FQ==", + "dependencies": { + "glob": "7.1.3", + "unxhr": "1.0.1" + }, + "engines": { + "node": ">=8.11" + } + }, + "node_modules/asciidoctor-opal-runtime/node_modules/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/asciinema-player": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/asciinema-player/-/asciinema-player-3.7.1.tgz", + "integrity": "sha512-zDJteGjBzNQhHEnD0aG7GqV3E53sOyKb1WCxKNRm2PquU70Lq3s4xxb91wyDS0hBJ3J/TB8aY3y8gjGPN+T23A==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "solid-js": "^1.3.0" + } + }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + }, + "node_modules/async-lock": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", + "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", + "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==" + }, + "node_modules/cache-directory": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cache-directory/-/cache-directory-2.0.0.tgz", + "integrity": "sha512-7YKEapH+2Uikde8hySyfobXBqPKULDyHNl/lhKm7cKf/GJFdG/tU/WpLrOg2y9aUrQrWUilYqawFIiGJPS6gDA==", + "dependencies": { + "xdg-basedir": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ci": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ci/-/ci-2.3.0.tgz", + "integrity": "sha512-0MGXkzJKkwV3enG7RUxjJKdiAkbaZ7visCjitfpCN2BQjv02KGRMxCHLv4RPokkjJ4xR33FLMAXweS+aQ0pFSQ==", + "bin": { + "ci": "dist/cli.js" + }, + "funding": { + "url": "https://github.com/privatenumber/ci?sponsor=1" + } + }, + "node_modules/clean-git-ref": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz", + "integrity": "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw==" + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==" + }, + "node_modules/cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dependencies": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "node_modules/cloneable-readable/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/cloneable-readable/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/cloneable-readable/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "engines": { + "node": ">=14" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/convict": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/convict/-/convict-6.2.4.tgz", + "integrity": "sha512-qN60BAwdMVdofckX7AlohVJ2x9UvjTNoKVXCL2LxFk1l7757EJqf1nySdMkPQer0bt8kQ5lQiyZ9/2NvrFBuwQ==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "yargs-parser": "^20.2.7" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "engines": { + "node": "*" + } + }, + "node_modules/decompress": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", + "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", + "dependencies": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "dependencies": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar/node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/decompress-tar/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/decompress-tar/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/decompress-tar/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/decompress-tar/node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "dependencies": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "dependencies": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", + "dependencies": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip/node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/diff3": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz", + "integrity": "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g==" + }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==" + }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, + "node_modules/fast-xml-parser": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz", + "integrity": "sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dependencies": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "node_modules/flush-write-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/flush-write-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/flush-write-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==", + "dependencies": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-7.0.0.tgz", + "integrity": "sha512-evR4kvr6s0Yo5t4CD4H171n4T8XcnPFznvsbeN8K9FPzc0Q0wYqcOWyGtck2qcvJSLXKnU6DnDyfmbDDabYvRQ==", + "dependencies": { + "extend": "^3.0.2", + "glob": "^7.2.0", + "glob-parent": "^6.0.2", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.1", + "pumpify": "^2.0.1", + "readable-stream": "^3.6.0", + "remove-trailing-separator": "^1.1.0", + "to-absolute-glob": "^2.0.2", + "unique-stream": "^2.3.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/help-me": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-4.2.0.tgz", + "integrity": "sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==", + "dependencies": { + "glob": "^8.0.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/help-me/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/help-me/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/help-me/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hpagent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", + "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==", + "engines": { + "node": ">=14" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==" + }, + "node_modules/is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==" + }, + "node_modules/is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/isomorphic-git": { + "version": "1.25.10", + "resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.25.10.tgz", + "integrity": "sha512-IxGiaKBwAdcgBXwIcxJU6rHLk+NrzYaaPKXXQffcA0GW3IUrQXdUPDXDo+hkGVcYruuz/7JlGBiuaeTCgIgivQ==", + "dependencies": { + "async-lock": "^1.4.1", + "clean-git-ref": "^2.0.1", + "crc-32": "^1.2.0", + "diff3": "0.0.3", + "ignore": "^5.1.4", + "minimisted": "^2.0.0", + "pako": "^1.0.10", + "pify": "^4.0.1", + "readable-stream": "^3.4.0", + "sha.js": "^2.4.9", + "simple-get": "^4.0.1" + }, + "bin": { + "isogit": "cli.cjs" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/isomorphic-git/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha512-IpSVCk9AYvLHo5ctcIXxOBpMWUe+4TKN3VPWAKUbJikkmsGp0VrSM8IttVc32D6J4WUsiPE6aEFRNmIoF/gdow==", + "dependencies": { + "flush-write-stream": "^1.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==" + }, + "node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/make-dir/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimisted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minimisted/-/minimisted-2.0.1.tgz", + "integrity": "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA==", + "dependencies": { + "minimist": "^1.2.5" + } + }, + "node_modules/multi-progress": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/multi-progress/-/multi-progress-4.0.0.tgz", + "integrity": "sha512-9zcjyOou3FFCKPXsmkbC3ethv51SFPoA4dJD6TscIp2pUmy26kBDZW6h9XofPELrzseSkuD7r0V+emGEeo39Pg==", + "peerDependencies": { + "progress": "^2.0.0" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "node_modules/node-gzip": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/node-gzip/-/node-gzip-1.1.2.tgz", + "integrity": "sha512-ZB6zWpfZHGtxZnPMrJSKHVPrRjURoUzaDbLFj3VO70mpLTW5np96vXyHwft4Id0o+PYIzgDkBUjIzaNHhQ8srw==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "dependencies": { + "once": "^1.3.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha512-Z87aSjx3r5c0ZB7bcJqIgIRX5bxR7A4aSzvIbaxd0oTkWBCOoKfuGHiKj60CHVUgg1Phm5yMZzBdt8XqRs73Mw==", + "dependencies": { + "readable-stream": "^2.0.1" + } + }, + "node_modules/ordered-read-streams/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/ordered-read-streams/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/ordered-read-streams/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==" + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pino": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.14.2.tgz", + "integrity": "sha512-zKu9aWeSWTy1JgvxIpZveJKKsAr4+6uNMZ0Vf0KRwzl/UNZA3XjHiIl/0WwqLMkDwuHuDkT5xAgPA2jpKq4whA==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "v1.0.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^2.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.1.0", + "thread-stream": "^2.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz", + "integrity": "sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-abstract-transport/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/pino-abstract-transport/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-pretty": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.0.1.tgz", + "integrity": "sha512-yrn00+jNpkvZX/NrPVCPIVHAfTDy3ahF0PND9tKqZk4j9s+loK8dpzrJj4dGb7i+WLuR50ussuTAiWoMWU+qeA==", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.0", + "fast-safe-stringify": "^2.1.1", + "help-me": "^4.0.1", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-pretty/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/pino-pretty/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/process-warning": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.2.tgz", + "integrity": "sha512-n9wh8tvBe5sFmsqlg+XQhaQLumwpqoAUruLwjCopgTmUBjJ/fjtBsJzKleCaIGBOMXYEhp1YfKl4d7rJ5ZKJGA==" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "dependencies": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + } + }, + "node_modules/queue": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/queue/-/queue-4.5.1.tgz", + "integrity": "sha512-AMD7w5hRXcFSb8s9u38acBZ+309u6GsiibP4/0YacJeaurRshogB7v/ZcVPxP5gD5+zIw6ixRHdutiYUJfwKHw==", + "dependencies": { + "inherits": "~2.0.0" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dependencies": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha512-wigO8/O08XHb8YPzpDDT+QmRANfW6vLqxfaXm1YXhnFf3AkSLyjfG3GEFg4McZkmgL7KvCj5u2KczkvSP6NfHA==", + "dependencies": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==" + }, + "node_modules/replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha512-NYDgziiroVeDC29xq7bp/CacZERYsA9bXYd1ZmcJlF3BcrZv5pTb4NG7SjdyKDnXZ84aC4vo2u6sNKIA1LCu/A==", + "dependencies": { + "value-or-function": "^3.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, + "node_modules/seek-bzip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", + "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", + "dependencies": { + "commander": "^2.8.1" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/seek-bzip/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/seroval": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.0.7.tgz", + "integrity": "sha512-n6ZMQX5q0Vn19Zq7CIKNIo7E75gPkGCFUEqDpa8jgwpYr/vScjqnQ6H09t1uIiZ0ZSK0ypEGvrYK2bhBGWsGdw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/seroval-plugins": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.0.7.tgz", + "integrity": "sha512-GO7TkWvodGp6buMEX9p7tNyIkbwlyuAWbI6G9Ec5bhcm7mQdu3JOK1IXbEUwb3FVzSc363GraG/wLW23NSavIw==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "seroval": "^1.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/should-proxy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/should-proxy/-/should-proxy-1.0.4.tgz", + "integrity": "sha512-RPQhIndEIVUCjkfkQ6rs6sOR6pkxJWCNdxtfG5pP0RVgUYbK5911kLTF0TNcCC0G3YCGd492rMollFT2aTd9iQ==" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/solid-js": { + "version": "1.8.17", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.8.17.tgz", + "integrity": "sha512-E0FkUgv9sG/gEBWkHr/2XkBluHb1fkrHywUgA6o6XolPDCJ4g1HaLmQufcBBhiF36ee40q+HpG/vCZu7fLpI3Q==", + "dependencies": { + "csstype": "^3.1.0", + "seroval": "^1.0.4", + "seroval-plugins": "^1.0.3" + } + }, + "node_modules/sonic-boom": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.3.0.tgz", + "integrity": "sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "dependencies": { + "is-natural-number": "^4.0.1" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/thread-stream": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", + "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "dependencies": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + } + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA==", + "dependencies": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q==", + "dependencies": { + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "dependencies": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } + }, + "node_modules/unxhr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unxhr/-/unxhr-1.0.1.tgz", + "integrity": "sha512-MAhukhVHyaLGDjyDYhy8gVjWJyhTECCdNsLwlMoGFoNJ3o79fpQhtQuzmAE4IxCMDwraF4cW8ZjpAV0m9CRQbg==", + "engines": { + "node": ">=8.11" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "dependencies": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-fs/node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/vinyl-fs/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/vinyl-fs/node_modules/glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw==", + "dependencies": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-fs/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vinyl-fs/node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/vinyl-fs/node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/vinyl-fs/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/vinyl-fs/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/vinyl-fs/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA==", + "dependencies": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-sourcemap/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha512-1Dly4xqlulvPD3fZUQJLY+FUIeqN3N2MM3uqe4rCJftAvOjFa3jFGfctOgluGx4ahPbUCsZkmJILiP0Vi4T6lQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dependencies": { + "buffer-crc32": "~0.2.3" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/antora/package.json b/antora/package.json new file mode 100644 index 000000000000..f7aa971cd360 --- /dev/null +++ b/antora/package.json @@ -0,0 +1,18 @@ +{ + "scripts": { + "antora": "node npm/antora.js" + }, + "dependencies": { + "@antora/cli": "3.2.0-alpha.4", + "@antora/site-generator": "3.2.0-alpha.4", + "@antora/atlas-extension": "1.0.0-alpha.2", + "@springio/antora-extensions": "1.11.1", + "@springio/antora-xref-extension": "1.0.0-alpha.3", + "@springio/antora-zip-contents-collector-extension": "1.0.0-alpha.8", + "@asciidoctor/tabs": "1.0.0-beta.6", + "@springio/asciidoctor-extensions": "1.0.0-alpha.10" + }, + "config": { + "ui-bundle-url": "https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.16/ui-bundle.zip" + } +} diff --git a/build.gradle b/build.gradle index 0795f569d49e..4f490c8e7c19 100644 --- a/build.gradle +++ b/build.gradle @@ -1,29 +1,12 @@ plugins { id "base" id "org.jetbrains.kotlin.jvm" apply false // https://youtrack.jetbrains.com/issue/KT-30276 - id "io.spring.nohttp" version "0.0.11" } description = "Spring Boot Build" defaultTasks 'build' -nohttp { - allowlistFile = project.file("src/nohttp/allowlist.lines") - source.exclude "**/bin/**" - source.exclude "**/build/**" - source.exclude "**/out/**" - source.exclude "**/target/**" - source.exclude "**/.settings/**" - source.exclude "**/.classpath" - source.exclude "**/.project" - source.exclude "spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/export.tar" -} - -check { - dependsOn checkstyleNohttp -} - allprojects { group "org.springframework.boot" @@ -41,7 +24,3 @@ allprojects { resolutionStrategy.cacheChangingModulesFor 0, "minutes" } } - -tasks.named("checkstyleNohttp").configure { - maxHeapSize = "1536m" -} diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 7afefa5d753a..8356c4275f8b 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -17,11 +17,12 @@ def versions = [:] new File(projectDir.parentFile, "gradle.properties").withInputStream { def properties = new Properties() properties.load(it) - ["assertj", "commonsCodec", "hamcrest", "jackson", "junitJupiter", - "kotlin", "maven", "springFramework"].each { + ["assertj", "commonsCodec", "hamcrest", "junitJupiter", "kotlin", "maven", "snakeYaml"].each { versions[it] = properties[it + "Version"] } } +versions["jackson"] = "2.15.3" +versions["springFramework"] = "6.0.12" ext.set("versions", versions) if (versions.springFramework.contains("-")) { repositories { @@ -40,19 +41,25 @@ dependencies { implementation(platform("org.springframework:spring-framework-bom:${versions.springFramework}")) implementation("com.diffplug.gradle:goomph:3.37.2") + implementation("dev.adamko.dokkatoo:dokkatoo-plugin:2.3.1") implementation("com.fasterxml.jackson.core:jackson-databind:${versions.jackson}") + implementation("com.github.node-gradle:gradle-node-plugin:3.5.1") implementation("com.gradle:develocity-gradle-plugin:3.17.2") implementation("com.tngtech.archunit:archunit:1.0.0") implementation("commons-codec:commons-codec:${versions.commonsCodec}") implementation("de.undercouch.download:de.undercouch.download.gradle.plugin:5.5.0") + implementation("io.spring.gradle.antora:spring-antora-plugin:0.0.1") implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}") + implementation("io.spring.nohttp:nohttp-gradle:0.0.11") + implementation("org.apache.httpcomponents.client5:httpclient5:5.3.1") implementation("org.apache.maven:maven-embedder:${versions.maven}") - implementation("org.asciidoctor:asciidoctor-gradle-jvm:3.3.2") + implementation("org.antora:gradle-antora-plugin:1.0.0") implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}") implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${versions.kotlin}") implementation("org.springframework:spring-context") implementation("org.springframework:spring-core") implementation("org.springframework:spring-web") + implementation("org.yaml:snakeyaml:${versions.snakeYaml}") testImplementation("org.assertj:assertj-core:${versions.assertj}") testImplementation("org.hamcrest:hamcrest:${versions.hamcrest}") @@ -62,6 +69,12 @@ dependencies { testRuntimeOnly("org.junit.platform:junit-platform-launcher") } +configurations.all { + exclude group:"org.slf4j", module:"slf4j-api" + exclude group:"ch.qos.logback", module:"logback-classic" + exclude group:"ch.qos.logback", module:"logback-core" +} + gradlePlugin { plugins { annotationProcessorPlugin { @@ -92,6 +105,10 @@ gradlePlugin { id = "org.springframework.boot.deployed" implementationClass = "org.springframework.boot.build.DeployedPlugin" } + dockerTestPlugin { + id = "org.springframework.boot.docker-test" + implementationClass = "org.springframework.boot.build.test.DockerTestPlugin" + } integrationTestPlugin { id = "org.springframework.boot.integration-test" implementationClass = "org.springframework.boot.build.test.IntegrationTestPlugin" @@ -131,9 +148,12 @@ test { useJUnitPlatform() } -eclipse.classpath.file.whenMerged { - def jreEntry = entries.find { it.path.contains("org.eclipse.jdt.launching.JRE_CONTAINER") } - jreEntry.entryAttributes['module'] = 'true' - jreEntry.entryAttributes['limit-modules'] = 'java.base' +eclipse { + jdt { + file { + withProperties { + it["org.eclipse.jdt.core.compiler.ignoreUnnamedModuleForSplitPackage"] = "enabled" + } + } + } } - diff --git a/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java new file mode 100644 index 000000000000..a05289a4a220 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java @@ -0,0 +1,213 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.gradle.node.NodeExtension; +import com.github.gradle.node.npm.task.NpmInstallTask; +import io.spring.gradle.antora.GenerateAntoraYmlPlugin; +import io.spring.gradle.antora.GenerateAntoraYmlTask; +import org.antora.gradle.AntoraPlugin; +import org.antora.gradle.AntoraTask; +import org.gradle.StartParameter; +import org.gradle.api.Project; +import org.gradle.api.logging.LogLevel; +import org.gradle.api.plugins.JavaBasePlugin; +import org.gradle.api.tasks.Copy; +import org.gradle.api.tasks.TaskContainer; + +import org.springframework.boot.build.antora.AntoraAsciidocAttributes; +import org.springframework.boot.build.antora.GenerateAntoraPlaybook; +import org.springframework.boot.build.bom.BomExtension; +import org.springframework.boot.build.constraints.ExtractVersionConstraints; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Conventions that are applied in the presence of the {@link AntoraPlugin}. + * + * @author Phillip Webb + */ +public class AntoraConventions { + + private static final String DEPENDENCIES_PATH = ":spring-boot-project:spring-boot-dependencies"; + + private static final String ANTORA_SOURCE_DIR = "src/docs/antora"; + + private static final List NAV_FILES = List.of("nav.adoc", "local-nav.adoc"); + + void apply(Project project) { + project.getPlugins().withType(AntoraPlugin.class, (antoraPlugin) -> apply(project, antoraPlugin)); + } + + private void apply(Project project, AntoraPlugin antoraPlugin) { + ExtractVersionConstraints dependencyVersionsTask = addDependencyVersionsTask(project); + project.getPlugins().apply(GenerateAntoraYmlPlugin.class); + TaskContainer tasks = project.getTasks(); + GenerateAntoraPlaybook generateAntoraPlaybookTask = tasks.create("generateAntoraPlaybook", + GenerateAntoraPlaybook.class); + configureGenerateAntoraPlaybookTask(project, generateAntoraPlaybookTask); + Copy copyAntoraPackageJsonTask = tasks.create("copyAntoraPackageJson", Copy.class); + configureCopyAntoraPackageJsonTask(project, copyAntoraPackageJsonTask); + NpmInstallTask npmInstallTask = tasks.create("antoraNpmInstall", NpmInstallTask.class); + configureNpmInstallTask(project, npmInstallTask, copyAntoraPackageJsonTask); + tasks.withType(GenerateAntoraYmlTask.class, (generateAntoraYmlTask) -> configureGenerateAntoraYmlTask(project, + generateAntoraYmlTask, dependencyVersionsTask)); + tasks.withType(AntoraTask.class, + (antoraTask) -> configureAntoraTask(project, antoraTask, npmInstallTask, generateAntoraPlaybookTask)); + project.getExtensions() + .configure(NodeExtension.class, (nodeExtension) -> configureNodeExtension(project, nodeExtension)); + } + + private void configureGenerateAntoraPlaybookTask(Project project, + GenerateAntoraPlaybook generateAntoraPlaybookTask) { + File nodeProjectDir = getNodeProjectDir(project.getBuildDir()); + generateAntoraPlaybookTask.getOutputFile().set(new File(nodeProjectDir, "antora-playbook.yml")); + } + + private void configureCopyAntoraPackageJsonTask(Project project, Copy copyAntoraPackageJsonTask) { + copyAntoraPackageJsonTask + .from(project.getRootProject().file("antora"), (spec) -> spec.include("package.json", "package-lock.json")) + .into(getNodeProjectDir(project.getBuildDir())); + } + + private void configureNpmInstallTask(Project project, NpmInstallTask npmInstallTask, Copy copyAntoraPackageJson) { + npmInstallTask.dependsOn(copyAntoraPackageJson); + Map environment = new HashMap<>(); + environment.put("npm_config_omit", "optional"); + environment.put("npm_config_update_notifier", "false"); + npmInstallTask.getEnvironment().set(environment); + npmInstallTask.getNpmCommand().set(List.of("ci", "--silent", "--no-progress")); + } + + private ExtractVersionConstraints addDependencyVersionsTask(Project project) { + return project.getTasks() + .create("dependencyVersions", ExtractVersionConstraints.class, + (task) -> task.enforcedPlatform(DEPENDENCIES_PATH)); + } + + private void configureGenerateAntoraYmlTask(Project project, GenerateAntoraYmlTask generateAntoraYmlTask, + ExtractVersionConstraints dependencyVersionsTask) { + generateAntoraYmlTask.getOutputs().doNotCacheIf("getAsciidocAttributes() changes output", (task) -> true); + generateAntoraYmlTask.dependsOn(dependencyVersionsTask); + generateAntoraYmlTask.setProperty("componentName", "boot"); + generateAntoraYmlTask.setProperty("outputFile", + new File(project.getBuildDir(), "generated/docs/antora-yml/antora.yml")); + generateAntoraYmlTask.setProperty("yml", getDefaultYml(project)); + generateAntoraYmlTask.doFirst((task) -> generateAntoraYmlTask.getAsciidocAttributes() + .putAll(project.provider(() -> getAsciidocAttributes(project, dependencyVersionsTask)))); + } + + private Map getDefaultYml(Project project) { + String navFile = null; + for (String candidate : NAV_FILES) { + if (project.file(ANTORA_SOURCE_DIR + "/" + candidate).exists()) { + Assert.state(navFile == null, "Multiple nav files found"); + navFile = candidate; + } + } + Map defaultYml = new LinkedHashMap<>(); + defaultYml.put("title", "Spring Boot"); + if (navFile != null) { + defaultYml.put("nav", List.of(navFile)); + } + return defaultYml; + } + + private Map getAsciidocAttributes(Project project, + ExtractVersionConstraints dependencyVersionsTask) { + BomExtension bom = (BomExtension) project.project(DEPENDENCIES_PATH).getExtensions().getByName("bom"); + Map dependencyVersions = dependencyVersionsTask.getVersionConstraints(); + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes(project, bom, dependencyVersions); + return attributes.get(); + } + + private void configureAntoraTask(Project project, AntoraTask antoraTask, NpmInstallTask npmInstallTask, + GenerateAntoraPlaybook generateAntoraPlaybookTask) { + antoraTask.setGroup("Documentation"); + antoraTask.dependsOn(npmInstallTask, generateAntoraPlaybookTask); + antoraTask.setPlaybook("antora-playbook.yml"); + antoraTask.setUiBundleUrl(getUiBundleUrl(project)); + antoraTask.getArgs().set(project.provider(() -> getAntoraNpxArs(project, antoraTask))); + project.getPlugins() + .withType(JavaBasePlugin.class, + (javaBasePlugin) -> project.getTasks() + .getByName(JavaBasePlugin.CHECK_TASK_NAME) + .dependsOn(antoraTask)); + } + + private List getAntoraNpxArs(Project project, AntoraTask antoraTask) { + logWarningIfNodeModulesInUserHome(project); + StartParameter startParameter = project.getGradle().getStartParameter(); + boolean showStacktrace = startParameter.getShowStacktrace().name().startsWith("ALWAYS"); + boolean debugLogging = project.getGradle().getStartParameter().getLogLevel() == LogLevel.DEBUG; + String playbookPath = antoraTask.getPlaybook(); + List arguments = new ArrayList<>(); + arguments.addAll(List.of("--package", "@antora/cli")); + arguments.add("antora"); + arguments.addAll((!showStacktrace) ? Collections.emptyList() : List.of("--stacktrace")); + arguments.addAll((!debugLogging) ? List.of("--quiet") : List.of("--log-level", "all")); + arguments.addAll(List.of("--ui-bundle-url", antoraTask.getUiBundleUrl())); + arguments.add(playbookPath); + return arguments; + } + + private void logWarningIfNodeModulesInUserHome(Project project) { + if (new File(System.getProperty("user.home"), "node_modules").exists()) { + project.getLogger() + .warn("Detected the existence of $HOME/node_modules. This directory is " + + "not compatible with this plugin. Please remove it."); + } + } + + private String getUiBundleUrl(Project project) { + try { + File packageJson = project.getRootProject().file("antora/package.json"); + ObjectMapper objectMapper = new ObjectMapper(); + Map json = objectMapper.readerFor(Map.class).readValue(packageJson); + Map config = (json != null) ? (Map) json.get("config") : null; + String url = (config != null) ? (String) config.get("ui-bundle-url") : null; + Assert.state(StringUtils.hasText(url.toString()), "package.json has not ui-bundle-url config"); + return url; + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + private void configureNodeExtension(Project project, NodeExtension nodeExtension) { + File buildDir = project.getBuildDir(); + nodeExtension.getWorkDir().set(buildDir.toPath().resolve(".gradle/nodejs").toFile()); + nodeExtension.getNpmWorkDir().set(buildDir.toPath().resolve(".gradle/npm").toFile()); + nodeExtension.getNodeProjectDir().set(getNodeProjectDir(buildDir)); + } + + private File getNodeProjectDir(File buildDir) { + return buildDir.toPath().resolve(".gradle/nodeproject").toFile(); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/AsciidoctorConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/AsciidoctorConventions.java deleted file mode 100644 index 537ff277f739..000000000000 --- a/buildSrc/src/main/java/org/springframework/boot/build/AsciidoctorConventions.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.build; - -import java.io.File; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask; -import org.asciidoctor.gradle.jvm.AsciidoctorJExtension; -import org.asciidoctor.gradle.jvm.AsciidoctorJPlugin; -import org.asciidoctor.gradle.jvm.AsciidoctorTask; -import org.gradle.api.JavaVersion; -import org.gradle.api.Project; -import org.gradle.api.tasks.PathSensitivity; -import org.gradle.api.tasks.Sync; - -import org.springframework.boot.build.artifacts.ArtifactRelease; -import org.springframework.util.StringUtils; - -/** - * Conventions that are applied in the presence of the {@link AsciidoctorJPlugin}. When - * the plugin is applied: - * - *
    - *
  • All warnings are made fatal. - *
  • The version of AsciidoctorJ is upgraded to 2.4.3. - *
  • An {@code asciidoctorExtensions} configuration is created. - *
  • For each {@link AsciidoctorTask} (HTML only): - *
      - *
    • A task is created to sync the documentation resources to its output directory. - *
    • {@code doctype} {@link AsciidoctorTask#options(Map) option} is configured. - *
    • The {@code backend} is configured. - *
    - *
  • For each {@link AbstractAsciidoctorTask} (HTML and PDF): - *
      - *
    • {@link AsciidoctorTask#attributes(Map) Attributes} are configured to enable - * warnings for references to missing attributes, the GitHub tag, the Artifactory repo for - * the current version, etc. - *
    • {@link AbstractAsciidoctorTask#baseDirFollowsSourceDir() baseDirFollowsSourceDir()} - * is enabled. - *
    • {@code asciidoctorExtensions} is added to the task's configurations. - *
    - *
- * - * @author Andy Wilkinson - * @author Scott Frederick - */ -class AsciidoctorConventions { - - private static final String ASCIIDOCTORJ_VERSION = "2.4.3"; - - private static final String EXTENSIONS_CONFIGURATION_NAME = "asciidoctorExtensions"; - - void apply(Project project) { - project.getPlugins().withType(AsciidoctorJPlugin.class, (asciidoctorPlugin) -> { - makeAllWarningsFatal(project); - upgradeAsciidoctorJVersion(project); - createAsciidoctorExtensionsConfiguration(project); - project.getTasks() - .withType(AbstractAsciidoctorTask.class, - (asciidoctorTask) -> configureAsciidoctorTask(project, asciidoctorTask)); - }); - } - - private void makeAllWarningsFatal(Project project) { - project.getExtensions().getByType(AsciidoctorJExtension.class).fatalWarnings(".*"); - } - - private void upgradeAsciidoctorJVersion(Project project) { - project.getExtensions().getByType(AsciidoctorJExtension.class).setVersion(ASCIIDOCTORJ_VERSION); - } - - private void createAsciidoctorExtensionsConfiguration(Project project) { - project.getConfigurations().create(EXTENSIONS_CONFIGURATION_NAME, (configuration) -> { - project.getConfigurations() - .matching((candidate) -> "dependencyManagement".equals(candidate.getName())) - .all(configuration::extendsFrom); - configuration.getDependencies() - .add(project.getDependencies() - .create("io.spring.asciidoctor.backends:spring-asciidoctor-backends:0.0.5")); - configuration.getDependencies() - .add(project.getDependencies().create("org.asciidoctor:asciidoctorj-pdf:1.5.3")); - }); - } - - private void configureAsciidoctorTask(Project project, AbstractAsciidoctorTask asciidoctorTask) { - asciidoctorTask.configurations(EXTENSIONS_CONFIGURATION_NAME); - configureCommonAttributes(project, asciidoctorTask); - configureOptions(asciidoctorTask); - configureForkOptions(asciidoctorTask); - asciidoctorTask.baseDirFollowsSourceDir(); - createSyncDocumentationSourceTask(project, asciidoctorTask); - if (asciidoctorTask instanceof AsciidoctorTask task) { - boolean pdf = task.getName().toLowerCase().contains("pdf"); - String backend = (!pdf) ? "spring-html" : "spring-pdf"; - task.outputOptions((outputOptions) -> outputOptions.backends(backend)); - } - } - - private void configureCommonAttributes(Project project, AbstractAsciidoctorTask asciidoctorTask) { - ArtifactRelease artifacts = ArtifactRelease.forProject(project); - Map attributes = new HashMap<>(); - attributes.put("attribute-missing", "warn"); - attributes.put("github-tag", determineGitHubTag(project)); - attributes.put("artifact-release-type", artifacts.getType()); - attributes.put("artifact-download-repo", artifacts.getDownloadRepo()); - attributes.put("revnumber", null); - asciidoctorTask.attributes(attributes); - } - - // See https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/597 - private void configureForkOptions(AbstractAsciidoctorTask asciidoctorTask) { - if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_16)) { - asciidoctorTask.forkOptions((options) -> options.jvmArgs("--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", - "--add-opens", "java.base/java.io=ALL-UNNAMED")); - } - } - - private String determineGitHubTag(Project project) { - String version = "v" + project.getVersion(); - return (version.endsWith("-SNAPSHOT")) ? "3.1.x" : version; - } - - private void configureOptions(AbstractAsciidoctorTask asciidoctorTask) { - asciidoctorTask.options(Collections.singletonMap("doctype", "book")); - } - - private Sync createSyncDocumentationSourceTask(Project project, AbstractAsciidoctorTask asciidoctorTask) { - Sync syncDocumentationSource = project.getTasks() - .create("syncDocumentationSourceFor" + StringUtils.capitalize(asciidoctorTask.getName()), Sync.class); - File syncedSource = new File(project.getBuildDir(), "docs/src/" + asciidoctorTask.getName()); - syncDocumentationSource.setDestinationDir(syncedSource); - syncDocumentationSource.from("src/docs/"); - asciidoctorTask.dependsOn(syncDocumentationSource); - asciidoctorTask.getInputs() - .dir(syncedSource) - .withPathSensitivity(PathSensitivity.RELATIVE) - .withPropertyName("synced source"); - asciidoctorTask.setSourceDir(project.relativePath(new File(syncedSource, "asciidoc/"))); - return syncDocumentationSource; - } - -} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java index 8a50535a4365..8b4769be00e0 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package org.springframework.boot.build; -import org.asciidoctor.gradle.jvm.AsciidoctorJPlugin; +import org.antora.gradle.AntoraPlugin; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.plugins.JavaBasePlugin; @@ -32,8 +32,8 @@ * When the {@link MavenPublishPlugin} is applied, the conventions in * {@link MavenPublishingConventions} are applied. * - * When the {@link AsciidoctorJPlugin} is applied, the conventions in - * {@link AsciidoctorConventions} are applied. + * When the {@link AntoraPlugin} is applied, the conventions in {@link AntoraConventions} + * are applied. * * @author Andy Wilkinson * @author Christoph Dreis @@ -43,9 +43,10 @@ public class ConventionsPlugin implements Plugin { @Override public void apply(Project project) { + new NoHttpConventions().apply(project); new JavaConventions().apply(project); new MavenPublishingConventions().apply(project); - new AsciidoctorConventions().apply(project); + new AntoraConventions().apply(project); new KotlinConventions().apply(project); new WarConventions().apply(project); new EclipseConventions().apply(project); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/ExtractResources.java b/buildSrc/src/main/java/org/springframework/boot/build/ExtractResources.java index 78473cec471f..35e295b4aaf2 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/ExtractResources.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/ExtractResources.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,15 +21,13 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.Task; import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.MapProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; @@ -42,54 +40,30 @@ * * @author Andy Wilkinson */ -public class ExtractResources extends DefaultTask { +public abstract class ExtractResources extends DefaultTask { private final PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("${", "}"); - private final Map properties = new HashMap<>(); - - private final DirectoryProperty destinationDirectory; - - private List resourceNames = new ArrayList<>(); - - public ExtractResources() { - this.destinationDirectory = getProject().getObjects().directoryProperty(); - } - @Input - public List getResourceNames() { - return this.resourceNames; - } - - public void setResourcesNames(List resourceNames) { - this.resourceNames = resourceNames; - } + public abstract ListProperty getResourceNames(); @OutputDirectory - public DirectoryProperty getDestinationDirectory() { - return this.destinationDirectory; - } - - public void property(String name, String value) { - this.properties.put(name, value); - } + public abstract DirectoryProperty getDestinationDirectory(); @Input - public Map getProperties() { - return this.properties; - } + public abstract MapProperty getProperties(); @TaskAction void extractResources() throws IOException { - for (String resourceName : this.resourceNames) { + for (String resourceName : getResourceNames().get()) { InputStream resourceStream = getClass().getClassLoader().getResourceAsStream(resourceName); if (resourceStream == null) { throw new GradleException("Resource '" + resourceName + "' does not exist"); } String resource = FileCopyUtils.copyToString(new InputStreamReader(resourceStream, StandardCharsets.UTF_8)); - resource = this.propertyPlaceholderHelper.replacePlaceholders(resource, this.properties::get); + resource = this.propertyPlaceholderHelper.replacePlaceholders(resource, getProperties().get()::get); FileCopyUtils.copy(resource, - new FileWriter(this.destinationDirectory.file(resourceName).get().getAsFile())); + new FileWriter(getDestinationDirectory().file(resourceName).get().getAsFile())); } } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java index fc59efaec9b2..e5ce6df4244f 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java @@ -129,8 +129,8 @@ private void configureJarManifestConventions(Project project) { ExtractResources extractLegalResources = project.getTasks() .create("extractLegalResources", ExtractResources.class); extractLegalResources.getDestinationDirectory().set(project.getLayout().getBuildDirectory().dir("legal")); - extractLegalResources.setResourcesNames(Arrays.asList("LICENSE.txt", "NOTICE.txt")); - extractLegalResources.property("version", project.getVersion().toString()); + extractLegalResources.getResourceNames().set(Arrays.asList("LICENSE.txt", "NOTICE.txt")); + extractLegalResources.getProperties().put("version", project.getVersion().toString()); SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class); Set sourceJarTaskNames = sourceSets.stream() .map(SourceSet::getSourcesJarTaskName) @@ -261,8 +261,9 @@ private void configureDependencyManagement(Project project) { configuration.setCanBeResolved(false); }); configurations - .matching((configuration) -> configuration.getName().endsWith("Classpath") + .matching((configuration) -> (configuration.getName().endsWith("Classpath") || JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME.equals(configuration.getName())) + && (!configuration.getName().contains("dokkatoo"))) .all((configuration) -> configuration.extendsFrom(dependencyManagement)); Dependency springBootParent = project.getDependencies() .enforcedPlatform(project.getDependencies() diff --git a/buildSrc/src/main/java/org/springframework/boot/build/KotlinConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/KotlinConventions.java index c5e896160b47..9975dcfca550 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/KotlinConventions.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/KotlinConventions.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,15 @@ package org.springframework.boot.build; +import java.net.URI; import java.util.ArrayList; import java.util.List; +import dev.adamko.dokkatoo.DokkatooExtension; +import dev.adamko.dokkatoo.formats.DokkatooHtmlPlugin; import org.gradle.api.Project; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions; import org.jetbrains.kotlin.gradle.tasks.KotlinCompile; @@ -44,9 +49,10 @@ class KotlinConventions { void apply(Project project) { - project.getPlugins() - .withId("org.jetbrains.kotlin.jvm", - (plugin) -> project.getTasks().withType(KotlinCompile.class, this::configure)); + project.getPlugins().withId("org.jetbrains.kotlin.jvm", (plugin) -> { + project.getTasks().withType(KotlinCompile.class, this::configure); + configureDokkatoo(project); + }); } private void configure(KotlinCompile compile) { @@ -60,4 +66,27 @@ private void configure(KotlinCompile compile) { compile.getKotlinOptions().setFreeCompilerArgs(freeCompilerArgs); } + private void configureDokkatoo(Project project) { + project.getPlugins().apply(DokkatooHtmlPlugin.class); + DokkatooExtension dokkatoo = project.getExtensions().getByType(DokkatooExtension.class); + dokkatoo.getDokkatooSourceSets().named(SourceSet.MAIN_SOURCE_SET_NAME).configure((sourceSet) -> { + sourceSet.getSourceRoots().setFrom(project.file("src/main/kotlin")); + sourceSet.getClasspath() + .from(project.getExtensions() + .getByType(SourceSetContainer.class) + .getByName(SourceSet.MAIN_SOURCE_SET_NAME) + .getOutput()); + sourceSet.getExternalDocumentationLinks().create("spring-boot-javadoc", (link) -> { + link.getUrl().set(URI.create("https://docs.spring.io/spring-boot/api/java/")); + link.getPackageListUrl().set(URI.create("https://docs.spring.io/spring-boot/api/java/element-list")); + }); + sourceSet.getExternalDocumentationLinks().create("spring-framework-javadoc", (link) -> { + String url = "https://docs.spring.io/spring-framework/docs/%s/javadoc-api/" + .formatted(project.property("springFrameworkVersion")); + link.getUrl().set(URI.create(url)); + link.getPackageListUrl().set(URI.create(url + "/element-list")); + }); + }); + } + } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/MavenPublishingConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/MavenPublishingConventions.java index fc1f5cb357dc..53ddea4f2edf 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/MavenPublishingConventions.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/MavenPublishingConventions.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,6 @@ import org.apache.maven.artifact.repository.MavenArtifactRepository; import org.gradle.api.Project; import org.gradle.api.attributes.Usage; -import org.gradle.api.component.AdhocComponentWithVariants; -import org.gradle.api.component.ConfigurationVariantDetails; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.publish.PublishingExtension; @@ -83,7 +81,6 @@ private void customizeMavenPublication(MavenPublication publication, Project pro project.getPlugins() .withType(JavaPlugin.class) .all((javaPlugin) -> customizeJavaMavenPublication(publication, project)); - suppressMavenOptionalFeatureWarnings(publication); } private void customizePom(MavenPom pom, Project project) { @@ -102,34 +99,12 @@ private void customizePom(MavenPom pom, Project project) { } private void customizeJavaMavenPublication(MavenPublication publication, Project project) { - addMavenOptionalFeature(project); publication.versionMapping((strategy) -> strategy.usage(Usage.JAVA_API, (mappingStrategy) -> mappingStrategy .fromResolutionOf(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME))); publication.versionMapping( (strategy) -> strategy.usage(Usage.JAVA_RUNTIME, VariantVersionMappingStrategy::fromResolutionResult)); } - /** - * Add a feature that allows maven plugins to declare optional dependencies that - * appear in the POM. This is required to make m2e in Eclipse happy. - * @param project the project to add the feature to - */ - private void addMavenOptionalFeature(Project project) { - JavaPluginExtension extension = project.getExtensions().getByType(JavaPluginExtension.class); - extension.registerFeature("mavenOptional", - (feature) -> feature.usingSourceSet(extension.getSourceSets().getByName("main"))); - AdhocComponentWithVariants javaComponent = (AdhocComponentWithVariants) project.getComponents() - .findByName("java"); - javaComponent.addVariantsFromConfiguration( - project.getConfigurations().findByName("mavenOptionalRuntimeElements"), - ConfigurationVariantDetails::mapToOptional); - } - - private void suppressMavenOptionalFeatureWarnings(MavenPublication publication) { - publication.suppressPomMetadataWarningsFor("mavenOptionalApiElements"); - publication.suppressPomMetadataWarningsFor("mavenOptionalRuntimeElements"); - } - private void customizeOrganization(MavenPomOrganization organization) { organization.getName().set("VMware, Inc."); organization.getUrl().set("https://spring.io"); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/NoHttpConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/NoHttpConventions.java new file mode 100644 index 000000000000..7b4847ff2aec --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/NoHttpConventions.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build; + +import io.spring.nohttp.gradle.NoHttpCheckstylePlugin; +import io.spring.nohttp.gradle.NoHttpExtension; +import org.gradle.api.Project; +import org.gradle.api.file.ConfigurableFileTree; +import org.gradle.api.plugins.quality.Checkstyle; + +/** + * Conventions that are applied to enforce that no HTTP urls are used. + * + * @author Phillip Webb + */ +public class NoHttpConventions { + + void apply(Project project) { + project.getPluginManager().apply(NoHttpCheckstylePlugin.class); + configureNoHttpExtension(project, project.getExtensions().getByType(NoHttpExtension.class)); + project.getTasks() + .named(NoHttpCheckstylePlugin.CHECKSTYLE_NOHTTP_TASK_NAME, Checkstyle.class) + .configure((task) -> task.getConfigDirectory().set(project.getRootProject().file("src/nohttp"))); + } + + private void configureNoHttpExtension(Project project, NoHttpExtension extension) { + extension.setAllowlistFile(project.getRootProject().file("src/nohttp/allowlist.lines")); + ConfigurableFileTree source = extension.getSource(); + source.exclude("bin/**"); + source.exclude("build/**"); + source.exclude("out/**"); + source.exclude("target/**"); + source.exclude(".settings/**"); + source.exclude(".classpath"); + source.exclude(".project"); + source.exclude(".gradle"); + source.exclude("**/docker/export.tar"); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/SyncAppSource.java b/buildSrc/src/main/java/org/springframework/boot/build/SyncAppSource.java index 5a863d221a74..ae318adf0f84 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/SyncAppSource.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/SyncAppSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2023 the original author or authors. + * Copyright 2021-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import org.gradle.api.DefaultTask; import org.gradle.api.file.DirectoryProperty; -import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputDirectory; @@ -31,45 +30,29 @@ * * @author Andy Wilkinson */ -public class SyncAppSource extends DefaultTask { +public abstract class SyncAppSource extends DefaultTask { - private final DirectoryProperty sourceDirectory; + public SyncAppSource() { + getPluginVersion().convention(getProject().provider(() -> getProject().getVersion().toString())); + } - private final DirectoryProperty destinationDirectory; + @InputDirectory + public abstract DirectoryProperty getSourceDirectory(); - private final Property pluginVersion; + @OutputDirectory + public abstract DirectoryProperty getDestinationDirectory(); - public SyncAppSource() { - ObjectFactory objects = getProject().getObjects(); - this.sourceDirectory = objects.directoryProperty(); - this.destinationDirectory = objects.directoryProperty(); - this.pluginVersion = objects.property(String.class) - .convention(getProject().provider(() -> getProject().getVersion().toString())); - } + @Input + public abstract Property getPluginVersion(); @TaskAction void syncAppSources() { getProject().sync((copySpec) -> { - copySpec.from(this.sourceDirectory); - copySpec.into(this.destinationDirectory); + copySpec.from(getSourceDirectory()); + copySpec.into(getDestinationDirectory()); copySpec.filter((line) -> line.replace("id \"org.springframework.boot\"", "id \"org.springframework.boot\" version \"" + getProject().getVersion() + "\"")); }); } - @InputDirectory - public DirectoryProperty getSourceDirectory() { - return this.sourceDirectory; - } - - @OutputDirectory - public DirectoryProperty getDestinationDirectory() { - return this.destinationDirectory; - } - - @Input - public Property getPluginVersion() { - return this.pluginVersion; - } - } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java b/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java new file mode 100644 index 000000000000..3d5bec724ae7 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java @@ -0,0 +1,162 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build.antora; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.gradle.api.Project; + +import org.springframework.boot.build.artifacts.ArtifactRelease; +import org.springframework.boot.build.bom.BomExtension; +import org.springframework.boot.build.bom.Library; +import org.springframework.util.Assert; + +/** + * Generates Asciidoctor attributes for use with Antora. + * + * @author Phillip Webb + */ +public class AntoraAsciidocAttributes { + + private static final String DASH_SNAPSHOT = "-SNAPSHOT"; + + private final String version; + + private final boolean latestVersion; + + private final ArtifactRelease artifactRelease; + + private final List libraries; + + private final Map dependencyVersions; + + private final Map projectProperties; + + public AntoraAsciidocAttributes(Project project, BomExtension dependencyBom, + Map dependencyVersions) { + this.version = String.valueOf(project.getVersion()); + this.latestVersion = Boolean.parseBoolean(String.valueOf(project.findProperty("latestVersion"))); + this.artifactRelease = ArtifactRelease.forProject(project); + this.libraries = dependencyBom.getLibraries(); + this.dependencyVersions = dependencyVersions; + this.projectProperties = project.getProperties(); + } + + AntoraAsciidocAttributes(String version, boolean latestVersion, List libraries, + Map dependencyVersions, Map projectProperties) { + this.version = version; + this.latestVersion = latestVersion; + this.artifactRelease = ArtifactRelease.forVersion(version); + this.libraries = (libraries != null) ? libraries : Collections.emptyList(); + this.dependencyVersions = (dependencyVersions != null) ? dependencyVersions : Collections.emptyMap(); + this.projectProperties = (projectProperties != null) ? projectProperties : Collections.emptyMap(); + } + + public Map get() { + Map attributes = new LinkedHashMap<>(); + addGitHubAttributes(attributes); + addVersionAttributes(attributes); + addUrlArtifactRepository(attributes); + addUrlLibraryLinkAttributes(attributes); + addPropertyAttributes(attributes); + return attributes; + } + + private void addGitHubAttributes(Map attributes) { + attributes.put("github-repo", "spring-projects/spring-boot"); + attributes.put("github-ref", determineGitHubRef()); + } + + private String determineGitHubRef() { + int snapshotIndex = this.version.lastIndexOf(DASH_SNAPSHOT); + if (snapshotIndex == -1) { + return "v" + this.version; + } + if (this.latestVersion) { + return "main"; + } + String versionRoot = this.version.substring(0, snapshotIndex); + int lastDot = versionRoot.lastIndexOf('.'); + return versionRoot.substring(0, lastDot) + ".x"; + } + + private void addVersionAttributes(Map attributes) { + this.libraries.forEach((library) -> { + String name = "version-" + library.getLinkRootName(); + String value = library.getVersion().toString(); + attributes.put(name, value); + }); + attributes.put("version-native-build-tools", (String) this.projectProperties.get("nativeBuildToolsVersion")); + attributes.put("version-graal", (String) this.projectProperties.get("graalVersion")); + addSpringDataDependencyVersion(attributes, "spring-data-commons"); + addSpringDataDependencyVersion(attributes, "spring-data-couchbase"); + addSpringDataDependencyVersion(attributes, "spring-data-elasticsearch"); + addSpringDataDependencyVersion(attributes, "spring-data-jdbc"); + addSpringDataDependencyVersion(attributes, "spring-data-jpa"); + addSpringDataDependencyVersion(attributes, "spring-data-mongodb"); + addSpringDataDependencyVersion(attributes, "spring-data-neo4j"); + addSpringDataDependencyVersion(attributes, "spring-data-r2dbc"); + addSpringDataDependencyVersion(attributes, "spring-data-rest", "spring-data-rest-core"); + } + + private void addSpringDataDependencyVersion(Map attributes, String artifactId) { + addSpringDataDependencyVersion(attributes, artifactId, artifactId); + } + + private void addSpringDataDependencyVersion(Map attributes, String name, String artifactId) { + String version = this.dependencyVersions.get("org.springframework.data:" + artifactId); + Assert.notNull(version, () -> "No version found for Spring Data artifact " + artifactId); + attributes.put("version-" + name, version); + } + + private void addUrlArtifactRepository(Map attributes) { + attributes.put("url-artifact-repository", this.artifactRelease.getDownloadRepo()); + } + + private void addUrlLibraryLinkAttributes(Map attributes) { + this.libraries.forEach((library) -> { + String prefix = "url-" + library.getLinkRootName() + "-"; + library.getLinks().forEach((name, link) -> attributes.put(prefix + name, link)); + }); + } + + private void addPropertyAttributes(Map attributes) { + Properties properties = new Properties() { + + @Override + public synchronized Object put(Object key, Object value) { + // Put directly because order is important for us + return attributes.put(key.toString(), value.toString()); + } + + }; + try (InputStream in = getClass().getResourceAsStream("antora-asciidoc-attributes.properties")) { + properties.load(in); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/antora/Extensions.java b/buildSrc/src/main/java/org/springframework/boot/build/antora/Extensions.java new file mode 100644 index 000000000000..8234edfea98a --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/antora/Extensions.java @@ -0,0 +1,190 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build.antora; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Consumer; +import java.util.stream.Stream; + +/** + * Antora and Asciidoc extensions used by Spring Boot. + * + * @author Phillip Webb + */ +public final class Extensions { + + private static final String ROOT_COMPONENT_EXTENSION = "@springio/antora-extensions/root-component-extension"; + + private static final List antora; + static { + List extensions = new ArrayList<>(); + extensions.add(new Extension("@springio/antora-extensions", ROOT_COMPONENT_EXTENSION, + "@springio/antora-extensions/static-page-extension", + "@springio/antora-extensions/override-navigation-builder-extension")); + extensions.add(new Extension("@springio/antora-xref-extension")); + extensions.add(new Extension("@springio/antora-zip-contents-collector-extension")); + antora = List.copyOf(extensions); + } + + private static final List asciidoc; + static { + List extensions = new ArrayList<>(); + extensions.add(new Extension("@asciidoctor/tabs")); + extensions.add(new Extension("@springio/asciidoctor-extensions", "@springio/asciidoctor-extensions", + "@springio/asciidoctor-extensions/configuration-properties-extension", + "@springio/asciidoctor-extensions/section-ids-extension")); + asciidoc = List.copyOf(extensions); + } + + private static final Map localOverrides = Collections.emptyMap(); + + private Extensions() { + } + + static List> antora(Consumer extensions) { + AntoraExtensionsConfiguration result = new AntoraExtensionsConfiguration( + antora.stream().flatMap(Extension::names).sorted().toList()); + extensions.accept(result); + return result.config(); + } + + static List asciidoc() { + return asciidoc.stream().flatMap(Extension::names).sorted().toList(); + } + + private record Extension(String name, String... includeNames) { + + Stream names() { + return (this.includeNames.length != 0) ? Arrays.stream(this.includeNames) : Stream.of(this.name); + } + + } + + static final class AntoraExtensionsConfiguration { + + private Map> extensions = new TreeMap<>(); + + private AntoraExtensionsConfiguration(List names) { + names.forEach((name) -> this.extensions.put(name, null)); + } + + void xref(Consumer xref) { + xref.accept(new Xref()); + } + + void zipContentsCollector(Consumer zipContentsCollector) { + zipContentsCollector.accept(new ZipContentsCollector()); + } + + void rootComponent(Consumer rootComponent) { + rootComponent.accept(new RootComponent()); + } + + List> config() { + List> config = new ArrayList<>(); + Map> orderedExtensions = new LinkedHashMap<>(this.extensions); + // The root component extension must be last + Map rootComponentConfig = orderedExtensions.remove(ROOT_COMPONENT_EXTENSION); + orderedExtensions.put(ROOT_COMPONENT_EXTENSION, rootComponentConfig); + orderedExtensions.forEach((name, customizations) -> { + Map extensionConfig = new LinkedHashMap<>(); + extensionConfig.put("require", localOverrides.getOrDefault(name, name)); + if (customizations != null) { + extensionConfig.putAll(customizations); + } + config.add(extensionConfig); + }); + return List.copyOf(config); + } + + abstract class Customizer { + + private final String name; + + Customizer(String name) { + this.name = name; + } + + protected void customize(String key, Object value) { + AntoraExtensionsConfiguration.this.extensions.computeIfAbsent(this.name, (name) -> new TreeMap<>()) + .put(key, value); + } + + } + + class Xref extends Customizer { + + Xref() { + super("@springio/antora-xref-extension"); + } + + void stub(List stub) { + if (stub != null && !stub.isEmpty()) { + customize("stub", stub); + } + } + + } + + class ZipContentsCollector extends Customizer { + + ZipContentsCollector() { + super("@springio/antora-zip-contents-collector-extension"); + } + + void versionFile(String versionFile) { + customize("version_file", versionFile); + } + + void locations(Path... locations) { + locations(Arrays.stream(locations).map(Path::toString).toList()); + } + + private void locations(List locations) { + customize("locations", locations); + } + + void alwaysInclude(Map alwaysInclude) { + if (alwaysInclude != null && !alwaysInclude.isEmpty()) { + customize("always_include", List.of(new TreeMap<>(alwaysInclude))); + } + } + + } + + class RootComponent extends Customizer { + + RootComponent() { + super(ROOT_COMPONENT_EXTENSION); + } + + void name(String name) { + customize("root_component_name", name); + } + + } + + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/antora/GenerateAntoraPlaybook.java b/buildSrc/src/main/java/org/springframework/boot/build/antora/GenerateAntoraPlaybook.java new file mode 100644 index 000000000000..f5dd4d80936e --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/antora/GenerateAntoraPlaybook.java @@ -0,0 +1,209 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build.antora; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.gradle.api.DefaultTask; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ProjectDependency; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; + +/** + * Task to generate a local Antora playbook. + * + * @author Phillip Webb + */ +public abstract class GenerateAntoraPlaybook extends DefaultTask { + + private static final String ANTORA_SOURCE_DIR = "src/docs/antora"; + + private static final String GENERATED_DOCS = "build/generated/docs/"; + + @OutputFile + public abstract RegularFileProperty getOutputFile(); + + @Input + public abstract Property getContentSourceConfiguration(); + + @Input + @Optional + public abstract ListProperty getXrefStubs(); + + @Input + @Optional + public abstract MapProperty getAlwaysInclude(); + + public GenerateAntoraPlaybook() { + setGroup("Documentation"); + setDescription("Generates an Antora playbook.yml file for local use"); + getOutputFile().convention(getProject().getLayout() + .getBuildDirectory() + .file("generated/docs/antora-playbook/antora-playbook.yml")); + getContentSourceConfiguration().convention("antoraContent"); + } + + @TaskAction + public void writePlaybookYml() throws IOException { + File file = getOutputFile().get().getAsFile(); + file.getParentFile().mkdirs(); + try (FileWriter out = new FileWriter(file)) { + createYaml().dump(getData(), out); + } + } + + @Input + final Map getData() throws IOException { + Map data = loadPlaybookTemplate(); + addExtensions(data); + addSources(data); + addDir(data); + return data; + } + + @SuppressWarnings("unchecked") + private Map loadPlaybookTemplate() throws IOException { + try (InputStream resource = getClass().getResourceAsStream("antora-playbook-template.yml")) { + return createYaml().loadAs(resource, LinkedHashMap.class); + } + } + + @SuppressWarnings("unchecked") + private void addExtensions(Map data) { + Map antora = (Map) data.get("antora"); + antora.put("extensions", Extensions.antora((extensions) -> { + extensions.xref((xref) -> xref.stub(getXrefStubs().getOrElse(Collections.emptyList()))); + extensions.zipContentsCollector((zipContentsCollector) -> { + zipContentsCollector.versionFile("gradle.properties"); + String locationName = getProject().getName() + "-${version}-${name}-${classifier}.zip"; + Path antoraContent = getRelativeProjectPath() + .resolve(GENERATED_DOCS + "antora-content/" + locationName); + Path antoraDependencies = getRelativeProjectPath() + .resolve(GENERATED_DOCS + "antora-dependencies-content/" + locationName); + zipContentsCollector.locations(antoraContent, antoraDependencies); + zipContentsCollector.alwaysInclude(getAlwaysInclude().getOrNull()); + }); + extensions.rootComponent((rootComponent) -> rootComponent.name("boot")); + })); + Map asciidoc = (Map) data.get("asciidoc"); + asciidoc.put("extensions", Extensions.asciidoc()); + } + + private void addSources(Map data) { + List> contentSources = getList(data, "content.sources"); + contentSources.add(createContentSource()); + } + + private Map createContentSource() { + Map source = new LinkedHashMap<>(); + Path playbookPath = getOutputFile().get().getAsFile().toPath().getParent(); + Path antoraSrc = getProjectPath(getProject()).resolve(ANTORA_SOURCE_DIR); + StringBuilder url = new StringBuilder("."); + relativizeFromRootProject(playbookPath).normalize().forEach((path) -> url.append(File.separator).append("..")); + source.put("url", url.toString()); + source.put("branches", "HEAD"); + source.put("version", getProject().getVersion().toString()); + Set startPaths = new LinkedHashSet<>(); + addAntoraContentStartPaths(startPaths); + startPaths.add(relativizeFromRootProject(antoraSrc).toString()); + source.put("start_paths", startPaths.stream().toList()); + return source; + } + + private void addAntoraContentStartPaths(Set startPaths) { + Configuration configuration = getProject().getConfigurations().findByName("antoraContent"); + if (configuration != null) { + for (ProjectDependency dependency : configuration.getAllDependencies().withType(ProjectDependency.class)) { + Path path = dependency.getDependencyProject().getProjectDir().toPath(); + startPaths.add(relativizeFromRootProject(path).resolve(ANTORA_SOURCE_DIR).toString()); + } + } + } + + private void addDir(Map data) { + Path playbookDir = toRealPath(getOutputFile().get().getAsFile().toPath()).getParent(); + Path outputDir = toRealPath(getProject().getBuildDir().toPath().resolve("site")); + data.put("output", Map.of("dir", "." + File.separator + playbookDir.relativize(outputDir).toString())); + } + + @SuppressWarnings("unchecked") + private List getList(Map data, String location) { + return (List) get(data, location); + } + + @SuppressWarnings("unchecked") + private Object get(Map data, String location) { + Object result = data; + String[] keys = location.split("\\."); + for (String key : keys) { + result = ((Map) result).get(key); + } + return result; + } + + private Yaml createYaml() { + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + options.setPrettyFlow(true); + return new Yaml(options); + } + + private Path getRelativeProjectPath() { + return relativizeFromRootProject(getProjectPath(getProject())); + } + + private Path relativizeFromRootProject(Path subPath) { + Path rootProjectPath = getProjectPath(getProject().getRootProject()); + return rootProjectPath.relativize(subPath).normalize(); + } + + private Path getProjectPath(Project project) { + return toRealPath(project.getProjectDir().toPath()); + } + + private Path toRealPath(Path path) { + try { + return Files.exists(path) ? path.toRealPath() : path; + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java index d671304708db..c36d46a67502 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 the original author or authors. + * Copyright 2022-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,12 +26,16 @@ import java.util.stream.Collectors; import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.JavaCall; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClass.Predicates; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.domain.JavaParameter; import com.tngtech.archunit.core.domain.properties.CanBeAnnotated; +import com.tngtech.archunit.core.domain.properties.HasName; +import com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With; +import com.tngtech.archunit.core.domain.properties.HasParameterTypes; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ArchRule; @@ -58,11 +62,14 @@ import org.gradle.api.tasks.SkipWhenEmpty; import org.gradle.api.tasks.TaskAction; +import org.springframework.util.ResourceUtils; + /** * {@link Task} that checks for architecture problems. * * @author Andy Wilkinson * @author Yanming Zhou + * @author Scott Frederick */ public abstract class ArchitectureCheck extends DefaultTask { @@ -75,7 +82,8 @@ public ArchitectureCheck() { allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveNoParameters(), noClassesShouldCallStepVerifierStepVerifyComplete(), noClassesShouldConfigureDefaultStepVerifierTimeout(), noClassesShouldCallCollectorsToList(), - noClassesShouldCallURLEncoderWithStringEncoding(), noClassesShouldCallURLDecoderWithStringEncoding()); + noClassesShouldCallURLEncoderWithStringEncoding(), noClassesShouldCallURLDecoderWithStringEncoding(), + noClassesShouldLoadResourcesUsingResourceUtils()); getRuleDescriptions().set(getRules().map((rules) -> rules.stream().map(ArchRule::getDescription).toList())); } @@ -208,6 +216,18 @@ private ArchRule noClassesShouldCallURLDecoderWithStringEncoding() { .because("java.net.URLDecoder.decode(String s, Charset charset) should be used instead"); } + private ArchRule noClassesShouldLoadResourcesUsingResourceUtils() { + return ArchRuleDefinition.noClasses() + .should() + .callMethodWhere(JavaCall.Predicates.target(With.owner(Predicates.type(ResourceUtils.class))) + .and(JavaCall.Predicates.target(HasName.Predicates.name("getURL"))) + .and(JavaCall.Predicates.target(HasParameterTypes.Predicates.rawParameterTypes(String.class))) + .or(JavaCall.Predicates.target(With.owner(Predicates.type(ResourceUtils.class))) + .and(JavaCall.Predicates.target(HasName.Predicates.name("getFile"))) + .and(JavaCall.Predicates.target(HasParameterTypes.Predicates.rawParameterTypes(String.class))))) + .because("org.springframework.boot.io.ApplicationResourceLoader should be used instead"); + } + public void setClasses(FileCollection classes) { this.classes = classes; } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitecturePlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitecturePlugin.java index afeaf6311700..4c73e021df27 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitecturePlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitecturePlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,6 +51,7 @@ private void registerTasks(Project project) { (task) -> { task.setClasses(sourceSet.getOutput().getClassesDirs()); task.getResourcesDirectory().set(sourceSet.getOutput().getResourcesDir()); + task.dependsOn(sourceSet.getProcessResourcesTaskName()); task.setDescription("Checks the architecture of the classes of the " + sourceSet.getName() + " source set."); task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/artifacts/ArtifactRelease.java b/buildSrc/src/main/java/org/springframework/boot/build/artifacts/ArtifactRelease.java index 3cf861543b0b..a0def21d15e5 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/artifacts/ArtifactRelease.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/artifacts/ArtifactRelease.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,24 +26,18 @@ */ public final class ArtifactRelease { - private static final String SNAPSHOT = "snapshot"; - - private static final String MILESTONE = "milestone"; - - private static final String RELEASE = "release"; - private static final String SPRING_REPO = "https://repo.spring.io/%s"; private static final String MAVEN_REPO = "https://repo.maven.apache.org/maven2"; - private final String type; + private final Type type; - private ArtifactRelease(String type) { + private ArtifactRelease(Type type) { this.type = type; } public String getType() { - return this.type; + return this.type.toString().toLowerCase(); } public String getDownloadRepo() { @@ -51,24 +45,34 @@ public String getDownloadRepo() { } public boolean isRelease() { - return RELEASE.equals(this.type); + return this.type == Type.RELEASE; } public static ArtifactRelease forProject(Project project) { - return new ArtifactRelease(determineReleaseType(project)); + return forVersion(project.getVersion().toString()); } - private static String determineReleaseType(Project project) { - String version = project.getVersion().toString(); - int modifierIndex = version.lastIndexOf('-'); - if (modifierIndex == -1) { - return RELEASE; - } - String type = version.substring(modifierIndex + 1); - if (type.startsWith("M") || type.startsWith("RC")) { - return MILESTONE; + public static ArtifactRelease forVersion(String version) { + return new ArtifactRelease(Type.forVersion(version)); + } + + enum Type { + + SNAPSHOT, MILESTONE, RELEASE; + + static Type forVersion(String version) { + int modifierIndex = version.lastIndexOf('-'); + if (modifierIndex == -1) { + return RELEASE; + } + String type = version.substring(modifierIndex + 1); + if (type.startsWith("M") || type.startsWith("RC")) { + return MILESTONE; + } + return SNAPSHOT; + } - return SNAPSHOT; + } } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationMetadata.java b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationMetadata.java index 751ca0b87521..bf24d219dd25 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationMetadata.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationMetadata.java @@ -28,11 +28,15 @@ import java.util.List; import java.util.Properties; import java.util.Set; -import java.util.concurrent.Callable; import org.gradle.api.DefaultTask; import org.gradle.api.Task; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.PathSensitive; import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskAction; @@ -48,47 +52,45 @@ * @author Andy Wilkinson * @author Scott Frederick */ -public class AutoConfigurationMetadata extends DefaultTask { +public abstract class AutoConfigurationMetadata extends DefaultTask { private static final String COMMENT_START = "#"; private final String moduleName; - private SourceSet sourceSet; - - private File outputFile; + private FileCollection classesDirectories; public AutoConfigurationMetadata() { - getInputs() - .file((Callable) () -> new File(this.sourceSet.getOutput().getResourcesDir(), - "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports")) - .withPathSensitivity(PathSensitivity.RELATIVE) - .withPropertyName("org.springframework.boot.autoconfigure.AutoConfiguration"); - - dependsOn((Callable) () -> this.sourceSet.getProcessResourcesTaskName()); getProject().getConfigurations() .maybeCreate(AutoConfigurationPlugin.AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME); this.moduleName = getProject().getName(); } public void setSourceSet(SourceSet sourceSet) { - this.sourceSet = sourceSet; + getAutoConfigurationImports().set(new File(sourceSet.getOutput().getResourcesDir(), + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports")); + this.classesDirectories = sourceSet.getOutput().getClassesDirs(); + dependsOn(sourceSet.getOutput()); } + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + abstract RegularFileProperty getAutoConfigurationImports(); + @OutputFile - public File getOutputFile() { - return this.outputFile; - } + public abstract RegularFileProperty getOutputFile(); - public void setOutputFile(File outputFile) { - this.outputFile = outputFile; + @Classpath + FileCollection getClassesDirectories() { + return this.classesDirectories; } @TaskAction void documentAutoConfiguration() throws IOException { Properties autoConfiguration = readAutoConfiguration(); - getOutputFile().getParentFile().mkdirs(); - try (FileWriter writer = new FileWriter(getOutputFile())) { + File outputFile = getOutputFile().get().getAsFile(); + outputFile.getParentFile().mkdirs(); + try (FileWriter writer = new FileWriter(outputFile)) { autoConfiguration.store(writer, null); } } @@ -120,8 +122,7 @@ private Properties readAutoConfiguration() throws IOException { * @return auto-configurations */ private List readAutoConfigurationsFile() throws IOException { - File file = new File(this.sourceSet.getOutput().getResourcesDir(), - "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"); + File file = getAutoConfigurationImports().getAsFile().get(); if (!file.exists()) { return Collections.emptyList(); } @@ -140,7 +141,7 @@ private String stripComment(String line) { private File findClassFile(String className) { String classFileName = className.replace(".", "/") + ".class"; - for (File classesDir : this.sourceSet.getOutput().getClassesDirs()) { + for (File classesDir : this.classesDirectories) { File classFile = new File(classesDir, classFileName); if (classFile.isFile()) { return classFile; diff --git a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationPlugin.java index eaaadf3b9474..97f36eb2c46c 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ import java.nio.file.Path; import java.util.Collections; import java.util.List; -import java.util.concurrent.Callable; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.lang.ArchCondition; @@ -94,10 +93,9 @@ public void apply(Project project) { .getByName(SourceSet.MAIN_SOURCE_SET_NAME); task.setSourceSet(main); task.dependsOn(main.getClassesTaskName()); - task.setOutputFile(new File(project.getBuildDir(), "auto-configuration-metadata.properties")); + task.getOutputFile().set(new File(project.getBuildDir(), "auto-configuration-metadata.properties")); project.getArtifacts() - .add(AutoConfigurationPlugin.AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME, - project.provider((Callable) task::getOutputFile), + .add(AutoConfigurationPlugin.AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME, task.getOutputFile(), (artifact) -> artifact.builtBy(task)); }); project.getPlugins().withType(ArchitecturePlugin.class, (architecturePlugin) -> { diff --git a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/DocumentAutoConfigurationClasses.java b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/DocumentAutoConfigurationClasses.java index ce8fbc414619..3eb1a4b36c1b 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/DocumentAutoConfigurationClasses.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/DocumentAutoConfigurationClasses.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ import org.gradle.api.DefaultTask; import org.gradle.api.Task; import org.gradle.api.file.FileCollection; +import org.gradle.api.file.RegularFileProperty; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; @@ -42,12 +43,10 @@ * * @author Andy Wilkinson */ -public class DocumentAutoConfigurationClasses extends DefaultTask { +public abstract class DocumentAutoConfigurationClasses extends DefaultTask { private FileCollection autoConfiguration; - private File outputDir; - @InputFiles public FileCollection getAutoConfiguration() { return this.autoConfiguration; @@ -58,13 +57,7 @@ public void setAutoConfiguration(FileCollection autoConfiguration) { } @OutputDirectory - public File getOutputDir() { - return this.outputDir; - } - - public void setOutputDir(File outputDir) { - this.outputDir = outputDir; - } + public abstract RegularFileProperty getOutputDir(); @TaskAction void documentAutoConfigurationClasses() throws IOException { @@ -80,18 +73,19 @@ void documentAutoConfigurationClasses() throws IOException { } private void writeTable(AutoConfiguration autoConfigurationClasses) throws IOException { - this.outputDir.mkdirs(); + File outputDir = getOutputDir().getAsFile().get(); + outputDir.mkdirs(); try (PrintWriter writer = new PrintWriter( - new FileWriter(new File(this.outputDir, autoConfigurationClasses.module + ".adoc")))) { + new FileWriter(new File(outputDir, autoConfigurationClasses.module + ".adoc")))) { writer.println("[cols=\"4,1\"]"); writer.println("|==="); writer.println("| Configuration Class | Links"); for (AutoConfigurationClass autoConfigurationClass : autoConfigurationClasses.classes) { writer.println(); - writer.printf("| {spring-boot-code}/spring-boot-project/%s/src/main/java/%s.java[`%s`]%n", + writer.printf("| {code-spring-boot}/spring-boot-project/%s/src/main/java/%s.java[`%s`]%n", autoConfigurationClasses.module, autoConfigurationClass.path, autoConfigurationClass.name); - writer.printf("| {spring-boot-api}/%s.html[javadoc]%n", autoConfigurationClass.path); + writer.printf("| xref:api:java/%s.html[javadoc]%n", autoConfigurationClass.path); } writer.println("|==="); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java index 57d0935c52db..59bc52845431 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java @@ -26,6 +26,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import javax.inject.Inject; import javax.xml.parsers.DocumentBuilderFactory; @@ -66,11 +67,14 @@ import org.springframework.boot.build.bom.bomr.version.DependencyVersion; import org.springframework.boot.build.mavenplugin.MavenExec; import org.springframework.util.FileCopyUtils; +import org.springframework.util.PropertyPlaceholderHelper; +import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; /** * DSL extensions for {@link BomPlugin}. * * @author Andy Wilkinson + * @author Phillip Webb */ public class BomExtension { @@ -121,7 +125,8 @@ public void library(String name, String version, Action action) : null; addLibrary(new Library(name, libraryHandler.calendarName, libraryVersion, libraryHandler.groups, libraryHandler.prohibitedVersions, libraryHandler.considerSnapshots, versionAlignment, - libraryHandler.alignWith.dependencyManagementDeclaredIn)); + libraryHandler.alignWith.dependencyManagementDeclaredIn, libraryHandler.linkRootName, + libraryHandler.links)); } public void effectiveBomArtifact() { @@ -149,7 +154,7 @@ public void effectiveBomArtifact() { } MavenExec generateEffectiveBom = this.project.getTasks() .create("generateEffectiveBom", MavenExec.class); - generateEffectiveBom.setProjectDir(generatedBomDir); + generateEffectiveBom.getProjectDir().set(generatedBomDir); File effectiveBom = new File(this.project.getBuildDir(), "generated/effective-bom/" + this.project.getName() + "-effective-bom.xml"); generateEffectiveBom.args("--settings", "settings.xml", "help:effective-pom", @@ -229,6 +234,10 @@ public static class LibraryHandler { private String calendarName; + private String linkRootName; + + private final Map> links = new HashMap<>(); + @Inject public LibraryHandler(Project project, String version) { this.version = version; @@ -265,6 +274,17 @@ public void alignWith(Action action) { action.execute(this.alignWith); } + public void links(Action action) { + links(null, action); + } + + public void links(String linkRootName, Action action) { + LinksHandler handler = new LinksHandler(); + action.execute(handler); + this.linkRootName = linkRootName; + this.links.putAll(handler.links); + } + public static class ProhibitedHandler { private String reason; @@ -417,6 +437,67 @@ public void managedBy(String managedBy) { } + public static class LinksHandler { + + private final Map> links = new HashMap<>(); + + public void site(String linkTemplate) { + site(asFactory(linkTemplate)); + } + + public void site(Function linkFactory) { + add("site", linkFactory); + } + + public void github(String linkTemplate) { + github(asFactory(linkTemplate)); + } + + public void github(Function linkFactory) { + add("github", linkFactory); + } + + public void docs(String linkTemplate) { + docs(asFactory(linkTemplate)); + } + + public void docs(Function linkFactory) { + add("docs", linkFactory); + } + + public void javadoc(String linkTemplate) { + javadoc(asFactory(linkTemplate)); + } + + public void javadoc(Function linkFactory) { + add("javadoc", linkFactory); + } + + public void releaseNotes(String linkTemplate) { + releaseNotes(asFactory(linkTemplate)); + } + + public void releaseNotes(Function linkFactory) { + add("releaseNotes", linkFactory); + } + + public void add(String name, String linkTemplate) { + add(name, asFactory(linkTemplate)); + } + + public void add(String name, Function linkFactory) { + this.links.put(name, linkFactory); + } + + private Function asFactory(String linkTemplate) { + return (version) -> { + PlaceholderResolver resolver = (name) -> "version".equals(name) ? version.toString() : null; + return new PropertyPlaceholderHelper("{", "}").replacePlaceholders(linkTemplate, resolver); + }; + } + + } + public static class UpgradeHandler { private UpgradePolicy upgradePolicy; diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java index 6cbd5ae787b5..07474bd79b79 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,8 +66,8 @@ public void apply(Project project) { project.getTasks().named("check").configure((check) -> check.dependsOn(checkBom)); project.getTasks().create("bomrUpgrade", UpgradeBom.class, bom); project.getTasks().create("moveToSnapshots", MoveToSnapshots.class, bom); + project.getTasks().register("checkLinks", CheckLinks.class, bom); new PublishingCustomizer(project, bom).customize(); - } private void createApiEnforcedConfiguration(Project project) { @@ -291,9 +291,7 @@ private boolean isNodeWithName(Object candidate, String name) { if ((node.name() instanceof QName qname) && name.equals(qname.getLocalPart())) { return true; } - if (name.equals(node.name())) { - return true; - } + return name.equals(node.name()); } return false; } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java index f600097b3d52..fb3f6e962215 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java @@ -45,7 +45,7 @@ * * @author Andy Wilkinson */ -public class CheckBom extends DefaultTask { +public abstract class CheckBom extends DefaultTask { private final BomExtension bom; diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckLinks.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckLinks.java new file mode 100644 index 000000000000..ca5a71b187d9 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckLinks.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build.bom; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import javax.inject.Inject; + +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.gradle.api.DefaultTask; +import org.gradle.api.tasks.TaskAction; +import org.gradle.internal.impldep.org.apache.http.client.config.CookieSpecs; + +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.DefaultResponseErrorHandler; +import org.springframework.web.client.RestTemplate; + +/** + * Task to check that links are working. + * + * @author Andy Wilkinson + * @author Phillip Webb + */ +public abstract class CheckLinks extends DefaultTask { + + private final BomExtension bom; + + @Inject + public CheckLinks(BomExtension bom) { + this.bom = bom; + } + + @TaskAction + void releaseNotes() { + RequestConfig config = RequestConfig.custom().setCookieSpec(CookieSpecs.IGNORE_COOKIES).build(); + CloseableHttpClient httpClient = HttpClients.custom().setDefaultRequestConfig(config).build(); + HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); + RestTemplate restTemplate = new RestTemplate(requestFactory); + restTemplate.setErrorHandler(new IgnoringErrorHandler()); + for (Library library : this.bom.getLibraries()) { + library.getLinks().forEach((name, link) -> { + URI uri; + try { + uri = new URI(link); + ResponseEntity response = restTemplate.exchange(uri, HttpMethod.HEAD, null, String.class); + System.out.println("[%3d] %s - %s (%s)".formatted(response.getStatusCode().value(), + library.getName(), name, uri)); + } + catch (URISyntaxException ex) { + throw new RuntimeException(ex); + } + }); + } + } + + static class IgnoringErrorHandler extends DefaultResponseErrorHandler { + + @Override + public void handleError(ClientHttpResponse response) throws IOException { + } + + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java index b1f66f2b1d19..4a0928c0c997 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java @@ -17,6 +17,7 @@ package org.springframework.boot.build.bom; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -24,13 +25,14 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.TreeMap; +import java.util.function.Function; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.apache.maven.artifact.versioning.VersionRange; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; -import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.artifacts.result.DependencyResult; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; @@ -61,6 +63,10 @@ public class Library { private final String alignsWithBom; + private final String linkRootName; + + private final Map> links; + /** * Create a new {@code Library} with the given {@code name}, {@code version}, and * {@code groups}. @@ -73,11 +79,14 @@ public class Library { * @param considerSnapshots whether to consider snapshots * @param versionAlignment version alignment, if any, for the library * @param alignsWithBom the coordinates of the bom, if any, that this library should - * inline + * align with + * @param linkRootName the root name to use when generating link variable or + * {@code null} to generate one based on the library {@code name} + * @param links a list of HTTP links relevant to the library */ public Library(String name, String calendarName, LibraryVersion version, List groups, List prohibitedVersions, boolean considerSnapshots, VersionAlignment versionAlignment, - String alignsWithBom) { + String alignsWithBom, String linkRootName, Map> links) { this.name = name; this.calendarName = (calendarName != null) ? calendarName : name; this.version = version; @@ -88,6 +97,12 @@ public Library(String name, String calendarName, LibraryVersion version, List getLinks() { + Map links = new TreeMap<>(); + this.links.forEach((name, linkFactory) -> links.put(name, linkFactory.apply(this.version))); + return Collections.unmodifiableMap(links); + } + /** * A version or range of versions that are prohibited from being used in a bom. */ @@ -194,6 +219,44 @@ public DependencyVersion getVersion() { return this.version; } + public int[] componentInts() { + return Arrays.stream(parts()).mapToInt(Integer::parseInt).toArray(); + } + + public String major() { + return parts()[0]; + } + + public String minor() { + return parts()[1]; + } + + public String patch() { + return parts()[2]; + } + + @Override + public String toString() { + return this.version.toString(); + } + + public String toString(String separator) { + return this.version.toString().replace(".", separator); + } + + public String forAntora() { + String[] parts = parts(); + String result = parts[0] + "." + parts[1]; + if (toString().endsWith("SNAPSHOT")) { + result += "-SNAPSHOT"; + } + return result; + } + + private String[] parts() { + return toString().split("[.-]"); + } + } /** @@ -334,17 +397,10 @@ public static class VersionAlignment { } public Set resolve() { - if (this.managedBy == null) { - throw new IllegalStateException("Version alignment without managedBy is not supported"); - } if (this.alignedVersions != null) { return this.alignedVersions; } - Library managingLibrary = this.libraries.stream() - .filter((candidate) -> this.managedBy.equals(candidate.getName())) - .findFirst() - .orElseThrow(() -> new IllegalStateException("Managing library '" + this.managedBy + "' not found.")); - Map versions = resolveAligningDependencies(managingLibrary); + Map versions = resolveAligningDependencies(); Set versionsInLibrary = new HashSet<>(); for (Group group : this.groups) { for (Module module : group.getModules()) { @@ -364,18 +420,8 @@ public Set resolve() { return this.alignedVersions; } - private Map resolveAligningDependencies(Library manager) { - DependencyHandler dependencyHandler = this.project.getDependencies(); - List boms = manager.getGroups() - .stream() - .flatMap((group) -> group.getBoms() - .stream() - .map((bom) -> dependencyHandler - .platform(group.getId() + ":" + bom + ":" + manager.getVersion().getVersion()))) - .toList(); - List dependencies = new ArrayList<>(); - dependencies.addAll(boms); - dependencies.add(dependencyHandler.create(this.from)); + private Map resolveAligningDependencies() { + List dependencies = getAligningDependencies(); Configuration alignmentConfiguration = this.project.getConfigurations() .detachedConfiguration(dependencies.toArray(new Dependency[0])); Map versions = new HashMap<>(); @@ -388,6 +434,58 @@ private Map resolveAligningDependencies(Library manager) { return versions; } + private List getAligningDependencies() { + if (this.managedBy == null) { + Library fromLibrary = findFromLibrary(); + return List + .of(this.project.getDependencies().create(this.from + ":" + fromLibrary.getVersion().getVersion())); + } + else { + Library managingLibrary = findManagingLibrary(); + List boms = getBomDependencies(managingLibrary); + List dependencies = new ArrayList<>(); + dependencies.addAll(boms); + dependencies.add(this.project.getDependencies().create(this.from)); + return dependencies; + } + } + + private Library findFromLibrary() { + for (Library library : this.libraries) { + for (Group group : library.getGroups()) { + for (Module module : group.getModules()) { + if (this.from.equals(group.getId() + ":" + module.getName())) { + return library; + } + } + } + } + return null; + } + + private Library findManagingLibrary() { + if (this.managedBy == null) { + return null; + } + return this.libraries.stream() + .filter((candidate) -> this.managedBy.equals(candidate.getName())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Managing library '" + this.managedBy + "' not found.")); + } + + private List getBomDependencies(Library manager) { + if (manager == null) { + return Collections.emptyList(); + } + return manager.getGroups() + .stream() + .flatMap((group) -> group.getBoms() + .stream() + .map((bom) -> this.project.getDependencies() + .platform(group.getId() + ":" + bom + ":" + manager.getVersion().getVersion()))) + .toList(); + } + String getFrom() { return this.from; } @@ -398,7 +496,11 @@ String getManagedBy() { @Override public String toString() { - return "version from dependencies of " + this.from + " that is managed by " + this.managedBy; + String result = "version from dependencies of " + this.from; + if (this.managedBy != null) { + result += " that is managed by " + this.managedBy; + } + return result; } } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/UpgradePolicy.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/UpgradePolicy.java index e9d72393965f..340c29a7a1c6 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/UpgradePolicy.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/UpgradePolicy.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,12 +35,12 @@ public enum UpgradePolicy implements BiPredicate candidate.isSameMajor(current)), + SAME_MAJOR_VERSION(DependencyVersion::isSameMajor), /** * Patch versions of the current minor version. */ - SAME_MINOR_VERSION((candidate, current) -> candidate.isSameMinor(current)); + SAME_MINOR_VERSION(DependencyVersion::isSameMinor); private final BiPredicate delegate; diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/StandardLibraryUpdateResolver.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/StandardLibraryUpdateResolver.java index 517bf262a3a9..f5ffd02cac42 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/StandardLibraryUpdateResolver.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/StandardLibraryUpdateResolver.java @@ -19,14 +19,12 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.function.BiPredicate; -import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -113,19 +111,14 @@ private List determineResolvedVersionOptions(Library library) { getLaterVersionsForModule(group.getId(), plugin, library)); } } - List allVersions = moduleVersions.values() + return moduleVersions.values() .stream() .flatMap(SortedSet::stream) .distinct() .filter((dependencyVersion) -> this.predicate.test(library, dependencyVersion)) - .toList(); - if (allVersions.isEmpty()) { - return Collections.emptyList(); - } - return allVersions.stream() - .map((version) -> new VersionOption.ResolvedVersionOption(version, + .map((version) -> (VersionOption) new VersionOption.ResolvedVersionOption(version, getMissingModules(moduleVersions, version))) - .collect(Collectors.toList()); + .toList(); } private List getMissingModules(Map> moduleVersions, diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeDependencies.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeDependencies.java index 04934445cd87..19e897cb823d 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeDependencies.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeDependencies.java @@ -169,8 +169,9 @@ private List verifyLabels(GitHubRepository repository) { if (!availableLabels.containsAll(issueLabels)) { List unknownLabels = new ArrayList<>(issueLabels); unknownLabels.removeAll(availableLabels); + String suffix = (unknownLabels.size() == 1) ? "" : "s"; throw new InvalidUserDataException( - "Unknown label(s): " + StringUtils.collectionToCommaDelimitedString(unknownLabels)); + "Unknown label" + suffix + ": " + StringUtils.collectionToCommaDelimitedString(unknownLabels)); } return issueLabels; } @@ -193,7 +194,7 @@ private Milestone determineMilestone(GitHubRepository repository) { java.util.Optional matchingMilestone = milestones.stream() .filter((milestone) -> milestone.getName().equals(getMilestone().get())) .findFirst(); - if (!matchingMilestone.isPresent()) { + if (matchingMilestone.isEmpty()) { throw new InvalidUserDataException("Unknown milestone: " + getMilestone().get()); } return matchingMilestone.get(); @@ -241,9 +242,9 @@ private boolean isAnUpgrade(Library library, DependencyVersion candidate) { } private boolean isNotProhibited(Library library, DependencyVersion candidate) { - return !library.getProhibitedVersions() + return library.getProhibitedVersions() .stream() - .anyMatch((prohibited) -> prohibited.isProhibited(candidate.toString())); + .noneMatch((prohibited) -> prohibited.isProhibited(candidate.toString())); } private List matchingLibraries() { diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/AbstractDependencyVersion.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/AbstractDependencyVersion.java index ffb119efa9cc..c602347ef97c 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/AbstractDependencyVersion.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/AbstractDependencyVersion.java @@ -57,10 +57,7 @@ public boolean equals(Object obj) { return false; } AbstractDependencyVersion other = (AbstractDependencyVersion) obj; - if (!this.comparableVersion.equals(other.comparableVersion)) { - return false; - } - return true; + return this.comparableVersion.equals(other.comparableVersion); } @Override diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/ArtifactVersionDependencyVersion.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/ArtifactVersionDependencyVersion.java index 61e1d761e201..9986c12add4c 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/ArtifactVersionDependencyVersion.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/ArtifactVersionDependencyVersion.java @@ -81,6 +81,9 @@ private boolean isSameMinor(ArtifactVersionDependencyVersion other) { @Override public boolean isUpgrade(DependencyVersion candidate, boolean movingToSnapshots) { + if (candidate instanceof MultipleComponentsDependencyVersion) { + return super.isUpgrade(candidate, movingToSnapshots); + } if (!(candidate instanceof ArtifactVersionDependencyVersion)) { return false; } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/DependencyVersion.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/DependencyVersion.java index f4b9b897a1ba..d82d5b8a50f5 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/DependencyVersion.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/DependencyVersion.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,7 +48,7 @@ public interface DependencyVersion extends Comparable { * Returns whether the given {@code candidate} is an upgrade of this version. * @param candidate the version to consider * @param movingToSnapshots whether the upgrade is to be considered as part of moving - * to snaphots + * to snapshots * @return {@code true} if the candidate is an upgrade, otherwise false */ boolean isUpgrade(DependencyVersion candidate, boolean movingToSnapshots); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/ReleaseTrainDependencyVersion.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/ReleaseTrainDependencyVersion.java index c9c79bcdc69b..e43c1b05d9de 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/ReleaseTrainDependencyVersion.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/ReleaseTrainDependencyVersion.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,22 +64,25 @@ public int compareTo(DependencyVersion other) { @Override public boolean isUpgrade(DependencyVersion candidate, boolean movingToSnapshots) { - if (!(candidate instanceof ReleaseTrainDependencyVersion)) { - return true; + if (candidate instanceof ReleaseTrainDependencyVersion candidateReleaseTrain) { + return isUpgrade(candidateReleaseTrain, movingToSnapshots); } - ReleaseTrainDependencyVersion candidateReleaseTrain = (ReleaseTrainDependencyVersion) candidate; - int comparison = this.releaseTrain.compareTo(candidateReleaseTrain.releaseTrain); + return true; + } + + private boolean isUpgrade(ReleaseTrainDependencyVersion candidate, boolean movingToSnapshots) { + int comparison = this.releaseTrain.compareTo(candidate.releaseTrain); if (comparison != 0) { return comparison < 0; } - if (movingToSnapshots && !isSnapshot() && candidateReleaseTrain.isSnapshot()) { + if (movingToSnapshots && !isSnapshot() && candidate.isSnapshot()) { return true; } - comparison = this.type.compareTo(candidateReleaseTrain.type); + comparison = this.type.compareTo(candidate.type); if (comparison != 0) { return comparison < 0; } - return Integer.compare(this.version, candidateReleaseTrain.version) < 0; + return Integer.compare(this.version, candidate.version) < 0; } private boolean isSnapshot() { @@ -88,10 +91,9 @@ private boolean isSnapshot() { @Override public boolean isSnapshotFor(DependencyVersion candidate) { - if (!isSnapshot() || !(candidate instanceof ReleaseTrainDependencyVersion)) { + if (!isSnapshot() || !(candidate instanceof ReleaseTrainDependencyVersion candidateReleaseTrain)) { return false; } - ReleaseTrainDependencyVersion candidateReleaseTrain = (ReleaseTrainDependencyVersion) candidate; return this.releaseTrain.equals(candidateReleaseTrain.releaseTrain); } @@ -127,10 +129,7 @@ public boolean equals(Object obj) { return false; } ReleaseTrainDependencyVersion other = (ReleaseTrainDependencyVersion) obj; - if (!this.original.equals(other.original)) { - return false; - } - return true; + return this.original.equals(other.original); } @Override diff --git a/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForConflicts.java b/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForConflicts.java index 72cdc0946129..3f30319aa362 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForConflicts.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForConflicts.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,7 +48,7 @@ * * @author Andy Wilkinson */ -public class CheckClasspathForConflicts extends DefaultTask { +public abstract class CheckClasspathForConflicts extends DefaultTask { private final List> ignores = new ArrayList<>(); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForProhibitedDependencies.java b/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForProhibitedDependencies.java index 548344b731fd..70d39f019462 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForProhibitedDependencies.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForProhibitedDependencies.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.boot.build.classpath; +import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; @@ -33,7 +34,12 @@ * * @author Andy Wilkinson */ -public class CheckClasspathForProhibitedDependencies extends DefaultTask { +public abstract class CheckClasspathForProhibitedDependencies extends DefaultTask { + + private static final Set PROHIBITED_GROUPS = Set.of("org.codehaus.groovy", "org.eclipse.jetty.toolchain", + "commons-logging", "org.apache.geronimo.specs", "com.sun.activation"); + + private static final Set PERMITTED_JAVAX_GROUPS = Set.of("javax.batch", "javax.cache", "javax.money"); private Configuration classpath; @@ -69,41 +75,20 @@ public void checkForProhibitedDependencies() { } private boolean prohibited(ModuleVersionIdentifier id) { - String group = id.getGroup(); - if (group.equals("javax.batch")) { - return false; - } - if (group.equals("javax.cache")) { - return false; - } - if (group.equals("javax.money")) { - return false; - } - if (group.equals("org.codehaus.groovy")) { - return true; - } - if (group.equals("org.eclipse.jetty.toolchain")) { - return true; - } - if (group.startsWith("javax")) { - return true; - } - if (group.equals("commons-logging")) { - return true; - } - if (group.equals("org.slf4j") && id.getName().equals("jcl-over-slf4j")) { - return true; - } - if (group.startsWith("org.jboss.spec")) { - return true; - } - if (group.equals("org.apache.geronimo.specs")) { - return true; - } - if (group.equals("com.sun.activation")) { - return true; - } - return false; + return PROHIBITED_GROUPS.contains(id.getGroup()) || prohibitedJavax(id) || prohibitedSlf4j(id) + || prohibitedJbossSpec(id); + } + + private boolean prohibitedSlf4j(ModuleVersionIdentifier id) { + return id.getGroup().equals("org.slf4j") && id.getName().equals("jcl-over-slf4j"); + } + + private boolean prohibitedJbossSpec(ModuleVersionIdentifier id) { + return id.getGroup().startsWith("org.jboss.spec"); + } + + private boolean prohibitedJavax(ModuleVersionIdentifier id) { + return id.getGroup().startsWith("javax.") && !PERMITTED_JAVAX_GROUPS.contains(id.getGroup()); } } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnconstrainedDirectDependencies.java b/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnconstrainedDirectDependencies.java index 543a33b772a1..6a846c2a7c70 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnconstrainedDirectDependencies.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnconstrainedDirectDependencies.java @@ -34,7 +34,7 @@ * * @author Andy Wilkinson */ -public class CheckClasspathForUnconstrainedDirectDependencies extends DefaultTask { +public abstract class CheckClasspathForUnconstrainedDirectDependencies extends DefaultTask { private Configuration classpath; diff --git a/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnnecessaryExclusions.java b/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnnecessaryExclusions.java index 511898299957..3fa6522cbb59 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnnecessaryExclusions.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnnecessaryExclusions.java @@ -48,7 +48,7 @@ * * @author Andy Wilkinson */ -public class CheckClasspathForUnnecessaryExclusions extends DefaultTask { +public abstract class CheckClasspathForUnnecessaryExclusions extends DefaultTask { private static final Map SPRING_BOOT_DEPENDENCIES_PROJECT = Collections.singletonMap("path", ":spring-boot-project:spring-boot-dependencies"); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/cli/HomebrewFormula.java b/buildSrc/src/main/java/org/springframework/boot/build/cli/HomebrewFormula.java index 62e790a31c06..8dc5dd709ce7 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/cli/HomebrewFormula.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/cli/HomebrewFormula.java @@ -18,16 +18,15 @@ import java.io.File; import java.security.MessageDigest; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import org.apache.commons.codec.digest.DigestUtils; import org.gradle.api.DefaultTask; import org.gradle.api.Project; import org.gradle.api.Task; -import org.gradle.api.file.RegularFile; -import org.gradle.api.provider.Provider; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.PathSensitive; @@ -42,62 +41,14 @@ * * @author Andy Wilkinson */ -public class HomebrewFormula extends DefaultTask { - - private Provider archive; - - private File template; - - private File outputDir; +public abstract class HomebrewFormula extends DefaultTask { public HomebrewFormula() { - getInputs().property("version", getProject().provider(getProject()::getVersion)); - } - - @InputFile - @PathSensitive(PathSensitivity.RELATIVE) - public RegularFile getArchive() { - return this.archive.get(); - } - - public void setArchive(Provider archive) { - this.archive = archive; - } - - @InputFile - @PathSensitive(PathSensitivity.RELATIVE) - public File getTemplate() { - return this.template; - } - - public void setTemplate(File template) { - this.template = template; - } - - @OutputDirectory - public File getOutputDir() { - return this.outputDir; - } - - public void setOutputDir(File outputDir) { - this.outputDir = outputDir; - } - - protected void createDescriptor(Map additionalProperties) { - getProject().copy((copy) -> { - copy.from(this.template); - copy.into(this.outputDir); - copy.expand(getProperties(additionalProperties)); - }); - } - - private Map getProperties(Map additionalProperties) { - Map properties = new HashMap<>(additionalProperties); Project project = getProject(); - properties.put("hash", sha256(this.archive.get().getAsFile())); - properties.put("repo", ArtifactRelease.forProject(project).getDownloadRepo()); - properties.put("project", project); - return properties; + MapProperty properties = getProperties(); + properties.put("hash", getArchive().map((archive) -> sha256(archive.getAsFile()))); + getProperties().put("repo", ArtifactRelease.forProject(project).getDownloadRepo()); + getProperties().put("version", project.getVersion().toString()); } private String sha256(File file) { @@ -110,9 +61,27 @@ private String sha256(File file) { } } + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + public abstract RegularFileProperty getArchive(); + + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + public abstract RegularFileProperty getTemplate(); + + @OutputDirectory + public abstract DirectoryProperty getOutputDir(); + + @Input + abstract MapProperty getProperties(); + @TaskAction void createFormula() { - createDescriptor(Collections.emptyMap()); + getProject().copy((copy) -> { + copy.from(getTemplate()); + copy.into(getOutputDir()); + copy.expand(getProperties().get()); + }); } } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/constraints/DocumentConstrainedVersions.java b/buildSrc/src/main/java/org/springframework/boot/build/constraints/DocumentConstrainedVersions.java index c4d9e05f59ea..e70502f785fc 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/constraints/DocumentConstrainedVersions.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/constraints/DocumentConstrainedVersions.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,10 +21,8 @@ import java.io.IOException; import java.io.PrintWriter; -import javax.inject.Inject; - import org.gradle.api.DefaultTask; -import org.gradle.api.model.ObjectFactory; +import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.OutputFile; @@ -37,38 +35,22 @@ * * @author Andy Wilkinson */ -public class DocumentConstrainedVersions extends DefaultTask { - - private final SetProperty constrainedVersions; - - private File outputFile; - - @Inject - public DocumentConstrainedVersions(ObjectFactory objectFactory) { - this.constrainedVersions = objectFactory.setProperty(ConstrainedVersion.class); - } +public abstract class DocumentConstrainedVersions extends DefaultTask { @Input - public SetProperty getConstrainedVersions() { - return this.constrainedVersions; - } + public abstract SetProperty getConstrainedVersions(); @OutputFile - public File getOutputFile() { - return this.outputFile; - } - - public void setOutputFile(File outputFile) { - this.outputFile = outputFile; - } + public abstract RegularFileProperty getOutputFile(); @TaskAction public void documentConstrainedVersions() throws IOException { - this.outputFile.getParentFile().mkdirs(); - try (PrintWriter writer = new PrintWriter(new FileWriter(this.outputFile))) { + File outputFile = getOutputFile().get().getAsFile(); + outputFile.getParentFile().mkdirs(); + try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) { writer.println("|==="); writer.println("| Group ID | Artifact ID | Version"); - for (ConstrainedVersion constrainedVersion : this.constrainedVersions.get()) { + for (ConstrainedVersion constrainedVersion : getConstrainedVersions().get()) { writer.println(); writer.printf("| `%s`%n", constrainedVersion.getGroup()); writer.printf("| `%s`%n", constrainedVersion.getArtifact()); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/constraints/DocumentVersionProperties.java b/buildSrc/src/main/java/org/springframework/boot/build/constraints/DocumentVersionProperties.java index e083b01277db..ef1c7c8647ad 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/constraints/DocumentVersionProperties.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/constraints/DocumentVersionProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,10 +21,8 @@ import java.io.IOException; import java.io.PrintWriter; -import javax.inject.Inject; - import org.gradle.api.DefaultTask; -import org.gradle.api.model.ObjectFactory; +import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.OutputFile; @@ -37,38 +35,22 @@ * * @author Christoph Dreis */ -public class DocumentVersionProperties extends DefaultTask { - - private final SetProperty versionProperties; - - private File outputFile; - - @Inject - public DocumentVersionProperties(ObjectFactory objectFactory) { - this.versionProperties = objectFactory.setProperty(VersionProperty.class); - } +public abstract class DocumentVersionProperties extends DefaultTask { @Input - public SetProperty getVersionProperties() { - return this.versionProperties; - } + public abstract SetProperty getVersionProperties(); @OutputFile - public File getOutputFile() { - return this.outputFile; - } - - public void setOutputFile(File outputFile) { - this.outputFile = outputFile; - } + public abstract RegularFileProperty getOutputFile(); @TaskAction public void documentVersionProperties() throws IOException { - this.outputFile.getParentFile().mkdirs(); - try (PrintWriter writer = new PrintWriter(new FileWriter(this.outputFile))) { + File outputFile = getOutputFile().getAsFile().get(); + outputFile.getParentFile().mkdirs(); + try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) { writer.println("|==="); writer.println("| Library | Version Property"); - for (VersionProperty versionProperty : this.versionProperties.get()) { + for (VersionProperty versionProperty : getVersionProperties().get()) { writer.println(); writer.printf("| `%s`%n", versionProperty.getLibraryName()); writer.printf("| `%s`%n", versionProperty.getVersionProperty()); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/constraints/ExtractVersionConstraints.java b/buildSrc/src/main/java/org/springframework/boot/build/constraints/ExtractVersionConstraints.java index 4d14b2977d4e..bd6a57d11d55 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/constraints/ExtractVersionConstraints.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/constraints/ExtractVersionConstraints.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,17 +26,20 @@ import java.util.TreeSet; import org.gradle.api.DefaultTask; +import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.ComponentMetadataDetails; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.DependencyConstraint; import org.gradle.api.artifacts.DependencyConstraintMetadata; +import org.gradle.api.artifacts.DependencyConstraintSet; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.TaskAction; import org.gradle.platform.base.Platform; import org.springframework.boot.build.bom.BomExtension; +import org.springframework.boot.build.bom.BomPlugin; import org.springframework.boot.build.bom.Library; /** @@ -45,7 +48,7 @@ * * @author Andy Wilkinson */ -public class ExtractVersionConstraints extends DefaultTask { +public abstract class ExtractVersionConstraints extends DefaultTask { private final Configuration configuration; @@ -55,7 +58,9 @@ public class ExtractVersionConstraints extends DefaultTask { private final Set versionProperties = new TreeSet<>(); - private final List projectPaths = new ArrayList<>(); + private final List dependencyConstraintSets = new ArrayList<>(); + + private final List boms = new ArrayList<>(); public ExtractVersionConstraints() { DependencyHandler dependencies = getProject().getDependencies(); @@ -68,7 +73,12 @@ public void enforcedPlatform(String projectPath) { .add(getProject().getDependencies() .enforcedPlatform( getProject().getDependencies().project(Collections.singletonMap("path", projectPath)))); - this.projectPaths.add(projectPath); + Project project = getProject().project(projectPath); + project.getPlugins().withType(BomPlugin.class).all((plugin) -> { + this.boms.add(project.getExtensions().getByType(BomExtension.class)); + this.dependencyConstraintSets + .add(project.getConfigurations().getByName("apiElements").getAllDependencyConstraints()); + }); } @Internal @@ -89,12 +99,9 @@ public Set getVersionProperties() { @TaskAction void extractVersionConstraints() { this.configuration.resolve(); - for (String projectPath : this.projectPaths) { - extractVersionProperties(projectPath); - for (DependencyConstraint constraint : getProject().project(projectPath) - .getConfigurations() - .getByName("apiElements") - .getAllDependencyConstraints()) { + this.boms.forEach(this::extractVersionProperties); + for (DependencyConstraintSet constraints : this.dependencyConstraintSets) { + for (DependencyConstraint constraint : constraints) { this.versionConstraints.put(constraint.getGroup() + ":" + constraint.getName(), constraint.getVersionConstraint().toString()); this.constrainedVersions.add(new ConstrainedVersion(constraint.getGroup(), constraint.getName(), @@ -103,9 +110,7 @@ void extractVersionConstraints() { } } - private void extractVersionProperties(String projectPath) { - Object bom = getProject().project(projectPath).getExtensions().getByName("bom"); - BomExtension bomExtension = (BomExtension) bom; + private void extractVersionProperties(BomExtension bomExtension) { for (Library lib : bomExtension.getLibraries()) { String versionProperty = lib.getVersionProperty(); if (versionProperty != null) { diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckAdditionalSpringConfigurationMetadata.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckAdditionalSpringConfigurationMetadata.java index a4c927e8b3cc..8405dc445f08 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckAdditionalSpringConfigurationMetadata.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckAdditionalSpringConfigurationMetadata.java @@ -46,21 +46,16 @@ * * @author Andy Wilkinson */ -public class CheckAdditionalSpringConfigurationMetadata extends SourceTask { +public abstract class CheckAdditionalSpringConfigurationMetadata extends SourceTask { private final File projectDir; - private final RegularFileProperty reportLocation; - public CheckAdditionalSpringConfigurationMetadata() { this.projectDir = getProject().getProjectDir(); - this.reportLocation = getProject().getObjects().fileProperty(); } @OutputFile - public RegularFileProperty getReportLocation() { - return this.reportLocation; - } + public abstract RegularFileProperty getReportLocation(); @Override @InputFiles @@ -96,7 +91,7 @@ private Report createReport() throws IOException, JsonParseException, JsonMappin @SuppressWarnings("unchecked") private void check(String key, Map json, Analysis analysis) { - List> groups = (List>) json.get(key); + List> groups = (List>) json.getOrDefault(key, Collections.emptyList()); List names = groups.stream().map((group) -> (String) group.get("name")).toList(); List sortedNames = sortedCopy(names); for (int i = 0; i < names.size(); i++) { diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckSpringConfigurationMetadata.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckSpringConfigurationMetadata.java index 048de4e9f5cc..f1bff685cd86 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckSpringConfigurationMetadata.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckSpringConfigurationMetadata.java @@ -32,6 +32,7 @@ import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.ListProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.OutputFile; @@ -45,41 +46,23 @@ * * @author Andy Wilkinson */ -public class CheckSpringConfigurationMetadata extends DefaultTask { +public abstract class CheckSpringConfigurationMetadata extends DefaultTask { - private List exclusions = new ArrayList<>(); - - private final File projectDir; - - private final RegularFileProperty reportLocation; - - private final RegularFileProperty metadataLocation; + private final Path projectRoot; public CheckSpringConfigurationMetadata() { - this.projectDir = getProject().getProjectDir(); - this.metadataLocation = getProject().getObjects().fileProperty(); - this.reportLocation = getProject().getObjects().fileProperty(); + this.projectRoot = getProject().getProjectDir().toPath(); } @OutputFile - public RegularFileProperty getReportLocation() { - return this.reportLocation; - } + public abstract RegularFileProperty getReportLocation(); @InputFile @PathSensitive(PathSensitivity.RELATIVE) - public RegularFileProperty getMetadataLocation() { - return this.metadataLocation; - } - - public void setExclusions(List exclusions) { - this.exclusions = exclusions; - } + public abstract RegularFileProperty getMetadataLocation(); @Input - public List getExclusions() { - return this.exclusions; - } + public abstract ListProperty getExclusions(); @TaskAction void check() throws JsonParseException, IOException { @@ -95,8 +78,8 @@ void check() throws JsonParseException, IOException { @SuppressWarnings("unchecked") private Report createReport() throws IOException, JsonParseException, JsonMappingException { ObjectMapper objectMapper = new ObjectMapper(); - File file = this.metadataLocation.get().getAsFile(); - Report report = new Report(this.projectDir.toPath().relativize(file.toPath())); + File file = getMetadataLocation().get().getAsFile(); + Report report = new Report(this.projectRoot.relativize(file.toPath())); Map json = objectMapper.readValue(file, Map.class); List> properties = (List>) json.get("properties"); for (Map property : properties) { @@ -109,7 +92,7 @@ private Report createReport() throws IOException, JsonParseException, JsonMappin } private boolean isExcluded(String propertyName) { - for (String exclusion : this.exclusions) { + for (String exclusion : getExclusions().get()) { if (propertyName.equals(exclusion)) { return true; } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CompoundRow.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CompoundRow.java index d322ce106290..ffa9f5687b5f 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CompoundRow.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CompoundRow.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ * * @author Brian Clozel * @author Phillip Webb + * @author Moritz Halbritter */ class CompoundRow extends Row { @@ -45,9 +46,9 @@ void addProperty(ConfigurationProperty property) { void write(Asciidoc asciidoc) { asciidoc.append("|"); asciidoc.append("[[" + getAnchor() + "]]"); - asciidoc.append("<<" + getAnchor() + ","); + asciidoc.append("xref:#" + getAnchor() + "["); this.propertyNames.forEach(asciidoc::appendWithHardLineBreaks); - asciidoc.appendln(">>"); + asciidoc.appendln("]"); asciidoc.appendln("|+++", this.description, "+++"); asciidoc.appendln("|"); } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java index d7c8710e2cb4..a7ac94f60ced 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,11 @@ package org.springframework.boot.build.context.properties; -import java.io.File; import java.io.IOException; import org.gradle.api.DefaultTask; import org.gradle.api.Task; +import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.OutputDirectory; @@ -36,12 +36,10 @@ * @author Andy Wilkinson * @author Phillip Webb */ -public class DocumentConfigurationProperties extends DefaultTask { +public abstract class DocumentConfigurationProperties extends DefaultTask { private FileCollection configurationPropertyMetadata; - private File outputDir; - @InputFiles @PathSensitive(PathSensitivity.RELATIVE) public FileCollection getConfigurationPropertyMetadata() { @@ -53,13 +51,7 @@ public void setConfigurationPropertyMetadata(FileCollection configurationPropert } @OutputDirectory - public File getOutputDir() { - return this.outputDir; - } - - public void setOutputDir(File outputDir) { - this.outputDir = outputDir; - } + public abstract DirectoryProperty getOutputDir(); @TaskAction void documentConfigurationProperties() throws IOException { @@ -78,10 +70,12 @@ void documentConfigurationProperties() throws IOException { snippets.add("application-properties.security", "Security Properties", this::securityPrefixes); snippets.add("application-properties.rsocket", "RSocket Properties", this::rsocketPrefixes); snippets.add("application-properties.actuator", "Actuator Properties", this::actuatorPrefixes); - snippets.add("application-properties.docker-compose", "Docker Compose Properties", this::dockerComposePrefixes); snippets.add("application-properties.devtools", "Devtools Properties", this::devtoolsPrefixes); + snippets.add("application-properties.docker-compose", "Docker Compose Properties", this::dockerComposePrefixes); + snippets.add("application-properties.testcontainers", "Testcontainers Properties", + this::testcontainersPrefixes); snippets.add("application-properties.testing", "Testing Properties", this::testingPrefixes); - snippets.writeTo(this.outputDir.toPath()); + snippets.writeTo(getOutputDir().getAsFile().get().toPath()); } private void corePrefixes(Config config) { @@ -106,6 +100,7 @@ private void corePrefixes(Config config) { config.accept("spring.reactor"); config.accept("spring.ssl"); config.accept("spring.task"); + config.accept("spring.threads"); config.accept("spring.mandatory-file-encoding"); config.accept("info"); config.accept("spring.output.ansi.enabled"); @@ -170,6 +165,7 @@ private void integrationPrefixes(Config prefix) { prefix.accept("spring.integration"); prefix.accept("spring.jms"); prefix.accept("spring.kafka"); + prefix.accept("spring.pulsar"); prefix.accept("spring.rabbitmq"); prefix.accept("spring.hazelcast"); prefix.accept("spring.webservices"); @@ -211,6 +207,7 @@ private void rsocketPrefixes(Config prefix) { private void actuatorPrefixes(Config prefix) { prefix.accept("management"); + prefix.accept("micrometer"); } private void dockerComposePrefixes(Config prefix) { @@ -222,7 +219,11 @@ private void devtoolsPrefixes(Config prefix) { } private void testingPrefixes(Config prefix) { - prefix.accept("spring.test"); + prefix.accept("spring.test."); + } + + private void testcontainersPrefixes(Config prefix) { + prefix.accept("spring.testcontainers."); } } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/SingleRow.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/SingleRow.java index 65f6bb3625fa..c81f57c9f4e2 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/SingleRow.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/SingleRow.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ * * @author Brian Clozel * @author Phillip Webb + * @author Moritz Halbritter */ class SingleRow extends Row { @@ -56,7 +57,7 @@ private String getDefaultValue(Object defaultValue) { void write(Asciidoc asciidoc) { asciidoc.append("|"); asciidoc.append("[[" + getAnchor() + "]]"); - asciidoc.appendln("<<" + getAnchor() + ",`+", this.displayName, "+`>>"); + asciidoc.appendln("xref:#" + getAnchor() + "[`+", this.displayName, "+`]"); writeDescription(asciidoc); writeDefaultValue(asciidoc); } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Snippets.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Snippets.java index 3e7d7ebf199e..a86560c2d6a7 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Snippets.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Snippets.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -102,11 +102,7 @@ private Asciidoc getAsciidoc(Snippet snippet, Table table) { private void writeAsciidoc(Path outputDirectory, Snippet snippet, Asciidoc asciidoc) throws IOException { String[] parts = (snippet.getAnchor()).split("\\."); - Path path = outputDirectory; - for (int i = 0; i < parts.length; i++) { - String name = (i < parts.length - 1) ? parts[i] : parts[i] + ".adoc"; - path = path.resolve(name); - } + Path path = outputDirectory.resolve(parts[parts.length - 1] + ".adoc"); createDirectory(path.getParent()); Files.deleteIfExists(path); try (OutputStream outputStream = Files.newOutputStream(path)) { diff --git a/buildSrc/src/main/java/org/springframework/boot/build/devtools/DocumentDevtoolsPropertyDefaults.java b/buildSrc/src/main/java/org/springframework/boot/build/devtools/DocumentDevtoolsPropertyDefaults.java index 515228ce70ab..106e53465743 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/devtools/DocumentDevtoolsPropertyDefaults.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/devtools/DocumentDevtoolsPropertyDefaults.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,18 +39,15 @@ * * @author Andy Wilkinson */ -public class DocumentDevtoolsPropertyDefaults extends DefaultTask { +public abstract class DocumentDevtoolsPropertyDefaults extends DefaultTask { private final Configuration devtools; - private final RegularFileProperty outputFile; - public DocumentDevtoolsPropertyDefaults() { this.devtools = getProject().getConfigurations().create("devtools"); - this.outputFile = getProject().getObjects().fileProperty(); - this.outputFile.convention(getProject().getLayout() + getOutputFile().convention(getProject().getLayout() .getBuildDirectory() - .file("docs/generated/using/devtools-property-defaults.adoc")); + .file("generated/docs/using/devtools-property-defaults.adoc")); Map dependency = new HashMap<>(); dependency.put("path", ":spring-boot-project:spring-boot-devtools"); dependency.put("configuration", "propertyDefaults"); @@ -63,9 +60,7 @@ public FileCollection getDevtools() { } @OutputFile - public RegularFileProperty getOutputFile() { - return this.outputFile; - } + public abstract RegularFileProperty getOutputFile(); @TaskAction void documentPropertyDefaults() throws IOException { @@ -86,7 +81,7 @@ private Map loadProperties() throws IOException, FileNotFoundExc } private void documentProperties(Map properties) throws IOException { - try (PrintWriter writer = new PrintWriter(new FileWriter(this.outputFile.getAsFile().get()))) { + try (PrintWriter writer = new PrintWriter(new FileWriter(getOutputFile().getAsFile().get()))) { writer.println("[cols=\"3,1\"]"); writer.println("|==="); writer.println("| Name | Default Value"); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/docs/ApplicationRunner.java b/buildSrc/src/main/java/org/springframework/boot/build/docs/ApplicationRunner.java index 0f95d55d3d67..9f43c059d50e 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/docs/ApplicationRunner.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/docs/ApplicationRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; @@ -47,29 +48,17 @@ * * @author Andy Wilkinson */ -public class ApplicationRunner extends DefaultTask { - - private final RegularFileProperty output = getProject().getObjects().fileProperty(); - - private final ListProperty args = getProject().getObjects().listProperty(String.class); - - private final Property mainClass = getProject().getObjects().property(String.class); - - private final Property expectedLogging = getProject().getObjects().property(String.class); - - private final Property applicationJar = getProject().getObjects() - .property(String.class) - .convention("/opt/apps/myapp.jar"); - - private final Map normalizations = new HashMap<>(); +public abstract class ApplicationRunner extends DefaultTask { private FileCollection classpath; - @OutputFile - public RegularFileProperty getOutput() { - return this.output; + public ApplicationRunner() { + getApplicationJar().convention("/opt/apps/myapp.jar"); } + @OutputFile + public abstract RegularFileProperty getOutput(); + @Classpath public FileCollection getClasspath() { return this.classpath; @@ -80,37 +69,27 @@ public void setClasspath(FileCollection classpath) { } @Input - public ListProperty getArgs() { - return this.args; - } + public abstract ListProperty getArgs(); @Input - public Property getMainClass() { - return this.mainClass; - } + public abstract Property getMainClass(); @Input - public Property getExpectedLogging() { - return this.expectedLogging; - } + public abstract Property getExpectedLogging(); @Input - Map getNormalizations() { - return this.normalizations; - } + abstract MapProperty getNormalizations(); @Input - public Property getApplicationJar() { - return this.applicationJar; - } + abstract Property getApplicationJar(); public void normalizeTomcatPort() { - this.normalizations.put("(Tomcat started on port\\(s\\): )[\\d]+( \\(http\\))", "$18080$2"); - this.normalizations.put("(Tomcat initialized with port\\(s\\): )[\\d]+( \\(http\\))", "$18080$2"); + getNormalizations().put("(Tomcat started on port )[\\d]+( \\(http\\))", "$18080$2"); + getNormalizations().put("(Tomcat initialized with port )[\\d]+( \\(http\\))", "$18080$2"); } public void normalizeLiveReloadPort() { - this.normalizations.put("(LiveReload server is running on port )[\\d]+", "$135729"); + getNormalizations().put("(LiveReload server is running on port )[\\d]+", "$135729"); } @TaskAction @@ -123,9 +102,9 @@ void runApplication() throws IOException { .stream() .map(File::getAbsolutePath) .collect(Collectors.joining(File.pathSeparator))); - command.add(this.mainClass.get()); - command.addAll(this.args.get()); - File outputFile = this.output.getAsFile().get(); + command.add(getMainClass().get()); + command.addAll(getArgs().get()); + File outputFile = getOutput().getAsFile().get(); Process process = new ProcessBuilder().redirectOutput(outputFile) .redirectError(outputFile) .command(command) @@ -137,7 +116,7 @@ void runApplication() throws IOException { private void awaitLogging(Process process) { long end = System.currentTimeMillis() + 60000; - String expectedLogging = this.expectedLogging.get(); + String expectedLogging = getExpectedLogging().get(); while (System.currentTimeMillis() < end) { for (String line : outputLines()) { if (line.contains(expectedLogging)) { @@ -152,7 +131,7 @@ private void awaitLogging(Process process) { } private List outputLines() { - Path outputPath = this.output.get().getAsFile().toPath(); + Path outputPath = getOutput().get().getAsFile().toPath(); try { return Files.readAllLines(outputPath); } @@ -164,7 +143,7 @@ private List outputLines() { private void normalizeLogging() { List outputLines = outputLines(); List normalizedLines = normalize(outputLines); - Path outputPath = this.output.get().getAsFile().toPath(); + Path outputPath = getOutput().get().getAsFile().toPath(); try { Files.write(outputPath, normalizedLines); } @@ -175,9 +154,9 @@ private void normalizeLogging() { private List normalize(List lines) { List normalizedLines = lines; - Map normalizations = new HashMap<>(this.normalizations); + Map normalizations = new HashMap<>(getNormalizations().get()); normalizations.put("(Starting .* using Java .* with PID [\\d]+ \\().*( started by ).*( in ).*(\\))", - "$1" + this.applicationJar.get() + "$2myuser$3/opt/apps/$4"); + "$1" + getApplicationJar().get() + "$2myuser$3/opt/apps/$4"); for (Entry normalization : normalizations.entrySet()) { Pattern pattern = Pattern.compile(normalization.getKey()); normalizedLines = normalize(normalizedLines, pattern, normalization.getValue()); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/DocumentPluginGoals.java b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/DocumentPluginGoals.java index f5256fc17c80..414ae1457007 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/DocumentPluginGoals.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/DocumentPluginGoals.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,10 +21,12 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.List; -import java.util.Map; import org.gradle.api.DefaultTask; import org.gradle.api.Task; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.MapProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.OutputDirectory; @@ -39,46 +41,22 @@ * * @author Andy Wilkinson */ -public class DocumentPluginGoals extends DefaultTask { +public abstract class DocumentPluginGoals extends DefaultTask { private final PluginXmlParser parser = new PluginXmlParser(); - private File pluginXml; - - private File outputDir; - - private Map goalSections; - @OutputDirectory - public File getOutputDir() { - return this.outputDir; - } - - public void setOutputDir(File outputDir) { - this.outputDir = outputDir; - } + public abstract DirectoryProperty getOutputDir(); @Input - public Map getGoalSections() { - return this.goalSections; - } - - public void setGoalSections(Map goalSections) { - this.goalSections = goalSections; - } + public abstract MapProperty getGoalSections(); @InputFile - public File getPluginXml() { - return this.pluginXml; - } - - public void setPluginXml(File pluginXml) { - this.pluginXml = pluginXml; - } + public abstract RegularFileProperty getPluginXml(); @TaskAction public void documentPluginGoals() throws IOException { - Plugin plugin = this.parser.parse(this.pluginXml); + Plugin plugin = this.parser.parse(getPluginXml().getAsFile().get()); writeOverview(plugin); for (Mojo mojo : plugin.getMojos()) { documentMojo(plugin, mojo); @@ -86,13 +64,14 @@ public void documentPluginGoals() throws IOException { } private void writeOverview(Plugin plugin) throws IOException { - try (PrintWriter writer = new PrintWriter(new FileWriter(new File(this.outputDir, "overview.adoc")))) { + try (PrintWriter writer = new PrintWriter( + new FileWriter(new File(getOutputDir().getAsFile().get(), "overview.adoc")))) { writer.println("[cols=\"1,3\"]"); writer.println("|==="); writer.println("| Goal | Description"); writer.println(); for (Mojo mojo : plugin.getMojos()) { - writer.printf("| <<%s,%s:%s>>%n", goalSectionId(mojo), plugin.getGoalPrefix(), mojo.getGoal()); + writer.printf("| xref:%s[%s:%s]%n", goalSectionId(mojo, false), plugin.getGoalPrefix(), mojo.getGoal()); writer.printf("| %s%n", mojo.getDescription()); writer.println(); } @@ -101,12 +80,11 @@ private void writeOverview(Plugin plugin) throws IOException { } private void documentMojo(Plugin plugin, Mojo mojo) throws IOException { - try (PrintWriter writer = new PrintWriter(new FileWriter(new File(this.outputDir, mojo.getGoal() + ".adoc")))) { - String sectionId = goalSectionId(mojo); - writer.println(); - writer.println(); + try (PrintWriter writer = new PrintWriter( + new FileWriter(new File(getOutputDir().getAsFile().get(), mojo.getGoal() + ".adoc")))) { + String sectionId = goalSectionId(mojo, true); writer.printf("[[%s]]%n", sectionId); - writer.printf("= `%s:%s`%n", plugin.getGoalPrefix(), mojo.getGoal()); + writer.printf("= `%s:%s`%n%n", plugin.getGoalPrefix(), mojo.getGoal()); writer.printf("`%s:%s:%s`%n", plugin.getGroupId(), plugin.getArtifactId(), plugin.getVersion()); writer.println(); writer.println(mojo.getDescription()); @@ -114,37 +92,43 @@ private void documentMojo(Plugin plugin, Mojo mojo) throws IOException { List requiredParameters = parameters.stream().filter(Parameter::isRequired).toList(); String detailsSectionId = sectionId + ".parameter-details"; if (!requiredParameters.isEmpty()) { + writer.println(); writer.println(); writer.println(); writer.printf("[[%s.required-parameters]]%n", sectionId); writer.println("== Required parameters"); + writer.println(); writeParametersTable(writer, detailsSectionId, requiredParameters); } List optionalParameters = parameters.stream() .filter((parameter) -> !parameter.isRequired()) .toList(); if (!optionalParameters.isEmpty()) { + writer.println(); writer.println(); writer.println(); writer.printf("[[%s.optional-parameters]]%n", sectionId); writer.println("== Optional parameters"); + writer.println(); writeParametersTable(writer, detailsSectionId, optionalParameters); } writer.println(); writer.println(); + writer.println(); writer.printf("[[%s]]%n", detailsSectionId); writer.println("== Parameter details"); + writer.println(); writeParameterDetails(writer, parameters, detailsSectionId); } } - private String goalSectionId(Mojo mojo) { - String goalSection = this.goalSections.get(mojo.getGoal()); + private String goalSectionId(Mojo mojo, boolean innerReference) { + String goalSection = getGoalSections().getting(mojo.getGoal()).get(); if (goalSection == null) { throw new IllegalStateException("Goal '" + mojo.getGoal() + "' has not be assigned to a section"); } String sectionId = goalSection + "." + mojo.getGoal() + "-goal"; - return sectionId; + return (!innerReference) ? goalSection + "#" + sectionId : sectionId; } private void writeParametersTable(PrintWriter writer, String detailsSectionId, List parameters) { @@ -154,7 +138,7 @@ private void writeParametersTable(PrintWriter writer, String detailsSectionId, L writer.println(); for (Parameter parameter : parameters) { String name = parameter.getName(); - writer.printf("| <<%s.%s,%s>>%n", detailsSectionId, parameterId(name), name); + writer.printf("| xref:#%s.%s[%s]%n", detailsSectionId, parameterId(name), name); writer.printf("| `%s`%n", typeNameToJavadocLink(shortTypeName(parameter.getType()), parameter.getType())); String defaultValue = parameter.getDefaultValue(); if (defaultValue != null) { @@ -236,10 +220,10 @@ private String typeNameToJavadocLink(String name) { private String typeNameToJavadocLink(String shortName, String name) { if (name.startsWith("org.springframework.boot.maven")) { - return "{spring-boot-docs}/maven-plugin/api/" + typeNameToJavadocPath(name) + ".html[" + shortName + "]"; + return "xref:maven-plugin:api/java/" + typeNameToJavadocPath(name) + ".html[" + shortName + "]"; } if (name.startsWith("org.springframework.boot")) { - return "{spring-boot-docs}/api/" + typeNameToJavadocPath(name) + ".html[" + shortName + "]"; + return "xref:api:java/" + typeNameToJavadocPath(name) + ".html[" + shortName + "]"; } return shortName; } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenExec.java b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenExec.java index fbbe5d651f6b..73ef436429ac 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenExec.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenExec.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,8 +24,12 @@ import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.JavaExec; +import org.gradle.api.tasks.PathSensitive; import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.TaskExecutionException; import org.gradle.process.internal.ExecException; @@ -37,29 +41,29 @@ * * @author Andy Wilkinson */ -public class MavenExec extends JavaExec { +public abstract class MavenExec extends JavaExec { private final Logger log = LoggerFactory.getLogger(MavenExec.class); - private File projectDir; - public MavenExec() { setClasspath(mavenConfiguration(getProject())); args("--batch-mode"); getMainClass().set("org.apache.maven.cli.MavenCli"); + getPom().set(getProjectDir().file("pom.xml")); } - public void setProjectDir(File projectDir) { - this.projectDir = projectDir; - getInputs().file(new File(projectDir, "pom.xml")) - .withPathSensitivity(PathSensitivity.RELATIVE) - .withPropertyName("pom"); - } + @Internal + public abstract DirectoryProperty getProjectDir(); + + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + abstract RegularFileProperty getPom(); @Override public void exec() { - workingDir(this.projectDir); - systemProperty("maven.multiModuleProjectDirectory", this.projectDir.getAbsolutePath()); + File workingDir = getProjectDir().getAsFile().get(); + workingDir(workingDir); + systemProperty("maven.multiModuleProjectDirectory", workingDir.getAbsolutePath()); try { Path logFile = Files.createTempFile(getName(), ".log"); try { @@ -97,9 +101,4 @@ private Configuration mavenConfiguration(Project project) { }); } - @Internal - public File getProjectDir() { - return this.projectDir; - } - } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java index 6301deb2b4b1..09eb5d333d45 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,6 +54,8 @@ import org.gradle.api.artifacts.result.ResolvedArtifactResult; import org.gradle.api.attributes.DocsType; import org.gradle.api.attributes.Usage; +import org.gradle.api.component.AdhocComponentWithVariants; +import org.gradle.api.component.SoftwareComponent; import org.gradle.api.file.CopySpec; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; @@ -65,6 +67,7 @@ import org.gradle.api.publish.PublishingExtension; import org.gradle.api.publish.maven.MavenPublication; import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; +import org.gradle.api.publish.tasks.GenerateModuleMetadata; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.JavaExec; @@ -82,11 +85,15 @@ import org.gradle.api.tasks.javadoc.Javadoc; import org.gradle.external.javadoc.StandardJavadocDocletOptions; import org.w3c.dom.Document; +import org.w3c.dom.Element; import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import org.springframework.boot.build.DeployedPlugin; import org.springframework.boot.build.MavenRepositoryPlugin; +import org.springframework.boot.build.optional.OptionalDependenciesPlugin; +import org.springframework.boot.build.test.DockerTestPlugin; import org.springframework.boot.build.test.IntegrationTestPlugin; import org.springframework.core.CollectionFactory; import org.springframework.util.Assert; @@ -115,6 +122,33 @@ public void apply(Project project) { addDocumentPluginGoalsTask(project, generatePluginDescriptorTask); addPrepareMavenBinariesTask(project); addExtractVersionPropertiesTask(project); + publishOptionalDependenciesInPom(project); + project.getTasks().withType(GenerateModuleMetadata.class).configureEach((task) -> task.setEnabled(false)); + } + + private void publishOptionalDependenciesInPom(Project project) { + project.getPlugins().withType(OptionalDependenciesPlugin.class, (optionalDependencies) -> { + SoftwareComponent component = project.getComponents().findByName("java"); + if (component instanceof AdhocComponentWithVariants componentWithVariants) { + componentWithVariants.addVariantsFromConfiguration( + project.getConfigurations().getByName(OptionalDependenciesPlugin.OPTIONAL_CONFIGURATION_NAME), + (variant) -> variant.mapToOptional()); + } + }); + MavenPublication publication = (MavenPublication) project.getExtensions() + .getByType(PublishingExtension.class) + .getPublications() + .getByName("maven"); + publication.getPom().withXml((xml) -> { + Element root = xml.asElement(); + NodeList children = root.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + if ("dependencyManagement".equals(child.getNodeName())) { + root.removeChild(child); + } + } + }); } private void configurePomPackaging(Project project) { @@ -134,15 +168,19 @@ private void addPopulateIntTestMavenRepositoryTask(Project project) { project.getObjects().named(DocsType.class, "maven-repository"))); RuntimeClasspathMavenRepository runtimeClasspathMavenRepository = project.getTasks() .create("runtimeClasspathMavenRepository", RuntimeClasspathMavenRepository.class); - runtimeClasspathMavenRepository.getOutputDirectory() + runtimeClasspathMavenRepository.getOutputDir() .set(new File(project.getBuildDir(), "runtime-classpath-repository")); project.getDependencies() .components((components) -> components.all(MavenRepositoryComponentMetadataRule.class)); - Sync task = project.getTasks().create("populateIntTestMavenRepository", Sync.class); - task.setDestinationDir(new File(project.getBuildDir(), "int-test-maven-repository")); + Sync task = project.getTasks().create("populateTestMavenRepository", Sync.class); + task.setDestinationDir(new File(project.getBuildDir(), "test-maven-repository")); task.with(copyIntTestMavenRepositoryFiles(project, runtimeClasspathMavenRepository)); task.dependsOn(project.getTasks().getByName(MavenRepositoryPlugin.PUBLISH_TO_PROJECT_REPOSITORY_TASK_NAME)); project.getTasks().getByName(IntegrationTestPlugin.INT_TEST_TASK_NAME).dependsOn(task); + project.getPlugins() + .withType(DockerTestPlugin.class) + .all((dockerTestPlugin) -> project.getTasks() + .named(DockerTestPlugin.DOCKER_TEST_TASK_NAME, (dockerTest) -> dockerTest.dependsOn(task))); } private CopySpec copyIntTestMavenRepositoryFiles(Project project, @@ -157,8 +195,8 @@ private CopySpec copyIntTestMavenRepositoryFiles(Project project, private void addDocumentPluginGoalsTask(Project project, MavenExec generatePluginDescriptorTask) { DocumentPluginGoals task = project.getTasks().create("documentPluginGoals", DocumentPluginGoals.class); File pluginXml = new File(generatePluginDescriptorTask.getOutputs().getFiles().getSingleFile(), "plugin.xml"); - task.setPluginXml(pluginXml); - task.setOutputDir(new File(project.getBuildDir(), "docs/generated/goals/")); + task.getPluginXml().set(pluginXml); + task.getOutputDir().set(new File(project.getBuildDir(), "generated/docs/maven-plugin-goals/")); task.dependsOn(generatePluginDescriptorTask); } @@ -172,7 +210,7 @@ private MavenExec addGenerateHelpMojoTask(Project project, Jar jarTask) { private MavenExec createGenerateHelpMojoTask(Project project, File helpMojoDir) { MavenExec task = project.getTasks().create("generateHelpMojo", MavenExec.class); - task.setProjectDir(helpMojoDir); + task.getProjectDir().set(helpMojoDir); task.args("org.apache.maven.plugins:maven-plugin-plugin:3.6.1:helpmojo"); task.getOutputs().dir(new File(helpMojoDir, "target/generated-sources/plugin")); return task; @@ -223,7 +261,7 @@ private FormatHelpMojoSource createFormatHelpMojoSource(Project project, MavenEx FormatHelpMojoSource formatHelpMojoSource = project.getTasks() .create("formatHelpMojoSource", FormatHelpMojoSource.class); formatHelpMojoSource.setGenerator(generateHelpMojoTask); - formatHelpMojoSource.setOutputDir(generatedHelpMojoDir); + formatHelpMojoSource.getOutputDir().set(generatedHelpMojoDir); return formatHelpMojoSource; } @@ -246,7 +284,7 @@ private MavenExec createGeneratePluginDescriptorTask(Project project, File maven .dir(new File(mavenDir, "target/classes/org")) .withPathSensitivity(PathSensitivity.RELATIVE) .withPropertyName("plugin classes"); - generatePluginDescriptor.setProjectDir(mavenDir); + generatePluginDescriptor.getProjectDir().set(mavenDir); return generatePluginDescriptor; } @@ -257,8 +295,9 @@ private void includeDescriptorInJar(Jar jar, JavaExec generatePluginDescriptorTa private void addPrepareMavenBinariesTask(Project project) { TaskProvider task = project.getTasks() - .register("prepareMavenBinaries", PrepareMavenBinaries.class, (prepareMavenBinaries) -> prepareMavenBinaries - .setOutputDir(new File(project.getBuildDir(), "maven-binaries"))); + .register("prepareMavenBinaries", PrepareMavenBinaries.class, + (prepareMavenBinaries) -> prepareMavenBinaries.getOutputDir() + .set(new File(project.getBuildDir(), "maven-binaries"))); project.getTasks() .getByName(IntegrationTestPlugin.INT_TEST_TASK_NAME) .getInputs() @@ -286,12 +325,10 @@ private void addExtractVersionPropertiesTask(Project project) { .map((dir) -> dir.file("extracted-versions.properties"))); } - public static class FormatHelpMojoSource extends DefaultTask { + public abstract static class FormatHelpMojoSource extends DefaultTask { private Task generator; - private File outputDir; - void setGenerator(Task generator) { this.generator = generator; getInputs().files(this.generator) @@ -300,13 +337,7 @@ void setGenerator(Task generator) { } @OutputDirectory - public File getOutputDir() { - return this.outputDir; - } - - void setOutputDir(File outputDir) { - this.outputDir = outputDir; - } + public abstract DirectoryProperty getOutputDir(); @TaskAction void syncAndFormat() { @@ -319,7 +350,7 @@ void syncAndFormat() { private void save(File output, FileEdit edit) { Path relativePath = output.toPath().relativize(edit.getFile().toPath()); - Path outputLocation = this.outputDir.toPath().resolve(relativePath); + Path outputLocation = getOutputDir().getAsFile().get().toPath().resolve(relativePath); try { Files.createDirectories(outputLocation.getParent()); Files.writeString(outputLocation, edit.getFormattedContent()); @@ -363,21 +394,16 @@ private void configureVariant(ComponentMetadataContext context, VariantMetadata } - public static class RuntimeClasspathMavenRepository extends DefaultTask { + public abstract static class RuntimeClasspathMavenRepository extends DefaultTask { private final Configuration runtimeClasspath; - private final DirectoryProperty outputDirectory; - public RuntimeClasspathMavenRepository() { this.runtimeClasspath = getProject().getConfigurations().getByName("runtimeClasspathWithMetadata"); - this.outputDirectory = getProject().getObjects().directoryProperty(); } @OutputDirectory - public DirectoryProperty getOutputDirectory() { - return this.outputDirectory; - } + public abstract DirectoryProperty getOutputDir(); @Classpath public Configuration getRuntimeClasspath() { @@ -391,7 +417,7 @@ public void createRepository() { String fileName = result.getFile() .getName() .replace(identifier.getVersion() + "-" + identifier.getVersion(), identifier.getVersion()); - File repositoryLocation = this.outputDirectory + File repositoryLocation = getOutputDir() .dir(identifier.getGroup().replace('.', '/') + "/" + identifier.getModule() + "/" + identifier.getVersion() + "/" + fileName) .get() @@ -410,16 +436,10 @@ public void createRepository() { } - public static class ExtractVersionProperties extends DefaultTask { - - private final RegularFileProperty destination; + public abstract static class ExtractVersionProperties extends DefaultTask { private FileCollection effectiveBoms; - public ExtractVersionProperties() { - this.destination = getProject().getObjects().fileProperty(); - } - @InputFiles @PathSensitive(PathSensitivity.RELATIVE) public FileCollection getEffectiveBoms() { @@ -431,9 +451,7 @@ public void setEffectiveBoms(FileCollection effectiveBoms) { } @OutputFile - public RegularFileProperty getDestination() { - return this.destination; - } + public abstract RegularFileProperty getDestination(); @TaskAction public void extractVersionProperties() { @@ -443,7 +461,7 @@ public void extractVersionProperties() { } private void writeProperties(Properties versions) { - File outputFile = this.destination.getAsFile().get(); + File outputFile = getDestination().getAsFile().get(); outputFile.getParentFile().mkdirs(); try (Writer writer = new FileWriter(outputFile)) { versions.store(writer, null); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/PrepareMavenBinaries.java b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/PrepareMavenBinaries.java index 3ebc30a4e95e..69c741739d8b 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/PrepareMavenBinaries.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/PrepareMavenBinaries.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,11 @@ package org.springframework.boot.build.mavenplugin; -import java.io.File; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Set; - import org.gradle.api.DefaultTask; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; @@ -33,38 +30,22 @@ * * @author Andy Wilkinson */ -public class PrepareMavenBinaries extends DefaultTask { - - private final Set versions = new LinkedHashSet<>(); - - private File outputDir; +public abstract class PrepareMavenBinaries extends DefaultTask { @OutputDirectory - public File getOutputDir() { - return this.outputDir; - } - - public void setOutputDir(File outputDir) { - this.outputDir = outputDir; - } + public abstract DirectoryProperty getOutputDir(); @Input - public Set getVersions() { - return this.versions; - } - - public void versions(String... versions) { - this.versions.addAll(Arrays.asList(versions)); - } + public abstract SetProperty getVersions(); @TaskAction public void prepareBinaries() { - for (String version : this.versions) { + for (String version : getVersions().get()) { Configuration configuration = getProject().getConfigurations() .detachedConfiguration( getProject().getDependencies().create("org.apache.maven:apache-maven:" + version + ":bin@zip")); getProject() - .copy((copy) -> copy.into(this.outputDir).from(getProject().zipTree(configuration.getSingleFile()))); + .copy((copy) -> copy.into(getOutputDir()).from(getProject().zipTree(configuration.getSingleFile()))); } } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/starters/DocumentStarters.java b/buildSrc/src/main/java/org/springframework/boot/build/starters/DocumentStarters.java index 669eeb40a0ba..fabbd7a93aad 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/starters/DocumentStarters.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/starters/DocumentStarters.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ import org.gradle.api.DefaultTask; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.OutputDirectory; @@ -47,12 +48,10 @@ * * @author Andy Wilkinson */ -public class DocumentStarters extends DefaultTask { +public abstract class DocumentStarters extends DefaultTask { private final Configuration starters; - private File outputDir; - public DocumentStarters() { this.starters = getProject().getConfigurations().create("starters"); getProject().getGradle().projectsEvaluated((gradle) -> { @@ -68,13 +67,7 @@ public DocumentStarters() { } @OutputDirectory - public File getOutputDir() { - return this.outputDir; - } - - public void setOutputDir(File outputDir) { - this.outputDir = outputDir; - } + public abstract DirectoryProperty getOutputDir(); @InputFiles @PathSensitive(PathSensitivity.RELATIVE) @@ -106,7 +99,7 @@ private Starter loadStarter(File metadata) { } private void writeTable(String name, Stream starters) { - File output = new File(this.outputDir, name + ".adoc"); + File output = new File(getOutputDir().getAsFile().get(), name + ".adoc"); output.getParentFile().mkdirs(); try (PrintWriter writer = new PrintWriter(new FileWriter(output))) { writer.println("|==="); @@ -128,7 +121,7 @@ private String postProcessDescription(String description) { } private String addStarterCrossLinks(String input) { - return input.replaceAll("(spring-boot-starter[A-Za-z-]*)", "<<$1,`$1`>>"); + return input.replaceAll("(spring-boot-starter[A-Za-z-]*)", "xref:#$1[`$1`]"); } private static final class Starter implements Comparable { diff --git a/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestBuildService.java b/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestBuildService.java new file mode 100644 index 000000000000..2f70a735513c --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestBuildService.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build.test; + +import org.gradle.api.Project; +import org.gradle.api.provider.Provider; +import org.gradle.api.services.BuildService; +import org.gradle.api.services.BuildServiceParameters; + +/** + * Build service for Docker-based tests. Configured to only allow serial execution, + * thereby ensuring that Docker-based tests do not run in parallel. + * + * @author Andy Wilkinson + */ +abstract class DockerTestBuildService implements BuildService { + + static Provider registerIfNecessary(Project project) { + return project.getGradle() + .getSharedServices() + .registerIfAbsent("dockerTest", DockerTestBuildService.class, (spec) -> spec.getMaxParallelUsages().set(1)); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestPlugin.java new file mode 100644 index 000000000000..017ca001a43b --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestPlugin.java @@ -0,0 +1,112 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build.test; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.provider.Provider; +import org.gradle.api.services.BuildService; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.testing.Test; +import org.gradle.language.base.plugins.LifecycleBasePlugin; +import org.gradle.plugins.ide.eclipse.EclipsePlugin; +import org.gradle.plugins.ide.eclipse.model.EclipseModel; + +/** + * Plugin for Docker-based tests. Creates a {@link SourceSet source set}, {@link Test + * test} task, and {@link BuildService shared service} named {@code dockerTest}. The build + * service is configured to only allow serial usage and the {@code dockerTest} task is + * configured to use the build service. In a parallel build, this ensures that only a + * single {@code dockerTest} task can run at any given time. + * + * @author Andy Wilkinson + */ +public class DockerTestPlugin implements Plugin { + + /** + * Name of the {@code dockerTest} task. + */ + public static String DOCKER_TEST_TASK_NAME = "dockerTest"; + + /** + * Name of the {@code dockerTest} source set. + */ + public static String DOCKER_TEST_SOURCE_SET_NAME = "dockerTest"; + + /** + * Name of the {@code dockerTest} shared service. + */ + public static String DOCKER_TEST_SERVICE_NAME = "dockerTest"; + + @Override + public void apply(Project project) { + project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> configureDockerTesting(project)); + } + + private void configureDockerTesting(Project project) { + Provider buildService = DockerTestBuildService.registerIfNecessary(project); + SourceSet dockerTestSourceSet = createSourceSet(project); + Provider dockerTest = createTestTask(project, dockerTestSourceSet, buildService); + project.getTasks().getByName(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(dockerTest); + project.getPlugins().withType(EclipsePlugin.class, (eclipsePlugin) -> { + EclipseModel eclipse = project.getExtensions().getByType(EclipseModel.class); + eclipse.classpath((classpath) -> classpath.getPlusConfigurations() + .add(project.getConfigurations() + .getByName(dockerTestSourceSet.getRuntimeClasspathConfigurationName()))); + }); + } + + private SourceSet createSourceSet(Project project) { + SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets(); + SourceSet dockerTestSourceSet = sourceSets.create(DOCKER_TEST_SOURCE_SET_NAME); + SourceSet main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); + SourceSet test = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME); + dockerTestSourceSet.setCompileClasspath(dockerTestSourceSet.getCompileClasspath() + .plus(main.getOutput()) + .plus(main.getCompileClasspath()) + .plus(test.getOutput())); + dockerTestSourceSet.setRuntimeClasspath(dockerTestSourceSet.getRuntimeClasspath() + .plus(main.getOutput()) + .plus(main.getRuntimeClasspath()) + .plus(test.getOutput())); + project.getPlugins().withType(IntegrationTestPlugin.class, (integrationTestPlugin) -> { + SourceSet intTest = sourceSets.getByName(IntegrationTestPlugin.INT_TEST_SOURCE_SET_NAME); + dockerTestSourceSet + .setCompileClasspath(dockerTestSourceSet.getCompileClasspath().plus(intTest.getOutput())); + dockerTestSourceSet + .setRuntimeClasspath(dockerTestSourceSet.getRuntimeClasspath().plus(intTest.getOutput())); + }); + return dockerTestSourceSet; + } + + private Provider createTestTask(Project project, SourceSet dockerTestSourceSet, + Provider buildService) { + Provider dockerTest = project.getTasks().register(DOCKER_TEST_TASK_NAME, Test.class, (task) -> { + task.usesService(buildService); + task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); + task.setDescription("Runs Docker-based tests."); + task.setTestClassesDirs(dockerTestSourceSet.getOutput().getClassesDirs()); + task.setClasspath(dockerTestSourceSet.getRuntimeClasspath()); + task.shouldRunAfter(JavaPlugin.TEST_TASK_NAME); + }); + return dockerTest; + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/DocumentTestSlices.java b/buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/DocumentTestSlices.java index 012790904704..939f425ab4db 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/DocumentTestSlices.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/DocumentTestSlices.java @@ -32,6 +32,7 @@ import org.gradle.api.DefaultTask; import org.gradle.api.Task; import org.gradle.api.file.FileCollection; +import org.gradle.api.file.RegularFileProperty; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.PathSensitive; @@ -46,12 +47,10 @@ * * @author Andy Wilkinson */ -public class DocumentTestSlices extends DefaultTask { +public abstract class DocumentTestSlices extends DefaultTask { private FileCollection testSlices; - private File outputFile; - @InputFiles @PathSensitive(PathSensitivity.RELATIVE) public FileCollection getTestSlices() { @@ -63,13 +62,7 @@ public void setTestSlices(FileCollection testSlices) { } @OutputFile - public File getOutputFile() { - return this.outputFile; - } - - public void setOutputFile(File outputFile) { - this.outputFile = outputFile; - } + public abstract RegularFileProperty getOutputFile(); @TaskAction void documentTestSlices() throws IOException { @@ -94,8 +87,9 @@ private Set readTestSlices() throws IOException { } private void writeTable(Set testSlices) throws IOException { - this.outputFile.getParentFile().mkdirs(); - try (PrintWriter writer = new PrintWriter(new FileWriter(this.outputFile))) { + File outputFile = getOutputFile().getAsFile().get(); + outputFile.getParentFile().mkdirs(); + try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) { writer.println("[cols=\"d,a\"]"); writer.println("|==="); writer.println("| Test slice | Imported auto-configuration"); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/TestSliceMetadata.java b/buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/TestSliceMetadata.java index 3c4c8a478d75..60ba288ec4c7 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/TestSliceMetadata.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/TestSliceMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,6 @@ import java.util.Properties; import java.util.SortedSet; import java.util.TreeSet; -import java.util.concurrent.Callable; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -41,7 +40,12 @@ import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.FileCollection; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.PathSensitive; import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskAction; @@ -59,44 +63,57 @@ * * @author Andy Wilkinson */ -public class TestSliceMetadata extends DefaultTask { +public abstract class TestSliceMetadata extends DefaultTask { - private SourceSet sourceSet; + private FileCollection classpath; - private File outputFile; + private FileCollection importsFiles; + + private FileCollection classesDirs; public TestSliceMetadata() { - getInputs().dir((Callable) () -> this.sourceSet.getOutput().getResourcesDir()) - .withPathSensitivity(PathSensitivity.RELATIVE) - .withPropertyName("resources"); - dependsOn((Callable) () -> this.sourceSet.getProcessResourcesTaskName()); - getInputs().files((Callable) () -> this.sourceSet.getOutput().getClassesDirs()) - .withPathSensitivity(PathSensitivity.RELATIVE) - .withPropertyName("classes"); + Configuration testSliceMetadata = getProject().getConfigurations().maybeCreate("testSliceMetadata"); + getProject().afterEvaluate((evaluated) -> evaluated.getArtifacts() + .add(testSliceMetadata.getName(), getOutputFile(), (artifact) -> artifact.builtBy(this))); } public void setSourceSet(SourceSet sourceSet) { - this.sourceSet = sourceSet; + this.classpath = sourceSet.getRuntimeClasspath(); + this.importsFiles = getProject().fileTree(new File(sourceSet.getOutput().getResourcesDir(), "META-INF/spring"), + (tree) -> tree.filter((file) -> file.getName().endsWith(".imports"))); + getSpringFactories().set(new File(sourceSet.getOutput().getResourcesDir(), "META-INF/spring.factories")); + this.classesDirs = sourceSet.getOutput().getClassesDirs(); } @OutputFile - public File getOutputFile() { - return this.outputFile; + public abstract RegularFileProperty getOutputFile(); + + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + abstract RegularFileProperty getSpringFactories(); + + @Classpath + FileCollection getClasspath() { + return this.classpath; } - public void setOutputFile(File outputFile) { - this.outputFile = outputFile; - Configuration testSliceMetadata = getProject().getConfigurations().maybeCreate("testSliceMetadata"); - getProject().getArtifacts() - .add(testSliceMetadata.getName(), getProject().provider((Callable) this::getOutputFile), - (artifact) -> artifact.builtBy(this)); + @InputFiles + @PathSensitive(PathSensitivity.RELATIVE) + FileCollection getImportFiles() { + return this.importsFiles; + } + + @Classpath + FileCollection getClassesDirs() { + return this.classesDirs; } @TaskAction void documentTestSlices() throws IOException { Properties testSlices = readTestSlices(); - getOutputFile().getParentFile().mkdirs(); - try (FileWriter writer = new FileWriter(getOutputFile())) { + File outputFile = getOutputFile().getAsFile().get(); + outputFile.getParentFile().mkdirs(); + try (FileWriter writer = new FileWriter(outputFile)) { testSlices.store(writer, null); } } @@ -104,15 +121,11 @@ void documentTestSlices() throws IOException { private Properties readTestSlices() throws IOException { Properties testSlices = CollectionFactory.createSortedProperties(true); try (URLClassLoader classLoader = new URLClassLoader( - StreamSupport.stream(this.sourceSet.getRuntimeClasspath().spliterator(), false) - .map(this::toURL) - .toArray(URL[]::new))) { + StreamSupport.stream(this.classpath.spliterator(), false).map(this::toURL).toArray(URL[]::new))) { MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(classLoader); - Properties springFactories = readSpringFactories( - new File(this.sourceSet.getOutput().getResourcesDir(), "META-INF/spring.factories")); - readTestSlicesDirectory(springFactories, - new File(this.sourceSet.getOutput().getResourcesDir(), "META-INF/spring/")); - for (File classesDir : this.sourceSet.getOutput().getClassesDirs()) { + Properties springFactories = readSpringFactories(getSpringFactories().getAsFile().get()); + readImportsFiles(springFactories, this.importsFiles); + for (File classesDir : this.classesDirs) { addTestSlices(testSlices, classesDir, metadataReaderFactory, springFactories); } } @@ -120,18 +133,14 @@ private Properties readTestSlices() throws IOException { } /** - * Reads files from the given directory and puts them in springFactories. The key is - * the file name, the value is the file contents, split by line, delimited with comma. - * This is done to mimic the spring.factories structure. + * Reads the given imports files and puts them in springFactories. The key is the file + * name, the value is the file contents, split by line, delimited with a comma. This + * is done to mimic the spring.factories structure. * @param springFactories spring.factories parsed as properties - * @param directory directory to scan + * @param importsFiles the imports files to read */ - private void readTestSlicesDirectory(Properties springFactories, File directory) { - File[] files = directory.listFiles((dir, name) -> name.endsWith(".imports")); - if (files == null) { - return; - } - for (File file : files) { + private void readImportsFiles(Properties springFactories, FileCollection importsFiles) { + for (File file : importsFiles.getFiles()) { try { List lines = removeComments(Files.readAllLines(file.toPath())); String fileNameWithoutExtension = file.getName() diff --git a/buildSrc/src/main/java/org/springframework/boot/build/toolchain/ToolchainPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/toolchain/ToolchainPlugin.java index 82abe1cd65d1..b5e602d7fa08 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/toolchain/ToolchainPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/toolchain/ToolchainPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,8 +71,7 @@ private void disableToolchainTasks(Project project) { } private void configureTestToolchain(Project project, ToolchainExtension toolchain) { - List jvmArgs = new ArrayList<>(); - jvmArgs.addAll(toolchain.getTestJvmArgs().getOrElse(Collections.emptyList())); + List jvmArgs = new ArrayList<>(toolchain.getTestJvmArgs().getOrElse(Collections.emptyList())); project.getTasks().withType(Test.class, (test) -> test.jvmArgs(jvmArgs)); } diff --git a/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties b/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties new file mode 100644 index 000000000000..023a3473c5d3 --- /dev/null +++ b/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties @@ -0,0 +1,75 @@ +# === INCLUDE-CODE LOCATIONS === + +include-java=ROOT:example$java/org/springframework/boot/docs +include-kotlin= ROOT:example$kotlin/org/springframework/boot/docs + +# === URLs === + +url-ant-docs=https://ant.apache.org/manual +url-buildpacks-docs=https://buildpacks.io/docs +url-dynatrace-docs=https://docs.dynatrace.com/docs +url-dynatrace-docs-shortlink={url-dynatrace-docs}/shortlink +url-github-raw=https://raw.githubusercontent.com/{github-repo}/{github-ref} +url-github-issues=https://github.com/{github-repo}/issues +url-github-wiki=https://github.com/{github-repo}/wiki +url-github=https://github.com/{github-repo} +url-graal-docs=https://www.graalvm.org/{version-graal}/reference-manual +url-graal-docs-native-image={url-graal-docs}/native-image +url-gradle-docs=https://docs.gradle.org/current/userguide +url-gradle-docs-application-plugin={url-gradle-docs}/application_plugin.html +url-gradle-docs-groovy-plugin={url-gradle-docs}/groovy_plugin.html +url-gradle-docs-java-plugin={url-gradle-docs}/java_plugin.html +url-gradle-docs-war-plugin={url-gradle-docs}/war_plugin.html +url-gradle-dsl=https://docs.gradle.org/current/dsl +url-gradle-javadoc=https://docs.gradle.org/current/javadoc +url-kotlin-docs-kotlin-plugin={url-kotlin-docs}/using-gradle.html +url-micrometer-docs-concepts={url-micrometer-docs}/concepts +url-micrometer-docs-implementations={url-micrometer-docs}/implementations +url-download-liberica-nik=https://bell-sw.com/pages/downloads/native-image-kit/#/nik-22-17 +url-native-build-tools-docs=https://graalvm.github.io/native-build-tools/{version-native-build-tools} +url-native-build-tools-docs-gradle-plugin={url-native-build-tools-docs}/gradle-plugin.html +url-native-build-tools-docs-maven-plugin={url-native-build-tools-docs}/maven-plugin.html +url-paketo-docs=https://paketo.io/docs +url-paketo-docs-java-buildpack={url-paketo-docs}/buildpacks/language-family-buildpacks/java +url-spring-boot-for-apache-geode-docs=https://docs.spring.io/spring-boot-data-geode-build/2.0.x/reference/html5 +url-spring-boot-for-apache-geode-site=https://github.com/spring-projects/spring-boot-data-geode +url-spring-data-cassandra-site=https://spring.io/projects/spring-data-cassandra +url-spring-data-commons-javadoc=https://docs.spring.io/spring-data/commons/docs/{version-spring-data-commons}/api +url-spring-data-couchbase-docs=https://docs.spring.io/spring-data/couchbase/reference/{version-spring-data-couchbase} +url-spring-data-couchbase-site=https://spring.io/projects/spring-data-couchbase +url-spring-data-elasticsearch-docs=https://docs.spring.io/spring-data/elasticsearch/reference/{version-spring-data-elasticsearch} +url-spring-data-elasticsearch-site=https://spring.io/projects/spring-data-elasticsearch +url-spring-data-envers-site=https://spring.io/projects/spring-data-envers +url-spring-data-gemfire-site=https://spring.io/projects/spring-data-gemfire +url-spring-data-geode-site=https://spring.io/projects/spring-data-geode +url-spring-data-jdbc-docs=https://docs.spring.io/spring-data/relational/reference/{version-spring-data-jdbc} +url-spring-data-jpa-javadoc=https://docs.spring.io/spring-data/jpa/docs/{version-spring-data-jpa}/api +url-spring-data-jpa-site=https://spring.io/projects/spring-jpa +url-spring-data-jpa-docs=https://docs.spring.io/spring-data/jpa/reference/{version-spring-data-jpa} +url-spring-data-ldap-site=https://spring.io/projects/spring-data-ldap +url-spring-data-mongodb-javadoc=https://docs.spring.io/spring-data/mongodb/docs/{version-spring-data-mongodb}/api +url-spring-data-mongodb-site=https://spring.io/projects/spring-data-mongodb +url-spring-data-mongodb-docs=https://docs.spring.io/spring-data/mongodb/reference/{version-spring-data-mongodb} +url-spring-data-neo4j-docs=https://docs.spring.io/spring-data/neo4j/reference/{version-spring-data-neo4j} +url-spring-data-neo4j-site=https://spring.io/projects/spring-data-neo4j +url-spring-data-r2dbc-javadoc=https://docs.spring.io/spring-data/r2dbc/docs/{version-spring-data-r2dbc}/api +url-spring-data-r2dbc-docs=https://docs.spring.io/spring-data/relational/reference/{version-spring-data-r2dbc} +url-spring-data-redis-site=https://spring.io/projects/spring-data-redis +url-spring-data-rest-javadoc=https://docs.spring.io/spring-data/rest/docs/{version-spring-data-rest}/api +url-spring-data-site=https://spring.io/projects/spring-data + +# === API References === + +apiref-gradle-plugin-boot-build-image=xref:gradle-plugin:api/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.html +apiref-gradle-plugin-boot-jar=xref:gradle-plugin:api/java/org/springframework/boot/gradle/tasks/bundling/BootJar.html +apiref-gradle-plugin-boot-run=xref:gradle-plugin:api/java/org/springframework/boot/gradle/tasks/run/BootRun.html +apiref-gradle-plugin-boot-war=xref:gradle-plugin:api/java/org/springframework/boot/gradle/tasks/bundling/BootWar.html +apiref-gradle-plugin-boot-build-info=xref:gradle-plugin:api/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfo.html +apiref-openjdk=https://docs.oracle.com/en/java/javase/17/docs/api + +# === Code Links === + +code-spring-boot=https://github.com/{github-repo}/tree/{github-ref} +code-spring-boot-autoconfigure-src={code-spring-boot}/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure +code-spring-boot-latest=https://github.com/{github-repo}/tree/main + diff --git a/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-playbook-template.yml b/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-playbook-template.yml new file mode 100644 index 000000000000..6960bc197928 --- /dev/null +++ b/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-playbook-template.yml @@ -0,0 +1,24 @@ +antora: + extensions: +site: + title: Spring Boot +content: + sources: [] +asciidoc: + sourcemap: true + attributes: + chomp: all + hide-uri-scheme: '@' + page-pagination: '' + page-stackoverflow-url: https://stackoverflow.com/tags/spring-boot + tabs-sync-option: '@' + extensions: + - '@asciidoctor/tabs' + - '@springio/asciidoctor-extensions' + - '@springio/asciidoctor-extensions/configuration-properties-extension' + - '@springio/asciidoctor-extensions/section-ids-extension' +urls: + latest_version_segment: '' +runtime: + log: + failure_level: warn diff --git a/buildSrc/src/test/java/org/springframework/boot/build/ConventionsPluginTests.java b/buildSrc/src/test/java/org/springframework/boot/build/ConventionsPluginTests.java index 15dda0d9336f..539cb3781795 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/ConventionsPluginTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/ConventionsPluginTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java b/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java new file mode 100644 index 000000000000..c197e3cefd40 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java @@ -0,0 +1,172 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build.antora; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.build.bom.Library; +import org.springframework.boot.build.bom.Library.Group; +import org.springframework.boot.build.bom.Library.LibraryVersion; +import org.springframework.boot.build.bom.Library.ProhibitedVersion; +import org.springframework.boot.build.bom.Library.VersionAlignment; +import org.springframework.boot.build.bom.bomr.version.DependencyVersion; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AntoraAsciidocAttributes}. + * + * @author Phillip Webb + */ +class AntoraAsciidocAttributesTests { + + @Test + void githubRefWhenReleasedVersionIsTag() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, null, + mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("github-ref", "v1.2.3"); + } + + @Test + void githubRefWhenLatestSnapshotVersionIsMainBranch() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, null, + mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("github-ref", "main"); + } + + @Test + void githubRefWhenOlderSnapshotVersionIsBranch() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", false, null, + mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("github-ref", "1.2.x"); + } + + @Test + void githubRefWhenOlderSnapshotHotFixVersionIsBranch() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3.1-SNAPSHOT", false, null, + mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("github-ref", "1.2.3.x"); + } + + @Test + void versionReferenceFromLibrary() { + Library library = mockLibrary(Collections.emptyMap()); + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3.1-SNAPSHOT", false, List.of(library), + mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("version-spring-framework", "1.2.3"); + } + + @Test + void versionReferenceFromSpringDataDependencyVersion() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, null, + mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("version-spring-data-mongodb", "1.2.3"); + } + + @Test + void versionNativeBuildTools() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, null, + mockDependencyVersions(), Map.of("nativeBuildToolsVersion", "3.4.5")); + assertThat(attributes.get()).containsEntry("version-native-build-tools", "3.4.5"); + } + + @Test + void urlArtifactRepositoryWhenRelease() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, null, + mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("url-artifact-repository", "https://repo.maven.apache.org/maven2"); + } + + @Test + void urlArtifactRepositoryWhenMilestone() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-M1", true, null, + mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("url-artifact-repository", "https://repo.spring.io/milestone"); + } + + @Test + void urlArtifactRepositoryWhenSnapshot() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, null, + mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("url-artifact-repository", "https://repo.spring.io/snapshot"); + } + + @Test + void urlLinksFromLibrary() { + Map> links = new LinkedHashMap<>(); + links.put("site", (version) -> "https://example.com/site/" + version); + links.put("docs", (version) -> "https://example.com/docs/" + version); + Library library = mockLibrary(links); + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3.1-SNAPSHOT", false, List.of(library), + mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("url-spring-framework-site", "https://example.com/site/1.2.3") + .containsEntry("url-spring-framework-docs", "https://example.com/docs/1.2.3"); + } + + @Test + void linksFromProperties() { + Map attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, null, + mockDependencyVersions(), null) + .get(); + assertThat(attributes).containsEntry("include-java", "ROOT:example$java/org/springframework/boot/docs"); + assertThat(attributes).containsEntry("url-spring-data-cassandra-site", + "https://spring.io/projects/spring-data-cassandra"); + List keys = new ArrayList<>(attributes.keySet()); + assertThat(keys.indexOf("include-java")).isLessThan(keys.indexOf("code-spring-boot-latest")); + } + + private Library mockLibrary(Map> links) { + String name = "Spring Framework"; + String calendarName = null; + LibraryVersion version = new LibraryVersion(DependencyVersion.parse("1.2.3")); + List groups = Collections.emptyList(); + List prohibitedVersion = Collections.emptyList(); + boolean considerSnapshots = false; + VersionAlignment versionAlignment = null; + String alignsWithBom = null; + String linkRootName = null; + Library library = new Library(name, calendarName, version, groups, prohibitedVersion, considerSnapshots, + versionAlignment, alignsWithBom, linkRootName, links); + return library; + } + + private Map mockDependencyVersions() { + Map versions = new LinkedHashMap<>(); + addMockSpringDataVersion(versions, "spring-data-commons"); + addMockSpringDataVersion(versions, "spring-data-couchbase"); + addMockSpringDataVersion(versions, "spring-data-elasticsearch"); + addMockSpringDataVersion(versions, "spring-data-jdbc"); + addMockSpringDataVersion(versions, "spring-data-jpa"); + addMockSpringDataVersion(versions, "spring-data-mongodb"); + addMockSpringDataVersion(versions, "spring-data-neo4j"); + addMockSpringDataVersion(versions, "spring-data-r2dbc"); + addMockSpringDataVersion(versions, "spring-data-rest-core"); + return versions; + } + + private void addMockSpringDataVersion(Map versions, String artifactId) { + versions.put("org.springframework.data:" + artifactId, "1.2.3"); + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/antora/GenerateAntoraPlaybookTests.java b/buildSrc/src/test/java/org/springframework/boot/build/antora/GenerateAntoraPlaybookTests.java new file mode 100644 index 000000000000..4bcaed2e2748 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/antora/GenerateAntoraPlaybookTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build.antora; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +import org.gradle.api.Project; +import org.gradle.testfixtures.ProjectBuilder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import org.springframework.util.function.ThrowingConsumer; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link GenerateAntoraPlaybook}. + * + * @author Phillip Webb + */ +class GenerateAntoraPlaybookTests { + + @TempDir + File temp; + + @Test + void writePlaybookGeneratesExpectedContent() throws Exception { + writePlaybookYml((task) -> { + task.getXrefStubs().addAll("appendix:.*", "api:.*", "reference:.*"); + task.getAlwaysInclude().set(Map.of("name", "test", "classifier", "local-aggregate-content")); + }); + String actual = Files.readString(this.temp.toPath() + .resolve("rootproject/project/build/generated/docs/antora-playbook/antora-playbook.yml")); + String expected = Files + .readString(Path.of("src/test/resources/org/springframework/boot/build/antora/expected-playbook.yml")); + System.out.println(actual); + assertThat(actual.replace('\\', '/')).isEqualToNormalizingNewlines(expected.replace('\\', '/')); + } + + private void writePlaybookYml(ThrowingConsumer customizer) throws Exception { + File rootProjectDir = new File(this.temp, "rootproject").getCanonicalFile(); + rootProjectDir.mkdirs(); + Project rootProject = ProjectBuilder.builder().withProjectDir(rootProjectDir).build(); + File projectDir = new File(rootProjectDir, "project"); + projectDir.mkdirs(); + Project project = ProjectBuilder.builder().withProjectDir(projectDir).withParent(rootProject).build(); + GenerateAntoraPlaybook task = project.getTasks().create("generateAntoraPlaybook", GenerateAntoraPlaybook.class); + customizer.accept(task); + task.writePlaybookYml(); + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java index 1294d6192975..11f15a8ade9c 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ * Tests for {@link ArchitectureCheck}. * * @author Andy Wilkinson + * @author Scott Frederick */ class ArchitectureCheckTests { @@ -121,6 +122,22 @@ void whenBeanFactoryPostProcessorBeanMethodIsStaticAndHasNoParametersTaskSucceed }); } + @Test + void whenClassLoadsResourceUsingResourceUtilsTaskFailsAndWritesReport() throws Exception { + prepareTask("resources/loads", (architectureCheck) -> { + assertThatExceptionOfType(GradleException.class).isThrownBy(architectureCheck::checkArchitecture); + assertThat(failureReport(architectureCheck)).isNotEmpty(); + }); + } + + @Test + void whenClassUsesResourceUtilsWithoutLoadingResourcesTaskSucceedsAndWritesAnEmptyReport() throws Exception { + prepareTask("resources/noloads", (architectureCheck) -> { + architectureCheck.checkArchitecture(); + assertThat(failureReport(architectureCheck)).isEmpty(); + }); + } + private void prepareTask(String classes, Callback callback) throws Exception { File projectDir = new File(this.temp, "project"); projectDir.mkdirs(); @@ -136,7 +153,6 @@ private void copyClasses(String name, File projectDir) throws IOException { Resource root = resolver.getResource("classpath:org/springframework/boot/build/architecture/" + name); FileSystemUtils.copyRecursively(root.getFile(), new File(projectDir, "classes/org/springframework/boot/build/architecture/" + name)); - } private interface Callback { diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/resources/loads/ResourceUtilsResourceLoader.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/resources/loads/ResourceUtilsResourceLoader.java new file mode 100644 index 000000000000..ce5ff3f61bd5 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/resources/loads/ResourceUtilsResourceLoader.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build.architecture.resources.loads; + +import java.io.FileNotFoundException; + +import org.springframework.util.ResourceUtils; + +public class ResourceUtilsResourceLoader { + + void getResource() throws FileNotFoundException { + ResourceUtils.getURL("gradle.properties"); + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/resources/noloads/ResourceUtilsWithoutLoading.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/resources/noloads/ResourceUtilsWithoutLoading.java new file mode 100644 index 000000000000..98d41edad5d2 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/resources/noloads/ResourceUtilsWithoutLoading.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build.architecture.resources.noloads; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.springframework.util.ResourceUtils; + +public class ResourceUtilsWithoutLoading { + + void inspectResourceLocation() throws MalformedURLException { + ResourceUtils.isUrl("gradle.properties"); + ResourceUtils.isFileURL(new URL("gradle.properties")); + "test".startsWith(ResourceUtils.FILE_URL_PREFIX); + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/bom/LibraryTests.java b/buildSrc/src/test/java/org/springframework/boot/build/bom/LibraryTests.java new file mode 100644 index 000000000000..e3af38dc17a4 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/bom/LibraryTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build.bom; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.build.bom.Library.Group; +import org.springframework.boot.build.bom.Library.LibraryVersion; +import org.springframework.boot.build.bom.Library.ProhibitedVersion; +import org.springframework.boot.build.bom.Library.VersionAlignment; +import org.springframework.boot.build.bom.bomr.version.DependencyVersion; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link Library}. + * + * @author Phillip Webb + */ +class LibraryTests { + + @Test + void getLinkRootNameWhenNoneSpecified() { + String name = "Spring Framework"; + String calendarName = null; + LibraryVersion version = new LibraryVersion(DependencyVersion.parse("1.2.3")); + List groups = Collections.emptyList(); + List prohibitedVersion = Collections.emptyList(); + boolean considerSnapshots = false; + VersionAlignment versionAlignment = null; + String alignsWithBom = null; + String linkRootName = null; + Map> links = Collections.emptyMap(); + Library library = new Library(name, calendarName, version, groups, prohibitedVersion, considerSnapshots, + versionAlignment, alignsWithBom, linkRootName, links); + assertThat(library.getLinkRootName()).isEqualTo("spring-framework"); + } + + @Test + void getLinkRootNameWhenSpecified() { + String name = "Spring Data BOM"; + String calendarName = null; + LibraryVersion version = new LibraryVersion(DependencyVersion.parse("1.2.3")); + List groups = Collections.emptyList(); + List prohibitedVersion = Collections.emptyList(); + boolean considerSnapshots = false; + VersionAlignment versionAlignment = null; + String alignsWithBom = null; + String linkRootName = "spring-data"; + Map> links = Collections.emptyMap(); + Library library = new Library(name, calendarName, version, groups, prohibitedVersion, considerSnapshots, + versionAlignment, alignsWithBom, linkRootName, links); + assertThat(library.getLinkRootName()).isEqualTo("spring-data"); + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeApplicatorTests.java b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeApplicatorTests.java index a074ca858ae0..b83742ad3691 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeApplicatorTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeApplicatorTests.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; +import java.util.Collections; import java.util.Properties; import org.junit.jupiter.api.Test; @@ -51,9 +52,11 @@ void whenUpgradeIsAppliedToLibraryWithVersionThenBomIsUpdated() throws IOExcepti String originalContents = Files.readString(bom.toPath()); File gradleProperties = new File(this.temp, "gradle.properties"); FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties); - new UpgradeApplicator(bom.toPath(), gradleProperties.toPath()) - .apply(new Upgrade(new Library("ActiveMQ", null, new LibraryVersion(DependencyVersion.parse("5.15.11")), - null, null, false, null, null), DependencyVersion.parse("5.16"))); + new UpgradeApplicator(bom.toPath(), gradleProperties.toPath()).apply( + new Upgrade( + new Library("ActiveMQ", null, new LibraryVersion(DependencyVersion.parse("5.15.11")), null, + null, false, null, null, null, Collections.emptyMap()), + DependencyVersion.parse("5.16"))); String bomContents = Files.readString(bom.toPath()); assertThat(bomContents).hasSize(originalContents.length() - 3); } @@ -66,7 +69,7 @@ void whenUpgradeIsAppliedToLibraryWithVersionPropertyThenGradlePropertiesIsUpdat FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties); new UpgradeApplicator(bom.toPath(), gradleProperties.toPath()) .apply(new Upgrade(new Library("Kotlin", null, new LibraryVersion(DependencyVersion.parse("1.3.70")), null, - null, false, null, null), DependencyVersion.parse("1.4"))); + null, false, null, null, null, Collections.emptyMap()), DependencyVersion.parse("1.4"))); Properties properties = new Properties(); try (InputStream in = new FileInputStream(gradleProperties)) { properties.load(in); diff --git a/buildSrc/src/test/java/org/springframework/boot/build/context/properties/CompoundRowTests.java b/buildSrc/src/test/java/org/springframework/boot/build/context/properties/CompoundRowTests.java index 887e01bf7557..4e6949e3f6d1 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/context/properties/CompoundRowTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/context/properties/CompoundRowTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ * Tests for {@link CompoundRow}. * * @author Brian Clozel + * @author Moritz Halbritter */ class CompoundRowTests { @@ -39,8 +40,8 @@ void simpleProperty() { row.addProperty(new ConfigurationProperty("spring.test.third", "java.lang.String")); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); - assertThat(asciidoc).hasToString("|[[my.spring.test]]<>" + NEWLINE + assertThat(asciidoc).hasToString("|[[my.spring.test]]xref:#my.spring.test[`+spring.test.first+` +" + NEWLINE + + "`+spring.test.second+` +" + NEWLINE + "`+spring.test.third+` +" + NEWLINE + "]" + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|" + NEWLINE); } diff --git a/buildSrc/src/test/java/org/springframework/boot/build/context/properties/SingleRowTests.java b/buildSrc/src/test/java/org/springframework/boot/build/context/properties/SingleRowTests.java index b1e48d8dff75..5d046a71efdb 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/context/properties/SingleRowTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/context/properties/SingleRowTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ * Tests for {@link SingleRow}. * * @author Brian Clozel + * @author Moritz Halbritter */ class SingleRowTests { @@ -38,7 +39,7 @@ void simpleProperty() { SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); - assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]<>" + assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]xref:#my.spring.test.prop[`+spring.test.prop+`]" + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|`+something+`" + NEWLINE); } @@ -49,7 +50,7 @@ void noDefaultValue() { SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); - assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]<>" + assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]xref:#my.spring.test.prop[`+spring.test.prop+`]" + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|" + NEWLINE); } @@ -60,7 +61,7 @@ void defaultValueWithPipes() { SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); - assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]<>" + assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]xref:#my.spring.test.prop[`+spring.test.prop+`]" + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|`+first\\|second+`" + NEWLINE); } @@ -71,7 +72,7 @@ void defaultValueWithBackslash() { SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); - assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]<>" + assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]xref:#my.spring.test.prop[`+spring.test.prop+`]" + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|`+first\\\\second+`" + NEWLINE); } @@ -82,7 +83,7 @@ void descriptionWithPipe() { SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); - assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]<>" + assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]xref:#my.spring.test.prop[`+spring.test.prop+`]" + NEWLINE + "|+++This is a description with a \\| pipe.+++" + NEWLINE + "|" + NEWLINE); } @@ -93,7 +94,7 @@ void mapProperty() { SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); - assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]<>" + assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]xref:#my.spring.test.prop[`+spring.test.prop.*+`]" + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|" + NEWLINE); } @@ -105,7 +106,7 @@ void listProperty() { SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); - assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]<>" + assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]xref:#my.spring.test.prop[`+spring.test.prop+`]" + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|`+first," + NEWLINE + "second," + NEWLINE + "third+`" + NEWLINE); } diff --git a/buildSrc/src/test/java/org/springframework/boot/build/context/properties/TableTests.java b/buildSrc/src/test/java/org/springframework/boot/build/context/properties/TableTests.java index 618218a6eb05..2ed509fbb951 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/context/properties/TableTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/context/properties/TableTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ * Tests for {@link Table}. * * @author Brian Clozel + * @author Moritz Halbritter */ class TableTests { @@ -44,10 +45,10 @@ void simpleTable() { assertThat(asciidoc).hasToString("[cols=\"4,3,3\", options=\"header\"]" + NEWLINE + "|===" + NEWLINE + "|Name|Description|Default Value" + NEWLINE + NEWLINE + - "|[[my.spring.test.other]]<>" + NEWLINE + + "|[[my.spring.test.other]]xref:#my.spring.test.other[`+spring.test.other+`]" + NEWLINE + "|+++This is another description.+++" + NEWLINE + "|`+other value+`" + NEWLINE + NEWLINE + - "|[[my.spring.test.prop]]<>" + NEWLINE + + "|[[my.spring.test.prop]]xref:#my.spring.test.prop[`+spring.test.prop+`]" + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|`+something+`" + NEWLINE + NEWLINE + "|===" + NEWLINE); diff --git a/buildSrc/src/test/resources/org/springframework/boot/build/antora/expected-playbook.yml b/buildSrc/src/test/resources/org/springframework/boot/build/antora/expected-playbook.yml new file mode 100644 index 000000000000..5f7f86a73d8a --- /dev/null +++ b/buildSrc/src/test/resources/org/springframework/boot/build/antora/expected-playbook.yml @@ -0,0 +1,48 @@ +antora: + extensions: + - require: '@springio/antora-extensions/override-navigation-builder-extension' + - require: '@springio/antora-extensions/static-page-extension' + - require: '@springio/antora-xref-extension' + stub: + - appendix:.* + - api:.* + - reference:.* + - require: '@springio/antora-zip-contents-collector-extension' + always_include: + - classifier: local-aggregate-content + name: test + locations: + - project/build/generated/docs/antora-content/test-${version}-${name}-${classifier}.zip + - project/build/generated/docs/antora-dependencies-content/test-${version}-${name}-${classifier}.zip + version_file: gradle.properties + - require: '@springio/antora-extensions/root-component-extension' + root_component_name: boot +site: + title: Spring Boot +content: + sources: + - url: ./../../../../.. + branches: HEAD + version: unspecified + start_paths: + - project/src/docs/antora +asciidoc: + sourcemap: true + attributes: + chomp: all + hide-uri-scheme: '@' + page-pagination: '' + page-stackoverflow-url: https://stackoverflow.com/tags/spring-boot + tabs-sync-option: '@' + extensions: + - '@asciidoctor/tabs' + - '@springio/asciidoctor-extensions' + - '@springio/asciidoctor-extensions/configuration-properties-extension' + - '@springio/asciidoctor-extensions/section-ids-extension' +urls: + latest_version_segment: '' +runtime: + log: + failure_level: warn +output: + dir: ./../../../site diff --git a/eclipse/spring-boot-project.setup b/eclipse/spring-boot-project.setup index 6cd6605e50d8..8cc04d8ee214 100644 --- a/eclipse/spring-boot-project.setup +++ b/eclipse/spring-boot-project.setup @@ -11,8 +11,8 @@ xmlns:setup.workingsets="http://www.eclipse.org/oomph/setup/workingsets/1.0" xmlns:workingsets="http://www.eclipse.org/oomph/workingsets/1.0" xsi:schemaLocation="http://www.eclipse.org/oomph/setup/jdt/1.0 http://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/JDT.ecore http://www.eclipse.org/buildship/oomph/1.0 https://raw.githubusercontent.com/eclipse/buildship/master/org.eclipse.buildship.oomph/model/GradleImport-1.0.ecore http://www.eclipse.org/oomph/predicates/1.0 http://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/Predicates.ecore http://www.eclipse.org/oomph/setup/workingsets/1.0 http://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/SetupWorkingSets.ecore http://www.eclipse.org/oomph/workingsets/1.0 http://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/WorkingSets.ecore" - name="spring.boot.3.1.x" - label="Spring Boot 3.1.x"> + name="spring.boot.3.4.x" + label="Spring Boot 3.4.x"> + pattern="spring-boot-(tools|antlib|configuration-.*|loader|loader-classic|.*-tools|.*-layertools|.*-plugin|autoconfigure-processor|buildpack.*)"/> diff --git a/git/hooks/prepare-forward-merge b/git/hooks/prepare-forward-merge index e9f03a32b594..da1a1ae1bd33 100755 --- a/git/hooks/prepare-forward-merge +++ b/git/hooks/prepare-forward-merge @@ -4,7 +4,7 @@ require 'net/http' require 'yaml' require 'logger' -$main_branch = "3.1.x" +$main_branch = "3.4.x" $log = Logger.new(STDOUT) $log.level = Logger::WARN diff --git a/gradle.properties b/gradle.properties index d112f4a6363b..d0d1f60899bb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,18 +1,22 @@ -version=3.1.13-SNAPSHOT +version=3.4.0-SNAPSHOT +latestVersion=true org.gradle.caching=true org.gradle.parallel=true org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8 -assertjVersion=3.24.2 -commonsCodecVersion=1.15 +assertjVersion=3.26.3 +commonsCodecVersion=1.17.1 +graalVersion=22.3 hamcrestVersion=2.2 -jacksonVersion=2.15.4 -junitJupiterVersion=5.9.3 -kotlinVersion=1.8.22 +jacksonVersion=2.17.2 +junitJupiterVersion=5.10.3 +kotlinVersion=1.9.24 mavenVersion=3.9.4 -nativeBuildToolsVersion=0.9.28 -springFrameworkVersion=6.0.21 -tomcatVersion=10.1.24 +nativeBuildToolsVersion=0.10.2 +springFrameworkVersion=6.2.0-M6 +springFramework60xVersion=6.0.21 +tomcatVersion=10.1.26 +snakeYamlVersion=2.2 kotlin.stdlib.default.dependency=false diff --git a/settings.gradle b/settings.gradle index cc0bde0f208a..fe32fe1063b0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -19,8 +19,8 @@ pluginManagement { } plugins { - id "com.gradle.develocity" version "3.17.2" - id "io.spring.ge.conventions" version "0.0.17" + id "com.gradle.develocity" version "3.17.5" + id "io.spring.develocity.conventions" version "0.0.19" } rootProject.name="spring-boot-build" @@ -35,13 +35,6 @@ settings.gradle.projectsLoaded { value('Toolchain version', toolchainVersion) tag("JDK-$toolchainVersion") } - def buildDir = settings.gradle.rootProject.getBuildDir() - buildDir.mkdirs() - new File(buildDir, "build-scan-uri.txt").text = "build scan not generated" - buildScanPublished { scan -> - buildDir.mkdirs() - new File(buildDir, "build-scan-uri.txt").text = "<${scan.buildScanUri}|build scan>\n" - } } } } @@ -53,15 +46,18 @@ include "spring-boot-project:spring-boot-tools:spring-boot-autoconfigure-process include "spring-boot-project:spring-boot-tools:spring-boot-buildpack-platform" include "spring-boot-project:spring-boot-tools:spring-boot-cli" include "spring-boot-project:spring-boot-tools:spring-boot-configuration-metadata" +include "spring-boot-project:spring-boot-tools:spring-boot-configuration-metadata-changelog-generator" include "spring-boot-project:spring-boot-tools:spring-boot-configuration-processor" include "spring-boot-project:spring-boot-tools:spring-boot-gradle-plugin" include "spring-boot-project:spring-boot-tools:spring-boot-gradle-test-support" -include "spring-boot-project:spring-boot-tools:spring-boot-jarmode-layertools" +include "spring-boot-project:spring-boot-tools:spring-boot-jarmode-tools" include "spring-boot-project:spring-boot-tools:spring-boot-loader" +include "spring-boot-project:spring-boot-tools:spring-boot-loader-classic" include "spring-boot-project:spring-boot-tools:spring-boot-loader-tools" include "spring-boot-project:spring-boot-tools:spring-boot-maven-plugin" include "spring-boot-project:spring-boot-tools:spring-boot-properties-migrator" include "spring-boot-project:spring-boot-tools:spring-boot-test-support" +include "spring-boot-project:spring-boot-tools:spring-boot-test-support-docker" include "spring-boot-project:spring-boot" include "spring-boot-project:spring-boot-autoconfigure" include "spring-boot-project:spring-boot-actuator" @@ -75,7 +71,9 @@ include "spring-boot-project:spring-boot-test-autoconfigure" include "spring-boot-tests:spring-boot-integration-tests:spring-boot-configuration-processor-tests" include "spring-boot-tests:spring-boot-integration-tests:spring-boot-launch-script-tests" include "spring-boot-tests:spring-boot-integration-tests:spring-boot-loader-tests" +include "spring-boot-tests:spring-boot-integration-tests:spring-boot-loader-classic-tests" include "spring-boot-tests:spring-boot-integration-tests:spring-boot-server-tests" +include "spring-boot-tests:spring-boot-integration-tests:spring-boot-sni-tests" include "spring-boot-system-tests:spring-boot-deployment-tests" include "spring-boot-system-tests:spring-boot-image-tests" diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle index bc927ed25f81..f52902b08759 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle @@ -1,6 +1,6 @@ plugins { id "java-library" - id "org.asciidoctor.jvm.convert" + id "org.antora" id "org.springframework.boot.auto-configuration" id "org.springframework.boot.configuration-properties" id "org.springframework.boot.conventions" @@ -11,13 +11,10 @@ plugins { description = "Spring Boot Actuator AutoConfigure" configurations { - documentation + antoraContent } dependencies { - asciidoctorExtensions("org.springframework.restdocs:spring-restdocs-asciidoctor") - asciidoctorExtensions("io.spring.asciidoctor:spring-asciidoctor-extensions-section-ids") - api(project(":spring-boot-project:spring-boot-actuator")) api(project(":spring-boot-project:spring-boot")) api(project(":spring-boot-project:spring-boot-autoconfigure")) @@ -26,7 +23,7 @@ dependencies { implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") optional("ch.qos.logback:logback-classic") - optional("com.datastax.oss:java-driver-core") { + optional("org.apache.cassandra:java-driver-core") { exclude group: "org.slf4j", module: "jcl-over-slf4j" } optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml") @@ -34,10 +31,9 @@ dependencies { optional("com.hazelcast:hazelcast") optional("com.hazelcast:hazelcast-spring") optional("com.zaxxer:HikariCP") - optional("io.dropwizard.metrics:metrics-jmx") optional("io.lettuce:lettuce-core") optional("io.micrometer:micrometer-observation") - optional("io.micrometer:micrometer-core") + optional("io.micrometer:micrometer-jakarta9") optional("io.micrometer:micrometer-tracing") optional("io.micrometer:micrometer-tracing-bridge-brave") optional("io.micrometer:micrometer-tracing-bridge-otel") @@ -58,6 +54,7 @@ dependencies { optional("io.micrometer:micrometer-registry-new-relic") optional("io.micrometer:micrometer-registry-otlp") optional("io.micrometer:micrometer-registry-prometheus") + optional("io.micrometer:micrometer-registry-prometheus-simpleclient") optional("io.micrometer:micrometer-registry-stackdriver") { exclude group: "commons-logging", module: "commons-logging" exclude group: "javax.annotation", module: "javax.annotation-api" @@ -74,12 +71,13 @@ dependencies { optional("io.opentelemetry:opentelemetry-exporter-otlp") optional("io.projectreactor.netty:reactor-netty-http") optional("io.r2dbc:r2dbc-pool") + optional("io.r2dbc:r2dbc-proxy") optional("io.r2dbc:r2dbc-spi") optional("jakarta.jms:jakarta.jms-api") optional("jakarta.persistence:jakarta.persistence-api") optional("jakarta.servlet:jakarta.servlet-api") optional("javax.cache:cache-api") - optional("org.apache.activemq:activemq-client-jakarta") + optional("org.apache.activemq:activemq-client") optional("org.apache.commons:commons-dbcp2") { exclude group: "commons-logging", module: "commons-logging" } @@ -102,6 +100,7 @@ dependencies { optional("org.flywaydb:flyway-core") optional("org.glassfish.jersey.core:jersey-server") optional("org.glassfish.jersey.containers:jersey-container-servlet-core") + optional("org.glassfish.jersey.ext:jersey-micrometer") optional("org.hibernate.orm:hibernate-core") optional("org.hibernate.orm:hibernate-micrometer") optional("org.hibernate.validator:hibernate-validator") @@ -143,6 +142,7 @@ dependencies { testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation("io.micrometer:micrometer-observation-test") testImplementation("io.projectreactor:reactor-test") + testImplementation("io.prometheus:prometheus-metrics-exposition-formats") testImplementation("io.r2dbc:r2dbc-h2") testImplementation("com.squareup.okhttp3:mockwebserver") testImplementation("com.jayway.jsonpath:json-path") @@ -160,9 +160,7 @@ dependencies { testImplementation("org.assertj:assertj-core") testImplementation("org.awaitility:awaitility") testImplementation("org.cache2k:cache2k-api") - testImplementation("org.eclipse.jetty:jetty-webapp") { - exclude group: "org.eclipse.jetty.toolchain", module: "jetty-jakarta-servlet-api" - } + testImplementation("org.eclipse.jetty.ee10:jetty-ee10-webapp") testImplementation("org.glassfish.jersey.ext:jersey-spring6") testImplementation("org.glassfish.jersey.media:jersey-media-json-jackson") testImplementation("org.hamcrest:hamcrest") @@ -173,6 +171,7 @@ dependencies { testImplementation("org.skyscreamer:jsonassert") testImplementation("org.springframework:spring-core-test") testImplementation("org.springframework:spring-orm") + testImplementation("org.springframework:spring-test") testImplementation("org.springframework.data:spring-data-rest-webmvc") testImplementation("org.springframework.integration:spring-integration-jmx") testImplementation("org.springframework.restdocs:spring-restdocs-mockmvc") @@ -196,38 +195,6 @@ dependencies { } } -task dependencyVersions(type: org.springframework.boot.build.constraints.ExtractVersionConstraints) { - enforcedPlatform(":spring-boot-project:spring-boot-dependencies") -} - -asciidoctor { - sources { - include "index.adoc" - } -} - -task asciidoctorPdf(type: org.asciidoctor.gradle.jvm.AsciidoctorTask) { - sources { - include "index.adoc" - } -} - -task zip(type: Zip) { - dependsOn asciidoctor, asciidoctorPdf - duplicatesStrategy "fail" - from(asciidoctorPdf.outputDir) { - into "pdf" - rename { "spring-boot-actuator-web-api.pdf" } - } - from(asciidoctor.outputDir) { - into "htmlsingle" - } -} - -artifacts { - documentation zip -} - tasks.named("test") { jvmArgs += "--add-opens=java.base/java.net=ALL-UNNAMED" filter { @@ -236,10 +203,10 @@ tasks.named("test") { } def documentationTest = tasks.register("documentationTest", Test) { + jvmArgs += "--add-opens=java.base/java.net=ALL-UNNAMED" filter { includeTestsMatching("org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation.*") } - jvmArgs += "--add-opens=java.base/java.net=ALL-UNNAMED" outputs.dir("${buildDir}/generated-snippets") develocity { predictiveTestSelection { @@ -248,14 +215,36 @@ def documentationTest = tasks.register("documentationTest", Test) { } } -tasks.withType(org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask) { - dependsOn dependencyVersions - doFirst { - def versionConstraints = dependencyVersions.versionConstraints - def integrationVersion = versionConstraints["org.springframework.integration:spring-integration-core"] - def integrationDocs = String.format("https://docs.spring.io/spring-integration/docs/%s/reference/html/", integrationVersion) - attributes "spring-integration-docs": integrationDocs +def antoraActuatorRestApiLocalAggregateContent = tasks.register("antoraActuatorRestApiLocalAggregateContent", Zip) { + destinationDirectory = layout.buildDirectory.dir('generated/docs/antora-content') + archiveClassifier = "actuator-rest-api-local-aggregate-content" + from(tasks.getByName("generateAntoraYml")) { + into "modules" } +} + +def antoraActuatorRestApiAggregateContent = tasks.register("antoraActuatorRestApiAggregateContent", Zip) { dependsOn documentationTest - inputs.dir("${buildDir}/generated-snippets").withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName("generatedSnippets") + inputs.dir("${buildDir}/generated-snippets") + .withPathSensitivity(PathSensitivity.RELATIVE) + .withPropertyName("generatedSnippets") + destinationDirectory = layout.buildDirectory.dir('generated/docs/antora-content') + archiveClassifier = "actuator-rest-api-aggregate-content" + from("${buildDir}/generated-snippets") { + into "modules/api/partials/rest/actuator" + } } + +tasks.named("generateAntoraPlaybook") { + alwaysInclude = [name: "actuator-rest-api", classifier: "local-aggregate-content"] + dependsOn antoraActuatorRestApiLocalAggregateContent +} + +tasks.named("antora") { + inputs.files(antoraActuatorRestApiAggregateContent) +} + +artifacts { + antoraContent antoraActuatorRestApiAggregateContent +} + diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/antora.yml b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/antora.yml new file mode 100644 index 000000000000..48c03f5e718f --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/antora.yml @@ -0,0 +1,7 @@ +name: boot +version: true +ext: + zip_contents_collector: + include: + - name: actuator-rest-api + classifier: aggregate-content diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/local-nav.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/local-nav.adoc new file mode 100644 index 000000000000..5216164fe903 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/local-nav.adoc @@ -0,0 +1 @@ +include::api:partial$nav-actuator-rest-api.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/auditevents.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/auditevents.adoc similarity index 76% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/auditevents.adoc rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/auditevents.adoc index 1aad3ea958c3..08c8c2951f38 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/auditevents.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/auditevents.adoc @@ -1,36 +1,40 @@ [[audit-events]] = Audit Events (`auditevents`) + The `auditevents` endpoint provides information about the application's audit events. [[audit-events.retrieving]] == Retrieving Audit Events + To retrieve the audit events, make a `GET` request to `/actuator/auditevents`, as shown in the following curl-based example: -include::{snippets}/auditevents/filtered/curl-request.adoc[] +include::partial$rest/actuator/auditevents/filtered/curl-request.adoc[] The preceding example retrieves `logout` events for the principal, `alice`, that occurred after 09:37 on 7 November 2017 in the UTC timezone. The resulting response is similar to the following: -include::{snippets}/auditevents/filtered/http-response.adoc[] +include::partial$rest/actuator/auditevents/filtered/http-response.adoc[] [[audit-events.retrieving.query-parameters]] === Query Parameters + The endpoint uses query parameters to limit the events that it returns. The following table shows the supported query parameters: [cols="2,4"] -include::{snippets}/auditevents/filtered/query-parameters.adoc[] +include::partial$rest/actuator/auditevents/filtered/query-parameters.adoc[] [[audit-events.retrieving.response-structure]] === Response Structure + The response contains details of all of the audit events that matched the query. The following table describes the structure of the response: [cols="2,1,3"] -include::{snippets}/auditevents/all/response-fields.adoc[] +include::partial$rest/actuator/auditevents/all/response-fields.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/beans.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/beans.adoc new file mode 100644 index 000000000000..3a05688cc141 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/beans.adoc @@ -0,0 +1,28 @@ +[[beans]] += Beans (`beans`) + +The `beans` endpoint provides information about the application's beans. + + + +[[beans.retrieving]] +== Retrieving the Beans + +To retrieve the beans, make a `GET` request to `/actuator/beans`, as shown in the following curl-based example: + +include::partial$rest/actuator/beans/curl-request.adoc[] + +The resulting response is similar to the following: + +include::partial$rest/actuator/beans/http-response.adoc[] + + + +[[beans.retrieving.response-structure]] +=== Response Structure + +The response contains details of the application's beans. +The following table describes the structure of the response: + +[cols="2,1,3"] +include::partial$rest/actuator/beans/response-fields.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/caches.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/caches.adoc similarity index 75% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/caches.adoc rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/caches.adoc index ebc2be7047a6..02df8bb26419 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/caches.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/caches.adoc @@ -1,78 +1,86 @@ [[caches]] = Caches (`caches`) + The `caches` endpoint provides access to the application's caches. [[caches.all]] == Retrieving All Caches + To retrieve the application's caches, make a `GET` request to `/actuator/caches`, as shown in the following curl-based example: -include::{snippets}/caches/all/curl-request.adoc[] +include::partial$rest/actuator/caches/all/curl-request.adoc[] The resulting response is similar to the following: -include::{snippets}/caches/all/http-response.adoc[] +include::partial$rest/actuator/caches/all/http-response.adoc[] [[caches.all.response-structure]] === Response Structure + The response contains details of the application's caches. The following table describes the structure of the response: [cols="3,1,3"] -include::{snippets}/caches/all/response-fields.adoc[] +include::partial$rest/actuator/caches/all/response-fields.adoc[] [[caches.named]] == Retrieving Caches by Name + To retrieve a cache by name, make a `GET` request to `/actuator/caches/\{name}`, as shown in the following curl-based example: -include::{snippets}/caches/named/curl-request.adoc[] +include::partial$rest/actuator/caches/named/curl-request.adoc[] The preceding example retrieves information about the cache named `cities`. The resulting response is similar to the following: -include::{snippets}/caches/named/http-response.adoc[] +include::partial$rest/actuator/caches/named/http-response.adoc[] [[caches.named.query-parameters]] === Query Parameters + If the requested name is specific enough to identify a single cache, no extra parameter is required. Otherwise, the `cacheManager` must be specified. The following table shows the supported query parameters: [cols="2,4"] -include::{snippets}/caches/named/query-parameters.adoc[] +include::partial$rest/actuator/caches/named/query-parameters.adoc[] [[caches.named.response-structure]] === Response Structure + The response contains details of the requested cache. The following table describes the structure of the response: [cols="3,1,3"] -include::{snippets}/caches/named/response-fields.adoc[] +include::partial$rest/actuator/caches/named/response-fields.adoc[] [[caches.evict-all]] == Evict All Caches + To clear all available caches, make a `DELETE` request to `/actuator/caches` as shown in the following curl-based example: -include::{snippets}/caches/evict-all/curl-request.adoc[] +include::partial$rest/actuator/caches/evict-all/curl-request.adoc[] [[caches.evict-named]] == Evict a Cache by Name + To evict a particular cache, make a `DELETE` request to `/actuator/caches/\{name}` as shown in the following curl-based example: -include::{snippets}/caches/evict-named/curl-request.adoc[] +include::partial$rest/actuator/caches/evict-named/curl-request.adoc[] NOTE: As there are two caches named `countries`, the `cacheManager` has to be provided to specify which `Cache` should be cleared. @@ -80,9 +88,10 @@ NOTE: As there are two caches named `countries`, the `cacheManager` has to be pr [[caches.evict-named.request-structure]] === Request Structure + If the requested name is specific enough to identify a single cache, no extra parameter is required. Otherwise, the `cacheManager` must be specified. The following table shows the supported query parameters: [cols="2,4"] -include::{snippets}/caches/evict-named/query-parameters.adoc[] +include::partial$rest/actuator/caches/evict-named/query-parameters.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/conditions.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/conditions.adoc similarity index 76% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/conditions.adoc rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/conditions.adoc index 96d3daf2b640..12e7313dfe91 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/conditions.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/conditions.adoc @@ -1,25 +1,28 @@ [[conditions]] = Conditions Evaluation Report (`conditions`) + The `conditions` endpoint provides information about the evaluation of conditions on configuration and auto-configuration classes. [[conditions.retrieving]] == Retrieving the Report + To retrieve the report, make a `GET` request to `/actuator/conditions`, as shown in the following curl-based example: -include::{snippets}/conditions/curl-request.adoc[] +include::partial$rest/actuator/conditions/curl-request.adoc[] The resulting response is similar to the following: -include::{snippets}/conditions/http-response.adoc[] +include::partial$rest/actuator/conditions/http-response.adoc[] [[conditions.retrieving.response-structure]] === Response Structure + The response contains details of the application's condition evaluation. The following table describes the structure of the response: [cols="3,1,3"] -include::{snippets}/conditions/response-fields.adoc[] +include::partial$rest/actuator/conditions/response-fields.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/configprops.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/configprops.adoc similarity index 76% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/configprops.adoc rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/configprops.adoc index b2775d87e71c..6279c198a6b2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/configprops.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/configprops.adoc @@ -1,40 +1,44 @@ [[configprops]] = Configuration Properties (`configprops`) + The `configprops` endpoint provides information about the application's `@ConfigurationProperties` beans. [[configprops.retrieving]] == Retrieving All @ConfigurationProperties Beans + To retrieve all of the `@ConfigurationProperties` beans, make a `GET` request to `/actuator/configprops`, as shown in the following curl-based example: -include::{snippets}/configprops/all/curl-request.adoc[] +include::partial$rest/actuator/configprops/all/curl-request.adoc[] The resulting response is similar to the following: -include::{snippets}/configprops/all/http-response.adoc[] +include::partial$rest/actuator/configprops/all/http-response.adoc[] [[configprops.retrieving.response-structure]] === Response Structure + The response contains details of the application's `@ConfigurationProperties` beans. The following table describes the structure of the response: [cols="2,1,3"] -include::{snippets}/configprops/all/response-fields.adoc[] +include::partial$rest/actuator/configprops/all/response-fields.adoc[] [[configprops.retrieving-by-prefix]] == Retrieving @ConfigurationProperties Beans By Prefix + To retrieve the `@ConfigurationProperties` beans mapped under a certain prefix, make a `GET` request to `/actuator/configprops/\{prefix}`, as shown in the following curl-based example: -include::{snippets}/configprops/prefixed/curl-request.adoc[] +include::partial$rest/actuator/configprops/prefixed/curl-request.adoc[] The resulting response is similar to the following: -include::{snippets}/configprops/prefixed/http-response.adoc[] +include::partial$rest/actuator/configprops/prefixed/http-response.adoc[] NOTE: The `\{prefix}` does not need to be exact, a more general prefix will return all beans mapped under that prefix stem. @@ -42,8 +46,9 @@ NOTE: The `\{prefix}` does not need to be exact, a more general prefix will retu [[configprops.retrieving-by-prefix.response-structure]] === Response Structure + The response contains details of the application's `@ConfigurationProperties` beans. The following table describes the structure of the response: [cols="2,1,3"] -include::{snippets}/configprops/prefixed/response-fields.adoc[] +include::partial$rest/actuator/configprops/prefixed/response-fields.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/env.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/env.adoc similarity index 76% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/env.adoc rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/env.adoc index 4e75bfec4f2d..01d689013d31 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/env.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/env.adoc @@ -1,49 +1,57 @@ [[env]] = Environment (`env`) + The `env` endpoint provides information about the application's `Environment`. [[env.entire]] == Retrieving the Entire Environment + To retrieve the entire environment, make a `GET` request to `/actuator/env`, as shown in the following curl-based example: -include::{snippets}/env/all/curl-request.adoc[] +include::partial$rest/actuator/env/all/curl-request.adoc[] The resulting response is similar to the following: -include::{snippets}/env/all/http-response.adoc[] +include::partial$rest/actuator/env/all/http-response.adoc[] NOTE: Sanitization of sensitive values has been switched off for this example. + [[env.entire.response-structure]] === Response Structure + The response contains details of the application's `Environment`. The following table describes the structure of the response: [cols="3,1,3"] -include::{snippets}/env/all/response-fields.adoc[] +include::partial$rest/actuator/env/all/response-fields.adoc[] [[env.single-property]] == Retrieving a Single Property + To retrieve a single property, make a `GET` request to `/actuator/env/{property.name}`, as shown in the following curl-based example: -include::{snippets}/env/single/curl-request.adoc[] +include::partial$rest/actuator/env/single/curl-request.adoc[] The preceding example retrieves information about the property named `com.example.cache.max-size`. The resulting response is similar to the following: -include::{snippets}/env/single/http-response.adoc[] +include::partial$rest/actuator/env/single/http-response.adoc[] NOTE: Sanitization of sensitive values has been switched off for this example. + + [[env.single-property.response-structure]] === Response Structure + The response contains details of the requested property. The following table describes the structure of the response: [cols="3,1,3"] -include::{snippets}/env/single/response-fields.adoc[] +include::partial$rest/actuator/env/single/response-fields.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/flyway.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/flyway.adoc similarity index 75% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/flyway.adoc rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/flyway.adoc index 5d69b9d7594c..69053bdc6693 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/flyway.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/flyway.adoc @@ -1,25 +1,28 @@ [[flyway]] = Flyway (`flyway`) + The `flyway` endpoint provides information about database migrations performed by Flyway. [[flyway.retrieving]] == Retrieving the Migrations + To retrieve the migrations, make a `GET` request to `/actuator/flyway`, as shown in the following curl-based example: -include::{snippets}/flyway/curl-request.adoc[] +include::partial$rest/actuator/flyway/curl-request.adoc[] The resulting response is similar to the following: -include::{snippets}/flyway/http-response.adoc[] +include::partial$rest/actuator/flyway/http-response.adoc[] [[flyway.retrieving.response-structure]] === Response Structure + The response contains details of the application's Flyway migrations. The following table describes the structure of the response: [cols="2,1,3"] -include::{snippets}/flyway/response-fields.adoc[] +include::partial$rest/actuator/flyway/response-fields.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/health.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/health.adoc similarity index 79% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/health.adoc rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/health.adoc index bf65b4686326..bd2bda85d871 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/health.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/health.adoc @@ -1,28 +1,31 @@ [[health]] = Health (`health`) + The `health` endpoint provides detailed information about the health of the application. [[health.retrieving]] == Retrieving the Health of the Application + To retrieve the health of the application, make a `GET` request to `/actuator/health`, as shown in the following curl-based example: -include::{snippets}/health/curl-request.adoc[] +include::partial$rest/actuator/health/curl-request.adoc[] The resulting response is similar to the following: -include::{snippets}/health/http-response.adoc[] +include::partial$rest/actuator/health/http-response.adoc[] [[health.retrieving.response-structure]] === Response Structure + The response contains details of the health of the application. The following table describes the structure of the response: [cols="2,1,3"] -include::{snippets}/health/response-fields.adoc[] +include::partial$rest/actuator/health/response-fields.adoc[] NOTE: The response fields above are for the V3 API. If you need to return V2 JSON you should use an accept header or `application/vnd.spring-boot.actuator.v2+json` @@ -31,35 +34,38 @@ If you need to return V2 JSON you should use an accept header or `application/vn [[health.retrieving-component]] == Retrieving the Health of a Component + To retrieve the health of a particular component of the application's health, make a `GET` request to `/actuator/health/\{component}`, as shown in the following curl-based example: -include::{snippets}/health/component/curl-request.adoc[] +include::partial$rest/actuator/health/component/curl-request.adoc[] The resulting response is similar to the following: -include::{snippets}/health/component/http-response.adoc[] +include::partial$rest/actuator/health/component/http-response.adoc[] [[health.retrieving-component.response-structure]] === Response Structure + The response contains details of the health of a particular component of the application's health. The following table describes the structure of the response: [cols="2,1,3"] -include::{snippets}/health/component/response-fields.adoc[] +include::partial$rest/actuator/health/component/response-fields.adoc[] [[health.retrieving-component-nested]] == Retrieving the Health of a Nested Component + If a particular component contains other nested components (as the `broker` indicator in the example above), the health of such a nested component can be retrieved by issuing a `GET` request to `/actuator/health/\{component}/\{subcomponent}`, as shown in the following curl-based example: -include::{snippets}/health/instance/curl-request.adoc[] +include::partial$rest/actuator/health/instance/curl-request.adoc[] The resulting response is similar to the following: -include::{snippets}/health/instance/http-response.adoc[] +include::partial$rest/actuator/health/instance/http-response.adoc[] Components of an application's health may be nested arbitrarily deep depending on the application's health indicators and how they have been grouped. The health endpoint supports any number of `/\{component}` identifiers in the URL to allow the health of a component at any depth to be retrieved. @@ -68,8 +74,9 @@ The health endpoint supports any number of `/\{component}` identifiers in the UR [[health.retrieving-component-nested.response-structure]] === Response Structure + The response contains details of the health of an instance of a particular component of the application. The following table describes the structure of the response: [cols="2,1,3"] -include::{snippets}/health/instance/response-fields.adoc[] +include::partial$rest/actuator/health/instance/response-fields.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/heapdump.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/heapdump.adoc similarity index 93% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/heapdump.adoc rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/heapdump.adoc index c1e9e026c91a..3219e957c579 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/heapdump.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/heapdump.adoc @@ -1,11 +1,13 @@ [[heapdump]] = Heap Dump (`heapdump`) + The `heapdump` endpoint provides a heap dump from the application's JVM. [[heapdump.retrieving]] == Retrieving the Heap Dump + To retrieve the heap dump, make a `GET` request to `/actuator/heapdump`. The response is binary data and can be large. Its format depends upon the JVM on which the application is running. @@ -14,6 +16,6 @@ and on OpenJ9 it is https://www.eclipse.org/openj9/docs/dump_heapdump/#portable- Typically, you should save the response to disk for subsequent analysis. When using curl, this can be achieved by using the `-O` option, as shown in the following example: -include::{snippets}/heapdump/curl-request.adoc[] +include::partial$rest/actuator/heapdump/curl-request.adoc[] The preceding example results in a file named `heapdump` being written to the current working directory. diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/httpexchanges.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/httpexchanges.adoc similarity index 75% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/httpexchanges.adoc rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/httpexchanges.adoc index ec07ddf32784..e53fb69a4247 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/httpexchanges.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/httpexchanges.adoc @@ -1,25 +1,28 @@ [[httpexchanges]] = HTTP Exchanges (`httpexchanges`) + The `httpexchanges` endpoint provides information about HTTP request-response exchanges. [[httpexchanges.retrieving]] == Retrieving the HTTP Exchanges + To retrieve the HTTP exchanges, make a `GET` request to `/actuator/httpexchanges`, as shown in the following curl-based example: -include::{snippets}/httpexchanges/curl-request.adoc[] +include::partial$rest/actuator/httpexchanges/curl-request.adoc[] The resulting response is similar to the following: -include::{snippets}/httpexchanges/http-response.adoc[] +include::partial$rest/actuator/httpexchanges/http-response.adoc[] [[httpexchanges.retrieving.response-structure]] === Response Structure + The response contains details of the traced HTTP request-response exchanges. The following table describes the structure of the response: [cols="2,1,3"] -include::{snippets}/httpexchanges/response-fields.adoc[] +include::partial$rest/actuator/httpexchanges/response-fields.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/index.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/index.adoc new file mode 100644 index 000000000000..342001687726 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/index.adoc @@ -0,0 +1,41 @@ +:navtitle: Actuator +[[overview]] += Actuator REST API + +This API documentation describes Spring Boot Actuators web endpoints. + +Before you proceed, you should read the following topics: + +* xref:#overview.endpoint-urls[] +* xref:#overview.timestamps[] + +NOTE: In order to get the correct JSON responses documented below, Jackson must be available. + + + +[[overview.endpoint-urls]] +== URLs + +By default, all web endpoints are available beneath the path `/actuator` with URLs of +the form `/actuator/\{id}`. The `/actuator` base path can be configured by using the +`management.endpoints.web.base-path` property, as shown in the following example: + +[source,properties] +---- +management.endpoints.web.base-path=/manage +---- + +The preceding `application.properties` example changes the form of the endpoint URLs from +`/actuator/\{id}` to `/manage/\{id}`. For example, the URL `info` endpoint would become +`/manage/info`. + + + +[[overview.timestamps]] +== Timestamps + +All timestamps that are consumed by the endpoints, either as query parameters or in the +request body, must be formatted as an offset date and time as specified in +https://en.wikipedia.org/wiki/ISO_8601[ISO 8601]. + + diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/info.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/info.adoc similarity index 80% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/info.adoc rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/info.adoc index 3f69831526ac..e050c469594e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/info.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/info.adoc @@ -1,23 +1,26 @@ [[info]] = Info (`info`) + The `info` endpoint provides general information about the application. [[info.retrieving]] == Retrieving the Info + To retrieve the information about the application, make a `GET` request to `/actuator/info`, as shown in the following curl-based example: -include::{snippets}/info/curl-request.adoc[] +include::partial$rest/actuator/info/curl-request.adoc[] The resulting response is similar to the following: -include::{snippets}/info/http-response.adoc[] +include::partial$rest/actuator/info/http-response.adoc[] [[info.retrieving.response-structure]] === Response Structure + The response contains general information about the application. Each section of the response is contributed by an `InfoContributor`. Spring Boot provides several contributors that are described below. @@ -26,19 +29,21 @@ Spring Boot provides several contributors that are described below. [[info.retrieving.response-structure.build]] ==== Build Response Structure + The following table describe the structure of the `build` section of the response: [cols="2,1,3"] -include::{snippets}/info/response-fields-beneath-build.adoc[] +include::partial$rest/actuator/info/response-fields-beneath-build.adoc[] [[info.retrieving.response-structure.git]] ==== Git Response Structure + The following table describes the structure of the `git` section of the response: [cols="2,1,3"] -include::{snippets}/info/response-fields-beneath-git.adoc[] +include::partial$rest/actuator/info/response-fields-beneath-git.adoc[] NOTE: This is the "simple" output. The contributor can also be configured to output all available data. diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/integrationgraph.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/integrationgraph.adoc new file mode 100644 index 000000000000..e9e86690b4b1 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/integrationgraph.adoc @@ -0,0 +1,38 @@ +[[integrationgraph]] += Spring Integration Graph (`integrationgraph`) + +The `integrationgraph` endpoint exposes a graph containing all Spring Integration components. + + + +[[integrationgraph.retrieving]] +== Retrieving the Spring Integration Graph + +To retrieve the information about the application, make a `GET` request to `/actuator/integrationgraph`, as shown in the following curl-based example: + +include::partial$rest/actuator/integrationgraph/graph/curl-request.adoc[] + +The resulting response is similar to the following: + +include::partial$rest/actuator/integrationgraph/graph/http-response.adoc[] + + + +[[integrationgraph.retrieving.response-structure]] +=== Response Structure + +The response contains all Spring Integration components used within the application, as well as the links between them. +More information about the structure can be found in the {url-spring-integration-docs}/graph.html[reference documentation]. + + + +[[integrationgraph.rebuilding]] +== Rebuilding the Spring Integration Graph + +To rebuild the exposed graph, make a `POST` request to `/actuator/integrationgraph`, as shown in the following curl-based example: + +include::partial$rest/actuator/integrationgraph/rebuild/curl-request.adoc[] + +This will result in a `204 - No Content` response: + +include::partial$rest/actuator/integrationgraph/rebuild/http-response.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/liquibase.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/liquibase.adoc similarity index 75% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/liquibase.adoc rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/liquibase.adoc index 7bfd46565da2..64cf174d4d61 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/liquibase.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/liquibase.adoc @@ -1,25 +1,28 @@ [[liquibase]] = Liquibase (`liquibase`) + The `liquibase` endpoint provides information about database change sets applied by Liquibase. [[liquibase.retrieving]] == Retrieving the Changes + To retrieve the changes, make a `GET` request to `/actuator/liquibase`, as shown in the following curl-based example: -include::{snippets}/liquibase/curl-request.adoc[] +include::partial$rest/actuator/liquibase/curl-request.adoc[] The resulting response is similar to the following: -include::{snippets}/liquibase/http-response.adoc[] +include::partial$rest/actuator/liquibase/http-response.adoc[] [[liquibase.retrieving.response-structure]] === Response Structure + The response contains details of the application's Liquibase change sets. The following table describes the structure of the response: [cols="2,1,3"] -include::{snippets}/liquibase/response-fields.adoc[] +include::partial$rest/actuator/liquibase/response-fields.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/logfile.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/logfile.adoc new file mode 100644 index 000000000000..07e843e1aae9 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/logfile.adoc @@ -0,0 +1,33 @@ +[[logfile]] += Log File (`logfile`) + +The `logfile` endpoint provides access to the contents of the application's log file. + + + +[[logfile.retrieving]] +== Retrieving the Log File + +To retrieve the log file, make a `GET` request to `/actuator/logfile`, as shown in the following curl-based example: + +include::partial$rest/actuator/logfile/entire/curl-request.adoc[] + +The resulting response is similar to the following: + +include::partial$rest/actuator/logfile/entire/http-response.adoc[] + + + +[[logfile.retrieving-part]] +== Retrieving Part of the Log File + +NOTE: Retrieving part of the log file is not supported when using Jersey. + +To retrieve part of the log file, make a `GET` request to `/actuator/logfile` by using the `Range` header, as shown in the following curl-based example: + +include::partial$rest/actuator/logfile/range/curl-request.adoc[] + +The preceding example retrieves the first 1024 bytes of the log file. +The resulting response is similar to the following: + +include::partial$rest/actuator/logfile/range/http-response.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/loggers.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/loggers.adoc new file mode 100644 index 000000000000..8aa9c568a8a7 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/loggers.adoc @@ -0,0 +1,134 @@ +[[loggers]] += Loggers (`loggers`) + +The `loggers` endpoint provides access to the application's loggers and the configuration of their levels. + + + +[[loggers.all]] +== Retrieving All Loggers + +To retrieve the application's loggers, make a `GET` request to `/actuator/loggers`, as shown in the following curl-based example: + +include::partial$rest/actuator/loggers/all/curl-request.adoc[] + +The resulting response is similar to the following: + +include::partial$rest/actuator/loggers/all/http-response.adoc[] + + + +[[loggers.all.response-structure]] +=== Response Structure + +The response contains details of the application's loggers. +The following table describes the structure of the response: + +[cols="3,1,3"] +include::partial$rest/actuator/loggers/all/response-fields.adoc[] + + + +[[loggers.single]] +== Retrieving a Single Logger + +To retrieve a single logger, make a `GET` request to `/actuator/loggers/{logger.name}`, as shown in the following curl-based example: + +include::partial$rest/actuator/loggers/single/curl-request.adoc[] + +The preceding example retrieves information about the logger named `com.example`. +The resulting response is similar to the following: + +include::partial$rest/actuator/loggers/single/http-response.adoc[] + + + +[[loggers.single.response-structure]] +=== Response Structure + +The response contains details of the requested logger. +The following table describes the structure of the response: + +[cols="3,1,3"] +include::partial$rest/actuator/loggers/single/response-fields.adoc[] + + + +[[loggers.group]] +== Retrieving a Single Group + +To retrieve a single group, make a `GET` request to `/actuator/loggers/{group.name}`, +as shown in the following curl-based example: + +include::partial$rest/actuator/loggers/group/curl-request.adoc[] + +The preceding example retrieves information about the logger group named `test`. +The resulting response is similar to the following: + +include::partial$rest/actuator/loggers/group/http-response.adoc[] + + + +[[loggers.group.response-structure]] +=== Response Structure + +The response contains details of the requested group. +The following table describes the structure of the response: + +[cols="3,1,3"] +include::partial$rest/actuator/loggers/group/response-fields.adoc[] + + + +[[loggers.setting-level]] +== Setting a Log Level + +To set the level of a logger, make a `POST` request to `/actuator/loggers/{logger.name}` with a JSON body that specifies the configured level for the logger, as shown in the following curl-based example: + +include::partial$rest/actuator/loggers/set/curl-request.adoc[] + +The preceding example sets the `configuredLevel` of the `com.example` logger to `DEBUG`. + + + +[[loggers.setting-level.request-structure]] +=== Request Structure + +The request specifies the desired level of the logger. +The following table describes the structure of the request: + +[cols="3,1,3"] +include::partial$rest/actuator/loggers/set/request-fields.adoc[] + + + +[[loggers.group-setting-level]] +== Setting a Log Level for a Group + +To set the level of a logger, make a `POST` request to `/actuator/loggers/{group.name}` with a JSON body that specifies the configured level for the logger group, as shown in the following curl-based example: + +include::partial$rest/actuator/loggers/setGroup/curl-request.adoc[] + +The preceding example sets the `configuredLevel` of the `test` logger group to `DEBUG`. + + + +[[loggers.group-setting-level.request-structure]] +=== Request Structure + +The request specifies the desired level of the logger group. +The following table describes the structure of the request: + +[cols="3,1,3"] +include::partial$rest/actuator/loggers/set/request-fields.adoc[] + + + +[[loggers.clearing-level]] +== Clearing a Log Level + +To clear the level of a logger, make a `POST` request to `/actuator/loggers/{logger.name}` with a JSON body containing an empty object, as shown in the following curl-based example: + +include::partial$rest/actuator/loggers/clear/curl-request.adoc[] + +The preceding example clears the configured level of the `com.example` logger. diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/mappings.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/mappings.adoc similarity index 80% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/mappings.adoc rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/mappings.adoc index 350c5d16f547..58015959bdc5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/mappings.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/mappings.adoc @@ -1,29 +1,32 @@ [[mappings]] = Mappings (`mappings`) + The `mappings` endpoint provides information about the application's request mappings. [[mappings.retrieving]] == Retrieving the Mappings + To retrieve the mappings, make a `GET` request to `/actuator/mappings`, as shown in the following curl-based example: -include::{snippets}/mappings/curl-request.adoc[] +include::partial$rest/actuator/mappings/curl-request.adoc[] The resulting response is similar to the following: -include::{snippets}/mappings/http-response.adoc[] +include::partial$rest/actuator/mappings/http-response.adoc[] [[mappings.retrieving.response-structure]] === Response Structure + The response contains details of the application's mappings. The items found in the response depend on the type of web application (reactive or Servlet-based). The following table describes the structure of the common elements of the response: [cols="2,1,3"] -include::{snippets}/mappings/response-fields.adoc[] +include::partial$rest/actuator/mappings/response-fields.adoc[] The entries that may be found in `contexts.*.mappings` are described in the following sections. @@ -31,38 +34,42 @@ The entries that may be found in `contexts.*.mappings` are described in the foll [[mappings.retrieving.response-structure-dispatcher-servlets]] === Dispatcher Servlets Response Structure + When using Spring MVC, the response contains details of any `DispatcherServlet` request mappings beneath `contexts.*.mappings.dispatcherServlets`. The following table describes the structure of this section of the response: [cols="4,1,2"] -include::{snippets}/mappings/response-fields-dispatcher-servlets.adoc[] +include::partial$rest/actuator/mappings/response-fields-dispatcher-servlets.adoc[] [[mappings.retrieving.response-structure-servlets]] === Servlets Response Structure + When using the Servlet stack, the response contains details of any `Servlet` mappings beneath `contexts.*.mappings.servlets`. The following table describes the structure of this section of the response: [cols="2,1,3"] -include::{snippets}/mappings/response-fields-servlets.adoc[] +include::partial$rest/actuator/mappings/response-fields-servlets.adoc[] [[mappings.retrieving.response-structure-servlet-filters]] === Servlet Filters Response Structure + When using the Servlet stack, the response contains details of any `Filter` mappings beneath `contexts.*.mappings.servletFilters`. The following table describes the structure of this section of the response: [cols="2,1,3"] -include::{snippets}/mappings/response-fields-servlet-filters.adoc[] +include::partial$rest/actuator/mappings/response-fields-servlet-filters.adoc[] [[mappings.retrieving.response-structure-dispatcher-handlers]] === Dispatcher Handlers Response Structure + When using Spring WebFlux, the response contains details of any `DispatcherHandler` request mappings beneath `contexts.*.mappings.dispatcherHandlers`. The following table describes the structure of this section of the response: [cols="4,1,2"] -include::{snippets}/mappings/response-fields-dispatcher-handlers.adoc[] +include::partial$rest/actuator/mappings/response-fields-dispatcher-handlers.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/metrics.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/metrics.adoc new file mode 100644 index 000000000000..fe153d63efd2 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/metrics.adoc @@ -0,0 +1,77 @@ +[[metrics]] += Metrics (`metrics`) + +The `metrics` endpoint provides access to application metrics. + + + +[[metrics.retrieving-names]] +== Retrieving Metric Names + +To retrieve the names of the available metrics, make a `GET` request to `/actuator/metrics`, as shown in the following curl-based example: + +include::partial$rest/actuator/metrics/names/curl-request.adoc[] + +The resulting response is similar to the following: + +include::partial$rest/actuator/metrics/names/http-response.adoc[] + + + +[[metrics.retrieving-names.response-structure]] +=== Response Structure + +The response contains details of the metric names. +The following table describes the structure of the response: + +[cols="3,1,2"] +include::partial$rest/actuator/metrics/names/response-fields.adoc[] + + + +[[metrics.retrieving-metric]] +== Retrieving a Metric + +To retrieve a metric, make a `GET` request to `/actuator/metrics/{metric.name}`, as shown in the following curl-based example: + +include::partial$rest/actuator/metrics/metric/curl-request.adoc[] + +The preceding example retrieves information about the metric named `jvm.memory.max`. +The resulting response is similar to the following: + +include::partial$rest/actuator/metrics/metric/http-response.adoc[] + + + +[[metrics.retrieving-metric.query-parameters]] +=== Query Parameters + +The endpoint uses query parameters to xref:rest/actuator/metrics.adoc#metrics.drilling-down[drill down] into a metric by using its tags. +The following table shows the single supported query parameter: + +[cols="2,4"] +include::partial$rest/actuator/metrics/metric-with-tags/query-parameters.adoc[] + + + +[[metrics.retrieving-metric.response-structure]] +=== Response Structure + +The response contains details of the metric. +The following table describes the structure of the response: + +include::partial$rest/actuator/metrics/metric/response-fields.adoc[] + + + +[[metrics.drilling-down]] +== Drilling Down + +To drill down into a metric, make a `GET` request to `/actuator/metrics/{metric.name}` using the `tag` query parameter, as shown in the following curl-based example: + +include::partial$rest/actuator/metrics/metric-with-tags/curl-request.adoc[] + +The preceding example retrieves the `jvm.memory.max` metric, where the `area` tag has a value of `nonheap` and the `id` attribute has a value of `Compressed Class Space`. +The resulting response is similar to the following: + +include::partial$rest/actuator/metrics/metric-with-tags/http-response.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/prometheus.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/prometheus.adoc new file mode 100644 index 000000000000..c405236a549e --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/prometheus.adoc @@ -0,0 +1,51 @@ +[[prometheus]] += Prometheus (`prometheus`) + +The `prometheus` endpoint provides Spring Boot application's metrics in the format required for scraping by a Prometheus server. + + + +[[prometheus.retrieving]] +== Retrieving All Metrics + +To retrieve all metrics, make a `GET` request to `/actuator/prometheus`, as shown in the following curl-based example: + +include::partial$rest/actuator/prometheus/all/curl-request.adoc[] + +The resulting response is similar to the following: + +include::partial$rest/actuator/prometheus/all/http-response.adoc[] + +The default response content type is `text/plain;version=0.0.4`. +The endpoint can also produce `application/openmetrics-text;version=1.0.0` when called with an appropriate `Accept` header, as shown in the following curl-based example: + +include::partial$rest/actuator/prometheus/openmetrics/curl-request.adoc[] + +The resulting response is similar to the following: + +include::partial$rest/actuator/prometheus/openmetrics/http-response.adoc[] + + + +[[prometheus.retrieving.query-parameters]] +=== Query Parameters + +The endpoint uses query parameters to limit the samples that it returns. +The following table shows the supported query parameters: + +[cols="2,4"] +include::partial$rest/actuator/prometheus/names/query-parameters.adoc[] + + + +[[prometheus.retrieving-names]] +== Retrieving Filtered Metrics + +To retrieve metrics matching specific names, make a `GET` request to `/actuator/prometheus` with the `includedNames` query parameter, as shown in the following curl-based example: + +include::partial$rest/actuator/prometheus/names/curl-request.adoc[] + +The resulting response is similar to the following: + +include::partial$rest/actuator/prometheus/names/http-response.adoc[] + diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/quartz.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/quartz.adoc new file mode 100644 index 000000000000..9bb74a5f26fe --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/quartz.adoc @@ -0,0 +1,276 @@ +[[quartz]] += Quartz (`quartz`) + +The `quartz` endpoint provides information about jobs and triggers that are managed by the Quartz Scheduler. + + + +[[quartz.report]] +== Retrieving Registered Groups + +Jobs and triggers are managed in groups. +To retrieve the list of registered job and trigger groups, make a `GET` request to `/actuator/quartz`, as shown in the following curl-based example: + +include::partial$rest/actuator/quartz/report/curl-request.adoc[] + +The resulting response is similar to the following: + +include::partial$rest/actuator/quartz/report/http-response.adoc[] + + + +[[quartz.report.response-structure]] +=== Response Structure + +The response contains the groups names for registered jobs and triggers. +The following table describes the structure of the response: + +[cols="3,1,3"] +include::partial$rest/actuator/quartz/report/response-fields.adoc[] + + + +[[quartz.job-groups]] +== Retrieving Registered Job Names + +To retrieve the list of registered job names, make a `GET` request to `/actuator/quartz/jobs`, as shown in the following curl-based example: + +include::partial$rest/actuator/quartz/jobs/curl-request.adoc[] + +The resulting response is similar to the following: + +include::partial$rest/actuator/quartz/jobs/http-response.adoc[] + + + +[[quartz.job-groups.response-structure]] +=== Response Structure + +The response contains the registered job names for each group. +The following table describes the structure of the response: + +[cols="3,1,3"] +include::partial$rest/actuator/quartz/jobs/response-fields.adoc[] + + + +[[quartz.trigger-groups]] +== Retrieving Registered Trigger Names + +To retrieve the list of registered trigger names, make a `GET` request to `/actuator/quartz/triggers`, as shown in the following curl-based example: + +include::partial$rest/actuator/quartz/triggers/curl-request.adoc[] + +The resulting response is similar to the following: + +include::partial$rest/actuator/quartz/triggers/http-response.adoc[] + + + +[[quartz.trigger-groups.response-structure]] +=== Response Structure + +The response contains the registered trigger names for each group. +The following table describes the structure of the response: + +[cols="3,1,3"] +include::partial$rest/actuator/quartz/triggers/response-fields.adoc[] + + + +[[quartz.job-group]] +== Retrieving Overview of a Job Group + +To retrieve an overview of the jobs in a particular group, make a `GET` request to `/actuator/quartz/jobs/\{groupName}`, as shown in the following curl-based example: + +include::partial$rest/actuator/quartz/job-group/curl-request.adoc[] + +The preceding example retrieves the summary for jobs in the `samples` group. +The resulting response is similar to the following: + +include::partial$rest/actuator/quartz/job-group/http-response.adoc[] + + + +[[quartz.job-group.response-structure]] +=== Response Structure + +The response contains an overview of jobs in a particular group. +The following table describes the structure of the response: + +[cols="3,1,3"] +include::partial$rest/actuator/quartz/job-group/response-fields.adoc[] + + + +[[quartz.trigger-group]] +== Retrieving Overview of a Trigger Group + +To retrieve an overview of the triggers in a particular group, make a `GET` request to `/actuator/quartz/triggers/\{groupName}`, as shown in the following curl-based example: + +include::partial$rest/actuator/quartz/trigger-group/curl-request.adoc[] + +The preceding example retrieves the summary for triggers in the `tests` group. +The resulting response is similar to the following: + +include::partial$rest/actuator/quartz/trigger-group/http-response.adoc[] + + + +[[quartz.trigger-group.response-structure]] +=== Response Structure + +The response contains an overview of triggers in a particular group. +Trigger implementation specific details are available. +The following table describes the structure of the response: + +[cols="3,1,3"] +include::partial$rest/actuator/quartz/trigger-group/response-fields.adoc[] + + + +[[quartz.job]] +== Retrieving Details of a Job + +To retrieve the details about a particular job, make a `GET` request to `/actuator/quartz/jobs/\{groupName}/\{jobName}`, as shown in the following curl-based example: + +include::partial$rest/actuator/quartz/job-details/curl-request.adoc[] + +The preceding example retrieves the details of the job identified by the `samples` group and `jobOne` name. +The resulting response is similar to the following: + +include::partial$rest/actuator/quartz/job-details/http-response.adoc[] + +If a key in the data map is identified as sensitive, its value is sanitized. + + + +[[quartz.job.response-structure]] +=== Response Structure + +The response contains the full details of a job including a summary of the triggers associated with it, if any. +The triggers are sorted by next fire time and priority. +The following table describes the structure of the response: + +[cols="2,1,3"] +include::partial$rest/actuator/quartz/job-details/response-fields.adoc[] + + + +[[quartz.trigger]] +== Retrieving Details of a Trigger + +To retrieve the details about a particular trigger, make a `GET` request to `/actuator/quartz/triggers/\{groupName}/\{triggerName}`, as shown in the following curl-based example: + +include::partial$rest/actuator/quartz/trigger-details-cron/curl-request.adoc[] + +The preceding example retrieves the details of trigger identified by the `samples` group and `example` name. + + + +[[quartz.trigger.common-response-structure]] +=== Common Response Structure + +The response has a common structure and an additional object that is specific to the trigger's type. +There are five supported types: + +* `cron` for `CronTrigger` +* `simple` for `SimpleTrigger` +* `dailyTimeInterval` for `DailyTimeIntervalTrigger` +* `calendarInterval` for `CalendarIntervalTrigger` +* `custom` for any other trigger implementations + +The following table describes the structure of the common elements of the response: + +[cols="2,1,3"] +include::partial$rest/actuator/quartz/trigger-details-common/response-fields.adoc[] + + + +[[quartz.trigger.cron-response-structure]] +=== Cron Trigger Response Structure + +A cron trigger defines the cron expression that is used to determine when it has to fire. +The resulting response for such a trigger implementation is similar to the following: + +include::partial$rest/actuator/quartz/trigger-details-cron/http-response.adoc[] + + +Much of the response is common to all trigger types. +The structure of the common elements of the response was xref:rest/actuator/quartz.adoc#quartz.trigger.common-response-structure[described previously]. +The following table describes the structure of the parts of the response that are specific to cron triggers: + +[cols="2,1,3"] +include::partial$rest/actuator/quartz/trigger-details-cron/response-fields.adoc[] + + + +[[quartz.trigger.simple-response-structure]] +=== Simple Trigger Response Structure + +A simple trigger is used to fire a Job at a given moment in time, and optionally repeated at a specified interval. +The resulting response for such a trigger implementation is similar to the following: + +include::partial$rest/actuator/quartz/trigger-details-simple/http-response.adoc[] + + +Much of the response is common to all trigger types. +The structure of the common elements of the response was xref:rest/actuator/quartz.adoc#quartz.trigger.common-response-structure[described previously]. +The following table describes the structure of the parts of the response that are specific to simple triggers: + +[cols="2,1,3"] +include::partial$rest/actuator/quartz/trigger-details-simple/response-fields.adoc[] + + + +[[quartz.trigger.daily-time-interval-response-structure]] +=== Daily Time Interval Trigger Response Structure + +A daily time interval trigger is used to fire a Job based upon daily repeating time intervals. +The resulting response for such a trigger implementation is similar to the following: + +include::partial$rest/actuator/quartz/trigger-details-daily-time-interval/http-response.adoc[] + + +Much of the response is common to all trigger types. +The structure of the common elements of the response was xref:rest/actuator/quartz.adoc#quartz.trigger.common-response-structure[described previously]. +The following table describes the structure of the parts of the response that are specific to daily time interval triggers: + +[cols="2,1,3"] +include::partial$rest/actuator/quartz/trigger-details-daily-time-interval/response-fields.adoc[] + + + +[[quartz.trigger.calendar-interval-response-structure]] +=== Calendar Interval Trigger Response Structure + +A calendar interval trigger is used to fire a Job based upon repeating calendar time intervals. +The resulting response for such a trigger implementation is similar to the following: + +include::partial$rest/actuator/quartz/trigger-details-calendar-interval/http-response.adoc[] + + +Much of the response is common to all trigger types. +The structure of the common elements of the response was xref:rest/actuator/quartz.adoc#quartz.trigger.common-response-structure[described previously]. +The following table describes the structure of the parts of the response that are specific to calendar interval triggers: + +[cols="2,1,3"] +include::partial$rest/actuator/quartz/trigger-details-calendar-interval/response-fields.adoc[] + + + +[[quartz.trigger.custom-response-structure]] +=== Custom Trigger Response Structure + +A custom trigger is any other implementation. +The resulting response for such a trigger implementation is similar to the following: + +include::partial$rest/actuator/quartz/trigger-details-custom/http-response.adoc[] + + +Much of the response is common to all trigger types. +The structure of the common elements of the response was xref:rest/actuator/quartz.adoc#quartz.trigger.common-response-structure[described previously]. +The following table describes the structure of the parts of the response that are specific to custom triggers: + +[cols="2,1,3"] +include::partial$rest/actuator/quartz/trigger-details-custom/response-fields.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/sbom.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/sbom.adoc new file mode 100644 index 000000000000..215b40ad2930 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/sbom.adoc @@ -0,0 +1,66 @@ +[[sbom]] += Software Bill of Materials (`sbom`) + +The `sbom` endpoint provides information about the software bill of materials (SBOM). + + + +[[sbom.retrieving-available-sboms]] +== Retrieving the Available SBOMs + +To retrieve the available SBOMs, make a `GET` request to `/actuator/sbom`, as shown in the following curl-based example: + +include::partial$rest/actuator/sbom/curl-request.adoc[] + +The resulting response is similar to the following: + +include::partial$rest/actuator/sbom/http-response.adoc[] + + + +[[sbom.retrieving-available-sboms.response-structure]] +=== Response Structure + +The response contains the available SBOMs. +The following table describes the structure of the response: + +[cols="2,1,3"] +include::partial$rest/actuator/sbom/response-fields.adoc[] + + + +[[sbom.retrieving-single-sbom]] +== Retrieving a Single SBOM + +To retrieve the available SBOMs, make a `GET` request to `/actuator/sbom/\{id}`, as shown in the following curl-based example: + +include::partial$rest/actuator/sbom/id/curl-request.adoc[] + +The preceding example retrieves the SBOM named application. +The resulting response depends on the format of the SBOM. +This example uses the CycloneDX format. + +[source,http,options="nowrap"] +---- +HTTP/1.1 200 OK +Content-Type: application/vnd.cyclonedx+json +Accept-Ranges: bytes +Content-Length: 160316 + +{ + "bomFormat" : "CycloneDX", + "specVersion" : "1.5", + "serialNumber" : "urn:uuid:13862013-3360-43e5-8055-3645aa43c548", + "version" : 1, + // ... +} +---- + + + +[[sbom.retrieving-single-sbom.response-structure]] +=== Response Structure +The response depends on the format of the SBOM: + +* https://cyclonedx.org/specification/overview/[CycloneDX] + diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/scheduledtasks.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/scheduledtasks.adoc similarity index 75% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/scheduledtasks.adoc rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/scheduledtasks.adoc index bde0b5397f59..6023bd70a17a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/scheduledtasks.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/scheduledtasks.adoc @@ -1,25 +1,28 @@ [[scheduled-tasks]] = Scheduled Tasks (`scheduledtasks`) + The `scheduledtasks` endpoint provides information about the application's scheduled tasks. [[scheduled-tasks.retrieving]] == Retrieving the Scheduled Tasks + To retrieve the scheduled tasks, make a `GET` request to `/actuator/scheduledtasks`, as shown in the following curl-based example: -include::{snippets}/scheduled-tasks/curl-request.adoc[] +include::partial$rest/actuator/scheduled-tasks/curl-request.adoc[] The resulting response is similar to the following: -include::{snippets}/scheduled-tasks/http-response.adoc[] +include::partial$rest/actuator/scheduled-tasks/http-response.adoc[] [[scheduled-tasks.retrieving.response-structure]] === Response Structure + The response contains details of the application's scheduled tasks. The following table describes the structure of the response: [cols="2,1,3"] -include::{snippets}/scheduled-tasks/response-fields.adoc[] +include::partial$rest/actuator/scheduled-tasks/response-fields.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/sessions.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/sessions.adoc similarity index 75% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/sessions.adoc rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/sessions.adoc index d921bcda8f18..f6cea0ae5e9b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/sessions.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/sessions.adoc @@ -1,69 +1,76 @@ [[sessions]] = Sessions (`sessions`) + The `sessions` endpoint provides information about the application's HTTP sessions that are managed by Spring Session. [[sessions.retrieving]] == Retrieving Sessions + To retrieve the sessions, make a `GET` request to `/actuator/sessions`, as shown in the following curl-based example: -include::{snippets}/sessions/username/curl-request.adoc[] +include::partial$rest/actuator/sessions/username/curl-request.adoc[] The preceding examples retrieves all of the sessions for the user whose username is `alice`. The resulting response is similar to the following: -include::{snippets}/sessions/username/http-response.adoc[] +include::partial$rest/actuator/sessions/username/http-response.adoc[] [[sessions.retrieving.query-parameters]] === Query Parameters + The endpoint uses query parameters to limit the sessions that it returns. The following table shows the single required query parameter: [cols="2,4"] -include::{snippets}/sessions/username/query-parameters.adoc[] +include::partial$rest/actuator/sessions/username/query-parameters.adoc[] [[sessions.retrieving.response-structure]] === Response Structure + The response contains details of the matching sessions. The following table describes the structure of the response: [cols="3,1,3"] -include::{snippets}/sessions/username/response-fields.adoc[] +include::partial$rest/actuator/sessions/username/response-fields.adoc[] [[sessions.retrieving-id]] == Retrieving a Single Session + To retrieve a single session, make a `GET` request to `/actuator/sessions/\{id}`, as shown in the following curl-based example: -include::{snippets}/sessions/id/curl-request.adoc[] +include::partial$rest/actuator/sessions/id/curl-request.adoc[] The preceding example retrieves the session with the `id` of `4db5efcc-99cb-4d05-a52c-b49acfbb7ea9`. The resulting response is similar to the following: -include::{snippets}/sessions/id/http-response.adoc[] +include::partial$rest/actuator/sessions/id/http-response.adoc[] [[sessions.retrieving-id.response-structure]] === Response Structure + The response contains details of the requested session. The following table describes the structure of the response: [cols="3,1,3"] -include::{snippets}/sessions/id/response-fields.adoc[] +include::partial$rest/actuator/sessions/id/response-fields.adoc[] [[sessions.deleting]] == Deleting a Session + To delete a session, make a `DELETE` request to `/actuator/sessions/\{id}`, as shown in the following curl-based example: -include::{snippets}/sessions/delete/curl-request.adoc[] +include::partial$rest/actuator/sessions/delete/curl-request.adoc[] The preceding example deletes the session with the `id` of `4db5efcc-99cb-4d05-a52c-b49acfbb7ea9`. diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/shutdown.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/shutdown.adoc new file mode 100644 index 000000000000..d3f334b3c212 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/shutdown.adoc @@ -0,0 +1,28 @@ +[[shutdown]] += Shutdown (`shutdown`) + +The `shutdown` endpoint is used to shut down the application. + + + +[[shutdown.shutting-down]] +== Shutting Down the Application + +To shut down the application, make a `POST` request to `/actuator/shutdown`, as shown in the following curl-based example: + +include::partial$rest/actuator/shutdown/curl-request.adoc[] + +A response similar to the following is produced: + +include::partial$rest/actuator/shutdown/http-response.adoc[] + + + +[[shutdown.shutting-down.response-structure]] +=== Response Structure + +The response contains details of the result of the shutdown request. +The following table describes the structure of the response: + +[cols="3,1,3"] +include::partial$rest/actuator/shutdown/response-fields.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/startup.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/startup.adoc similarity index 78% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/startup.adoc rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/startup.adoc index 8675c75a28fc..f58eadf22912 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/startup.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/startup.adoc @@ -1,43 +1,48 @@ [[startup]] = Application Startup (`startup`) + The `startup` endpoint provides information about the application's startup sequence. [[startup.retrieving]] == Retrieving the Application Startup Steps + The application startup steps can either be retrieved as a snapshot (`GET`) or drained from the buffer (`POST`). [[startup.retrieving.snapshot]] === Retrieving a snapshot of the Application Startup Steps + To retrieve the steps recorded so far during the application startup phase, make a `GET` request to `/actuator/startup`, as shown in the following curl-based example: -include::{snippets}/startup-snapshot/curl-request.adoc[] +include::partial$rest/actuator/startup-snapshot/curl-request.adoc[] The resulting response is similar to the following: -include::{snippets}/startup-snapshot/http-response.adoc[] +include::partial$rest/actuator/startup-snapshot/http-response.adoc[] [[startup.retrieving.drain]] === Draining the Application Startup Steps + To drain and return the steps recorded so far during the application startup phase, make a `POST` request to `/actuator/startup`, as shown in the following curl-based example: -include::{snippets}/startup/curl-request.adoc[] +include::partial$rest/actuator/startup/curl-request.adoc[] The resulting response is similar to the following: -include::{snippets}/startup/http-response.adoc[] +include::partial$rest/actuator/startup/http-response.adoc[] [[startup.retrieving.response-structure]] === Response Structure + The response contains details of the application startup steps. The following table describes the structure of the response: [cols="2,1,3"] -include::{snippets}/startup/response-fields.adoc[] +include::partial$rest/actuator/startup/response-fields.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/threaddump.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/threaddump.adoc new file mode 100644 index 000000000000..68d72eb4fcdc --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/pages/rest/actuator/threaddump.adoc @@ -0,0 +1,42 @@ +[[threaddump]] += Thread Dump (`threaddump`) + +The `threaddump` endpoint provides a thread dump from the application's JVM. + + + +[[threaddump.retrieving-json]] +== Retrieving the Thread Dump as JSON + +To retrieve the thread dump as JSON, make a `GET` request to `/actuator/threaddump` with an appropriate `Accept` header, as shown in the following curl-based example: + +include::partial$rest/actuator/threaddump/json/curl-request.adoc[] + +The resulting response is similar to the following: + +include::partial$rest/actuator/threaddump/json/http-response.adoc[] + + + +[[threaddump.retrieving-json.response-structure]] +=== Response Structure + +The response contains details of the JVM's threads. +The following table describes the structure of the response: + +[cols="3,1,2"] +include::partial$rest/actuator/threaddump/json/response-fields.adoc[] + + + +[[threaddump.retrieving-text]] +== Retrieving the Thread Dump as Text + +To retrieve the thread dump as text, make a `GET` request to `/actuator/threaddump` that +accepts `text/plain`, as shown in the following curl-based example: + +include::partial$rest/actuator/threaddump/text/curl-request.adoc[] + +The resulting response is similar to the following: + +include::partial$rest/actuator/threaddump/text/http-response.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/partials/nav-actuator-rest-api.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/partials/nav-actuator-rest-api.adoc new file mode 100644 index 000000000000..2dd16596f71d --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/antora/modules/api/partials/nav-actuator-rest-api.adoc @@ -0,0 +1,26 @@ +* xref:api:rest/actuator/index.adoc[] +** xref:api:rest/actuator/auditevents.adoc[] +** xref:api:rest/actuator/beans.adoc[] +** xref:api:rest/actuator/caches.adoc[] +** xref:api:rest/actuator/conditions.adoc[] +** xref:api:rest/actuator/configprops.adoc[] +** xref:api:rest/actuator/env.adoc[] +** xref:api:rest/actuator/flyway.adoc[] +** xref:api:rest/actuator/health.adoc[] +** xref:api:rest/actuator/heapdump.adoc[] +** xref:api:rest/actuator/httpexchanges.adoc[] +** xref:api:rest/actuator/info.adoc[] +** xref:api:rest/actuator/integrationgraph.adoc[] +** xref:api:rest/actuator/liquibase.adoc[] +** xref:api:rest/actuator/logfile.adoc[] +** xref:api:rest/actuator/loggers.adoc[] +** xref:api:rest/actuator/mappings.adoc[] +** xref:api:rest/actuator/metrics.adoc[] +** xref:api:rest/actuator/prometheus.adoc[] +** xref:api:rest/actuator/quartz.adoc[] +** xref:api:rest/actuator/sbom.adoc[] +** xref:api:rest/actuator/scheduledtasks.adoc[] +** xref:api:rest/actuator/sessions.adoc[] +** xref:api:rest/actuator/shutdown.adoc[] +** xref:api:rest/actuator/startup.adoc[] +** xref:api:rest/actuator/threaddump.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/anchor-rewrite.properties b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/anchor-rewrite.properties deleted file mode 100644 index d68c413f9e9e..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/anchor-rewrite.properties +++ /dev/null @@ -1,138 +0,0 @@ -overview=overview -overview-endpoint-urls=overview.endpoint-urls -overview-timestamps=overview.timestamps -audit-events=audit-events -audit-events-retrieving=audit-events.retrieving -audit-events-retrieving-query-parameters=audit-events.retrieving.query-parameters -audit-events-retrieving-response-structure=audit-events.retrieving.response-structure -beans=beans -beans-retrieving=beans.retrieving -beans-retrieving-response-structure=beans.retrieving.response-structure -caches=caches -caches-all=caches.all -caches-all-response-structure=caches.all.response-structure -caches-named=caches.named -caches-named-query-parameters=caches.named.query-parameters -caches-named-response-structure=caches.named.response-structure -caches-evict-all=caches.evict-all -caches-evict-named=caches.evict-named -caches-evict-named-request-structure=caches.evict-named.request-structure -conditions=conditions -conditions-retrieving=conditions.retrieving -conditions-retrieving-response-structure=conditions.retrieving.response-structure -configprops=configprops -configprops-retrieving=configprops.retrieving -configprops-retrieving-response-structure=configprops.retrieving.response-structure -configprops-retrieving-by-prefix=configprops.retrieving-by-prefix -configprops-retrieving-by-prefix-response-structure=configprops.retrieving-by-prefix.response-structure -env=env -env-entire=env.entire -env-entire-response-structure=env.entire.response-structure -env-single-property=env.single-property -env-single-response-structure=env.single-property.response-structure -flyway=flyway -flyway-retrieving=flyway.retrieving -flyway-retrieving-response-structure=flyway.retrieving.response-structure -health=health -health-retrieving=health.retrieving -health-retrieving-response-structure=health.retrieving.response-structure -health-retrieving-component=health.retrieving-component -health-retrieving-component-response-structure=health.retrieving-component.response-structure -health-retrieving-component-nested=health.retrieving-component-nested -health-retrieving-component-instance-response-structure=health.retrieving-component-nested.response-structure -heapdump=heapdump -heapdump-retrieving=heapdump.retrieving -http-trace=http-trace -http-trace-retrieving=http-trace.retrieving -http-trace-retrieving-response-structure=http-trace.retrieving.response-structure -info=info -info-retrieving=info.retrieving -info-retrieving-response-structure=info.retrieving.response-structure -info-retrieving-response-structure-build=info.retrieving.response-structure.build -info-retrieving-response-structure-git=info.retrieving.response-structure.git -integrationgraph=integrationgraph -integrationgraph-retrieving=integrationgraph.retrieving -integrationgraph-retrieving-response-structure=integrationgraph.retrieving.response-structure -integrationgraph-rebuilding=integrationgraph.rebuilding -liquibase=liquibase -liquibase-retrieving=liquibase.retrieving -liquibase-retrieving-response-structure=liquibase.retrieving.response-structure -log-file=logfile -logfile-retrieving=logfile.retrieving -logfile-retrieving-part=logfile.retrieving-part -loggers=loggers -loggers-all=loggers.all -loggers-all-response-structure=loggers.all.response-structure -loggers-single=loggers.single -loggers-single-response-structure=loggers.single.response-structure -loggers-group=loggers.group -loggers-group-response-structure=loggers.group.response-structure -loggers-setting-level=loggers.setting-level -loggers-setting-level-request-structure=loggers.setting-level.request-structure -loggers-group-setting-level=loggers.group-setting-level -loggers-group-setting-level-request-structure=loggers.group-setting-level.request-structure -loggers-clearing-level=loggers.clearing-level -mappings=mappings -mappings-retrieving=mappings.retrieving -mappings-retrieving-response-structure=mappings.retrieving.response-structure -mappings-retrieving-response-structure-dispatcher-servlets=mappings.retrieving.response-structure-dispatcher-servlets -mappings-retrieving-response-structure-servlets=mappings.retrieving.response-structure-servlets -mappings-retrieving-response-structure-servlet-filters=mappings.retrieving.response-structure-servlet-filters -mappings-retrieving-response-structure-dispatcher-handlers=mappings.retrieving.response-structure-dispatcher-handlers -metrics=metrics -metrics-retrieving-names=metrics.retrieving-names -metrics-retrieving-names-response-structure=metrics.retrieving-names.response-structure -metrics-retrieving-metric=metrics.retrieving-metric -metrics-retrieving-metric-query-parameters=metrics.retrieving-metric.query-parameters -metrics-retrieving-metric-response-structure=metrics.retrieving-metric.response-structure -metrics-drilling-down=metrics.drilling-down -prometheus=prometheus -prometheus-retrieving=prometheus.retrieving -prometheus-retrieving-query-parameters=prometheus.retrieving.query-parameters -prometheus-retrieving-names=prometheus.retrieving-names -quartz=quartz -quartz-report=quartz.report -quartz-report-response-structure=quartz.report.response-structure -quartz-job-groups=quartz.job-groups -quartz-job-groups-response-structure=quartz.job-groups.response-structure -quartz-trigger-groups=quartz.trigger-groups -quartz-trigger-groups-response-structure=quartz.trigger-groups.response-structure -quartz-job-group=quartz.job-group -quartz-job-group-response-structure=quartz.job-group.response-structure -quartz-trigger-group=quartz.trigger-group -quartz-trigger-group-response-structure=quartz.trigger-group.response-structure -quartz-job=quartz.job -quartz-job-response-structure=quartz.job.response-structure -quartz-trigger=quartz.trigger -quartz-trigger-common-response-structure=quartz.trigger.common-response-structure -quartz-trigger-cron-response-structure=quartz.trigger.cron-response-structure -quartz-trigger-simple-response-structure=quartz.trigger.simple-response-structure -quartz-trigger-daily-time-interval-response-structure=quartz.trigger.daily-time-interval-response-structure -quartz-trigger-calendar-interval-response-structure=quartz.trigger.calendar-interval-response-structure -quartz-trigger-custom-response-structure=quartz.trigger.custom-response-structure -scheduled-tasks=scheduled-tasks -scheduled-tasks-retrieving=scheduled-tasks.retrieving -scheduled-tasks-retrieving-response-structure=scheduled-tasks.retrieving.response-structure -sessions=sessions -sessions-retrieving=sessions.retrieving -sessions-retrieving-query-parameters=sessions.retrieving.query-parameters -sessions-retrieving-response-structure=sessions.retrieving.response-structure -sessions-retrieving-id=sessions.retrieving-id -sessions-retrieving-id-response-structure=sessions.retrieving-id.response-structure -sessions-deleting=sessions.deleting -shutdown=shutdown -shutdown-shutting-down=shutdown.shutting-down -shutdown-shutting-down-response-structure=shutdown.shutting-down.response-structure -startup=startup -startup-retrieving=startup.retrieving -startup-retrieving-snapshot=startup.retrieving.snapshot -startup-retrieving-drain=startup.retrieving.drain -startup-retrieving-response-structure=startup.retrieving.response-structure -threaddump=threaddump -threaddump-retrieving-json=threaddump.retrieving-json -threaddump-retrieving-json-response-structure=threaddump.retrieving-json.response-structure -threaddump-retrieving-text=threaddump.retrieving-text -http-trace=httpexchanges -http-trace.retrieving=httpexchanges.retrieving -http-trace.retrieving.response-structure=httpexchanges.retrieving.response-structure - diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/beans.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/beans.adoc deleted file mode 100644 index d48cfff22ab7..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/beans.adoc +++ /dev/null @@ -1,25 +0,0 @@ -[[beans]] -= Beans (`beans`) -The `beans` endpoint provides information about the application's beans. - - - -[[beans.retrieving]] -== Retrieving the Beans -To retrieve the beans, make a `GET` request to `/actuator/beans`, as shown in the following curl-based example: - -include::{snippets}/beans/curl-request.adoc[] - -The resulting response is similar to the following: - -include::{snippets}/beans/http-response.adoc[] - - - -[[beans.retrieving.response-structure]] -=== Response Structure -The response contains details of the application's beans. -The following table describes the structure of the response: - -[cols="2,1,3"] -include::{snippets}/beans/response-fields.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/integrationgraph.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/integrationgraph.adoc deleted file mode 100644 index 5709ca935833..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/integrationgraph.adoc +++ /dev/null @@ -1,34 +0,0 @@ -[[integrationgraph]] -= Spring Integration graph (`integrationgraph`) -The `integrationgraph` endpoint exposes a graph containing all Spring Integration components. - - - -[[integrationgraph.retrieving]] -== Retrieving the Spring Integration Graph -To retrieve the information about the application, make a `GET` request to `/actuator/integrationgraph`, as shown in the following curl-based example: - -include::{snippets}/integrationgraph/graph/curl-request.adoc[] - -The resulting response is similar to the following: - -include::{snippets}/integrationgraph/graph/http-response.adoc[] - - - -[[integrationgraph.retrieving.response-structure]] -=== Response Structure -The response contains all Spring Integration components used within the application, as well as the links between them. -More information about the structure can be found in the {spring-integration-docs}index-single.html#integration-graph[reference documentation]. - - - -[[integrationgraph.rebuilding]] -== Rebuilding the Spring Integration Graph -To rebuild the exposed graph, make a `POST` request to `/actuator/integrationgraph`, as shown in the following curl-based example: - -include::{snippets}/integrationgraph/rebuild/curl-request.adoc[] - -This will result in a `204 - No Content` response: - -include::{snippets}/integrationgraph/rebuild/http-response.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/logfile.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/logfile.adoc deleted file mode 100644 index 0fd16d6abd13..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/logfile.adoc +++ /dev/null @@ -1,30 +0,0 @@ -[[logfile]] -= Log File (`logfile`) -The `logfile` endpoint provides access to the contents of the application's log file. - - - -[[logfile.retrieving]] -== Retrieving the Log File -To retrieve the log file, make a `GET` request to `/actuator/logfile`, as shown in the following curl-based example: - -include::{snippets}/logfile/entire/curl-request.adoc[] - -The resulting response is similar to the following: - -include::{snippets}/logfile/entire/http-response.adoc[] - - - -[[logfile.retrieving-part]] -== Retrieving Part of the Log File -NOTE: Retrieving part of the log file is not supported when using Jersey. - -To retrieve part of the log file, make a `GET` request to `/actuator/logfile` by using the `Range` header, as shown in the following curl-based example: - -include::{snippets}/logfile/range/curl-request.adoc[] - -The preceding example retrieves the first 1024 bytes of the log file. -The resulting response is similar to the following: - -include::{snippets}/logfile/range/http-response.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/loggers.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/loggers.adoc deleted file mode 100644 index 36cf215dac18..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/loggers.adoc +++ /dev/null @@ -1,122 +0,0 @@ -[[loggers]] -= Loggers (`loggers`) -The `loggers` endpoint provides access to the application's loggers and the configuration of their levels. - - - -[[loggers.all]] -== Retrieving All Loggers -To retrieve the application's loggers, make a `GET` request to `/actuator/loggers`, as shown in the following curl-based example: - -include::{snippets}/loggers/all/curl-request.adoc[] - -The resulting response is similar to the following: - -include::{snippets}/loggers/all/http-response.adoc[] - - - -[[loggers.all.response-structure]] -=== Response Structure -The response contains details of the application's loggers. -The following table describes the structure of the response: - -[cols="3,1,3"] -include::{snippets}/loggers/all/response-fields.adoc[] - - - -[[loggers.single]] -== Retrieving a Single Logger -To retrieve a single logger, make a `GET` request to `/actuator/loggers/{logger.name}`, as shown in the following curl-based example: - -include::{snippets}/loggers/single/curl-request.adoc[] - -The preceding example retrieves information about the logger named `com.example`. -The resulting response is similar to the following: - -include::{snippets}/loggers/single/http-response.adoc[] - - - -[[loggers.single.response-structure]] -=== Response Structure -The response contains details of the requested logger. -The following table describes the structure of the response: - -[cols="3,1,3"] -include::{snippets}/loggers/single/response-fields.adoc[] - - - -[[loggers.group]] -== Retrieving a Single Group -To retrieve a single group, make a `GET` request to `/actuator/loggers/{group.name}`, -as shown in the following curl-based example: - -include::{snippets}/loggers/group/curl-request.adoc[] - -The preceding example retrieves information about the logger group named `test`. -The resulting response is similar to the following: - -include::{snippets}/loggers/group/http-response.adoc[] - - - -[[loggers.group.response-structure]] -=== Response Structure -The response contains details of the requested group. -The following table describes the structure of the response: - -[cols="3,1,3"] -include::{snippets}/loggers/group/response-fields.adoc[] - - - -[[loggers.setting-level]] -== Setting a Log Level -To set the level of a logger, make a `POST` request to `/actuator/loggers/{logger.name}` with a JSON body that specifies the configured level for the logger, as shown in the following curl-based example: - -include::{snippets}/loggers/set/curl-request.adoc[] - -The preceding example sets the `configuredLevel` of the `com.example` logger to `DEBUG`. - - - -[[loggers.setting-level.request-structure]] -=== Request Structure -The request specifies the desired level of the logger. -The following table describes the structure of the request: - -[cols="3,1,3"] -include::{snippets}/loggers/set/request-fields.adoc[] - - - -[[loggers.group-setting-level]] -== Setting a Log Level for a Group -To set the level of a logger, make a `POST` request to `/actuator/loggers/{group.name}` with a JSON body that specifies the configured level for the logger group, as shown in the following curl-based example: - -include::{snippets}/loggers/setGroup/curl-request.adoc[] - -The preceding example sets the `configuredLevel` of the `test` logger group to `DEBUG`. - - - -[[loggers.group-setting-level.request-structure]] -=== Request Structure -The request specifies the desired level of the logger group. -The following table describes the structure of the request: - -[cols="3,1,3"] -include::{snippets}/loggers/set/request-fields.adoc[] - - - -[[loggers.clearing-level]] -== Clearing a Log Level -To clear the level of a logger, make a `POST` request to `/actuator/loggers/{logger.name}` with a JSON body containing an empty object, as shown in the following curl-based example: - -include::{snippets}/loggers/clear/curl-request.adoc[] - -The preceding example clears the configured level of the `com.example` logger. diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/metrics.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/metrics.adoc deleted file mode 100644 index a7313d172116..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/metrics.adoc +++ /dev/null @@ -1,70 +0,0 @@ -[[metrics]] -= Metrics (`metrics`) -The `metrics` endpoint provides access to application metrics. - - - -[[metrics.retrieving-names]] -== Retrieving Metric Names -To retrieve the names of the available metrics, make a `GET` request to `/actuator/metrics`, as shown in the following curl-based example: - -include::{snippets}/metrics/names/curl-request.adoc[] - -The resulting response is similar to the following: - -include::{snippets}/metrics/names/http-response.adoc[] - - - -[[metrics.retrieving-names.response-structure]] -=== Response Structure -The response contains details of the metric names. -The following table describes the structure of the response: - -[cols="3,1,2"] -include::{snippets}/metrics/names/response-fields.adoc[] - - - -[[metrics.retrieving-metric]] -== Retrieving a Metric -To retrieve a metric, make a `GET` request to `/actuator/metrics/{metric.name}`, as shown in the following curl-based example: - -include::{snippets}/metrics/metric/curl-request.adoc[] - -The preceding example retrieves information about the metric named `jvm.memory.max`. -The resulting response is similar to the following: - -include::{snippets}/metrics/metric/http-response.adoc[] - - - -[[metrics.retrieving-metric.query-parameters]] -=== Query Parameters -The endpoint uses query parameters to <> into a metric by using its tags. -The following table shows the single supported query parameter: - -[cols="2,4"] -include::{snippets}/metrics/metric-with-tags/query-parameters.adoc[] - - - -[[metrics.retrieving-metric.response-structure]] -=== Response structure -The response contains details of the metric. -The following table describes the structure of the response: - -include::{snippets}/metrics/metric/response-fields.adoc[] - - - -[[metrics.drilling-down]] -== Drilling Down -To drill down into a metric, make a `GET` request to `/actuator/metrics/{metric.name}` using the `tag` query parameter, as shown in the following curl-based example: - -include::{snippets}/metrics/metric-with-tags/curl-request.adoc[] - -The preceding example retrieves the `jvm.memory.max` metric, where the `area` tag has a value of `nonheap` and the `id` attribute has a value of `Compressed Class Space`. -The resulting response is similar to the following: - -include::{snippets}/metrics/metric-with-tags/http-response.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/prometheus.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/prometheus.adoc deleted file mode 100644 index 2fb9efba945c..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/prometheus.adoc +++ /dev/null @@ -1,47 +0,0 @@ -[[prometheus]] -= Prometheus (`prometheus`) -The `prometheus` endpoint provides Spring Boot application's metrics in the format required for scraping by a Prometheus server. - - - -[[prometheus.retrieving]] -== Retrieving All Metrics -To retrieve all metrics, make a `GET` request to `/actuator/prometheus`, as shown in the following curl-based example: - -include::{snippets}/prometheus/all/curl-request.adoc[] - -The resulting response is similar to the following: - -include::{snippets}/prometheus/all/http-response.adoc[] - -The default response content type is `text/plain;version=0.0.4`. -The endpoint can also produce `application/openmetrics-text;version=1.0.0` when called with an appropriate `Accept` header, as shown in the following curl-based example: - -include::{snippets}/prometheus/openmetrics/curl-request.adoc[] - -The resulting response is similar to the following: - -include::{snippets}/prometheus/openmetrics/http-response.adoc[] - - - -[[prometheus.retrieving.query-parameters]] -=== Query Parameters -The endpoint uses query parameters to limit the samples that it returns. -The following table shows the supported query parameters: - -[cols="2,4"] -include::{snippets}/prometheus/names/query-parameters.adoc[] - - - -[[prometheus.retrieving-names]] -== Retrieving Filtered Metrics -To retrieve metrics matching specific names, make a `GET` request to `/actuator/prometheus` with the `includedNames` query parameter, as shown in the following curl-based example: - -include::{snippets}/prometheus/names/curl-request.adoc[] - -The resulting response is similar to the following: - -include::{snippets}/prometheus/names/http-response.adoc[] - diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/quartz.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/quartz.adoc deleted file mode 100644 index 24b72d458843..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/quartz.adoc +++ /dev/null @@ -1,257 +0,0 @@ -[[quartz]] -= Quartz (`quartz`) -The `quartz` endpoint provides information about jobs and triggers that are managed by the Quartz Scheduler. - - - -[[quartz.report]] -== Retrieving Registered Groups -Jobs and triggers are managed in groups. -To retrieve the list of registered job and trigger groups, make a `GET` request to `/actuator/quartz`, as shown in the following curl-based example: - -include::{snippets}/quartz/report/curl-request.adoc[] - -The resulting response is similar to the following: - -include::{snippets}/quartz/report/http-response.adoc[] - - - -[[quartz.report.response-structure]] -=== Response Structure -The response contains the groups names for registered jobs and triggers. -The following table describes the structure of the response: - -[cols="3,1,3"] -include::{snippets}/quartz/report/response-fields.adoc[] - - - -[[quartz.job-groups]] -== Retrieving Registered Job Names -To retrieve the list of registered job names, make a `GET` request to `/actuator/quartz/jobs`, as shown in the following curl-based example: - -include::{snippets}/quartz/jobs/curl-request.adoc[] - -The resulting response is similar to the following: - -include::{snippets}/quartz/jobs/http-response.adoc[] - - - -[[quartz.job-groups.response-structure]] -=== Response Structure -The response contains the registered job names for each group. -The following table describes the structure of the response: - -[cols="3,1,3"] -include::{snippets}/quartz/jobs/response-fields.adoc[] - - - -[[quartz.trigger-groups]] -== Retrieving Registered Trigger Names -To retrieve the list of registered trigger names, make a `GET` request to `/actuator/quartz/triggers`, as shown in the following curl-based example: - -include::{snippets}/quartz/triggers/curl-request.adoc[] - -The resulting response is similar to the following: - -include::{snippets}/quartz/triggers/http-response.adoc[] - - - -[[quartz.trigger-groups.response-structure]] -=== Response Structure -The response contains the registered trigger names for each group. -The following table describes the structure of the response: - -[cols="3,1,3"] -include::{snippets}/quartz/triggers/response-fields.adoc[] - - - -[[quartz.job-group]] -== Retrieving Overview of a Job Group -To retrieve an overview of the jobs in a particular group, make a `GET` request to `/actuator/quartz/jobs/\{groupName}`, as shown in the following curl-based example: - -include::{snippets}/quartz/job-group/curl-request.adoc[] - -The preceding example retrieves the summary for jobs in the `samples` group. -The resulting response is similar to the following: - -include::{snippets}/quartz/job-group/http-response.adoc[] - - - -[[quartz.job-group.response-structure]] -=== Response Structure -The response contains an overview of jobs in a particular group. -The following table describes the structure of the response: - -[cols="3,1,3"] -include::{snippets}/quartz/job-group/response-fields.adoc[] - - - -[[quartz.trigger-group]] -== Retrieving Overview of a Trigger Group - -To retrieve an overview of the triggers in a particular group, make a `GET` request to `/actuator/quartz/triggers/\{groupName}`, as shown in the following curl-based example: - -include::{snippets}/quartz/trigger-group/curl-request.adoc[] - -The preceding example retrieves the summary for triggers in the `tests` group. -The resulting response is similar to the following: - -include::{snippets}/quartz/trigger-group/http-response.adoc[] - - - -[[quartz.trigger-group.response-structure]] -=== Response Structure -The response contains an overview of triggers in a particular group. -Trigger implementation specific details are available. -The following table describes the structure of the response: - -[cols="3,1,3"] -include::{snippets}/quartz/trigger-group/response-fields.adoc[] - - - -[[quartz.job]] -== Retrieving Details of a Job -To retrieve the details about a particular job, make a `GET` request to `/actuator/quartz/jobs/\{groupName}/\{jobName}`, as shown in the following curl-based example: - -include::{snippets}/quartz/job-details/curl-request.adoc[] - -The preceding example retrieves the details of the job identified by the `samples` group and `jobOne` name. -The resulting response is similar to the following: - -include::{snippets}/quartz/job-details/http-response.adoc[] - -If a key in the data map is identified as sensitive, its value is sanitized. - - - -[[quartz.job.response-structure]] -=== Response Structure -The response contains the full details of a job including a summary of the triggers associated with it, if any. -The triggers are sorted by next fire time and priority. -The following table describes the structure of the response: - -[cols="2,1,3"] -include::{snippets}/quartz/job-details/response-fields.adoc[] - - - -[[quartz.trigger]] -== Retrieving Details of a Trigger -To retrieve the details about a particular trigger, make a `GET` request to `/actuator/quartz/triggers/\{groupName}/\{triggerName}`, as shown in the following curl-based example: - -include::{snippets}/quartz/trigger-details-cron/curl-request.adoc[] - -The preceding example retrieves the details of trigger identified by the `samples` group and `example` name. - - - -[[quartz.trigger.common-response-structure]] -=== Common Response Structure -The response has a common structure and an additional object that is specific to the trigger's type. -There are five supported types: - -* `cron` for `CronTrigger` -* `simple` for `SimpleTrigger` -* `dailyTimeInterval` for `DailyTimeIntervalTrigger` -* `calendarInterval` for `CalendarIntervalTrigger` -* `custom` for any other trigger implementations - -The following table describes the structure of the common elements of the response: - -[cols="2,1,3"] -include::{snippets}/quartz/trigger-details-common/response-fields.adoc[] - - - -[[quartz.trigger.cron-response-structure]] -=== Cron Trigger Response Structure -A cron trigger defines the cron expression that is used to determine when it has to fire. -The resulting response for such a trigger implementation is similar to the following: - -include::{snippets}/quartz/trigger-details-cron/http-response.adoc[] - - -Much of the response is common to all trigger types. -The structure of the common elements of the response was <>. -The following table describes the structure of the parts of the response that are specific to cron triggers: - -[cols="2,1,3"] -include::{snippets}/quartz/trigger-details-cron/response-fields.adoc[] - - - -[[quartz.trigger.simple-response-structure]] -=== Simple Trigger Response Structure -A simple trigger is used to fire a Job at a given moment in time, and optionally repeated at a specified interval. -The resulting response for such a trigger implementation is similar to the following: - -include::{snippets}/quartz/trigger-details-simple/http-response.adoc[] - - -Much of the response is common to all trigger types. -The structure of the common elements of the response was <>. -The following table describes the structure of the parts of the response that are specific to simple triggers: - -[cols="2,1,3"] -include::{snippets}/quartz/trigger-details-simple/response-fields.adoc[] - - - -[[quartz.trigger.daily-time-interval-response-structure]] -=== Daily Time Interval Trigger Response Structure -A daily time interval trigger is used to fire a Job based upon daily repeating time intervals. -The resulting response for such a trigger implementation is similar to the following: - -include::{snippets}/quartz/trigger-details-daily-time-interval/http-response.adoc[] - - -Much of the response is common to all trigger types. -The structure of the common elements of the response was <>. -The following table describes the structure of the parts of the response that are specific to daily time interval triggers: - -[cols="2,1,3"] -include::{snippets}/quartz/trigger-details-daily-time-interval/response-fields.adoc[] - - - -[[quartz.trigger.calendar-interval-response-structure]] -=== Calendar Interval Trigger Response Structure -A calendar interval trigger is used to fire a Job based upon repeating calendar time intervals. -The resulting response for such a trigger implementation is similar to the following: - -include::{snippets}/quartz/trigger-details-calendar-interval/http-response.adoc[] - - -Much of the response is common to all trigger types. -The structure of the common elements of the response was <>. -The following table describes the structure of the parts of the response that are specific to calendar interval triggers: - -[cols="2,1,3"] -include::{snippets}/quartz/trigger-details-calendar-interval/response-fields.adoc[] - - - -[[quartz.trigger.custom-response-structure]] -=== Custom Trigger Response Structure -A custom trigger is any other implementation. -The resulting response for such a trigger implementation is similar to the following: - -include::{snippets}/quartz/trigger-details-custom/http-response.adoc[] - - -Much of the response is common to all trigger types. -The structure of the common elements of the response was <>. -The following table describes the structure of the parts of the response that are specific to custom triggers: - -[cols="2,1,3"] -include::{snippets}/quartz/trigger-details-custom/response-fields.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/shutdown.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/shutdown.adoc deleted file mode 100644 index 7b498b43f59e..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/shutdown.adoc +++ /dev/null @@ -1,25 +0,0 @@ -[[shutdown]] -= Shutdown (`shutdown`) -The `shutdown` endpoint is used to shut down the application. - - - -[[shutdown.shutting-down]] -== Shutting Down the Application -To shut down the application, make a `POST` request to `/actuator/shutdown`, as shown in the following curl-based example: - -include::{snippets}/shutdown/curl-request.adoc[] - -A response similar to the following is produced: - -include::{snippets}/shutdown/http-response.adoc[] - - - -[[shutdown.shutting-down.response-structure]] -=== Response Structure -The response contains details of the result of the shutdown request. -The following table describes the structure of the response: - -[cols="3,1,3"] -include::{snippets}/shutdown/response-fields.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/threaddump.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/threaddump.adoc deleted file mode 100644 index 566ff24444e4..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/threaddump.adoc +++ /dev/null @@ -1,38 +0,0 @@ -[[threaddump]] -= Thread Dump (`threaddump`) -The `threaddump` endpoint provides a thread dump from the application's JVM. - - - -[[threaddump.retrieving-json]] -== Retrieving the Thread Dump as JSON -To retrieve the thread dump as JSON, make a `GET` request to `/actuator/threaddump` with an appropriate `Accept` header, as shown in the following curl-based example: - -include::{snippets}/threaddump/json/curl-request.adoc[] - -The resulting response is similar to the following: - -include::{snippets}/threaddump/json/http-response.adoc[] - - - -[[threaddump.retrieving-json.response-structure]] -=== Response Structure -The response contains details of the JVM's threads. -The following table describes the structure of the response: - -[cols="3,1,2"] -include::{snippets}/threaddump/json/response-fields.adoc[] - - - -[[threaddump.retrieving-text]] -== Retrieving the Thread Dump as Text -To retrieve the thread dump as text, make a `GET` request to `/actuator/threaddump` that -accepts `text/plain`, as shown in the following curl-based example: - -include::{snippets}/threaddump/text/curl-request.adoc[] - -The resulting response is similar to the following: - -include::{snippets}/threaddump/text/http-response.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/index.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/index.adoc deleted file mode 100644 index daf6fa9fedbf..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/index.adoc +++ /dev/null @@ -1,103 +0,0 @@ -[[spring-boot-actuator-web-api-documentation]] -= Spring Boot Actuator Web API Documentation -Andy Wilkinson; Stephane Nicoll -v{gradle-project-version} -:!version-label: -:doctype: book -:toc: left -:toclevels: 4 -:numbered: -:icons: font -:hide-uri-scheme: -:docinfo: shared,private -:attribute-missing: warn - - - -This API documentation describes Spring Boot Actuators web endpoints. - - - -[[overview]] -== Overview -Before you proceed, you should read the following topics: - -* <> -* <> - -NOTE: In order to get the correct JSON responses documented below, Jackson must be available. - - - -[[overview.endpoint-urls]] -=== URLs -By default, all web endpoints are available beneath the path `/actuator` with URLs of -the form `/actuator/\{id}`. The `/actuator` base path can be configured by using the -`management.endpoints.web.base-path` property, as shown in the following example: - -[source,properties,indent=0] ----- - management.endpoints.web.base-path=/manage ----- - -The preceding `application.properties` example changes the form of the endpoint URLs from -`/actuator/\{id}` to `/manage/\{id}`. For example, the URL `info` endpoint would become -`/manage/info`. - - - -[[overview.timestamps]] -=== Timestamps -All timestamps that are consumed by the endpoints, either as query parameters or in the -request body, must be formatted as an offset date and time as specified in -https://en.wikipedia.org/wiki/ISO_8601[ISO 8601]. - - - -include::endpoints/auditevents.adoc[leveloffset=+1] - -include::endpoints/beans.adoc[leveloffset=+1] - -include::endpoints/caches.adoc[leveloffset=+1] - -include::endpoints/conditions.adoc[leveloffset=+1] - -include::endpoints/configprops.adoc[leveloffset=+1] - -include::endpoints/env.adoc[leveloffset=+1] - -include::endpoints/flyway.adoc[leveloffset=+1] - -include::endpoints/health.adoc[leveloffset=+1] - -include::endpoints/heapdump.adoc[leveloffset=+1] - -include::endpoints/httpexchanges.adoc[leveloffset=+1] - -include::endpoints/info.adoc[leveloffset=+1] - -include::endpoints/integrationgraph.adoc[leveloffset=+1] - -include::endpoints/liquibase.adoc[leveloffset=+1] - -include::endpoints/logfile.adoc[leveloffset=+1] - -include::endpoints/loggers.adoc[leveloffset=+1] - -include::endpoints/mappings.adoc[leveloffset=+1] - -include::endpoints/metrics.adoc[leveloffset=+1] - -include::endpoints/prometheus.adoc[leveloffset=+1] - -include::endpoints/quartz.adoc[leveloffset=+1] - -include::endpoints/scheduledtasks.adoc[leveloffset=+1] - -include::endpoints/sessions.adoc[leveloffset=+1] - -include::endpoints/shutdown.adoc[leveloffset=+1] - -include::endpoints/startup.adoc[leveloffset=+1] - -include::endpoints/threaddump.adoc[leveloffset=+1] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscoverer.java index 173bcbe9e951..c401f5cf7801 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,11 +65,9 @@ public CloudFoundryWebEndpointDiscoverer(ApplicationContext applicationContext, @Override protected boolean isExtensionTypeExposed(Class extensionBeanType) { - if (isHealthEndpointExtension(extensionBeanType) && !isCloudFoundryHealthEndpointExtension(extensionBeanType)) { - // Filter regular health endpoint extensions so a CF version can replace them - return false; - } - return true; + // Filter regular health endpoint extensions so a CF version can replace them + return !isHealthEndpointExtension(extensionBeanType) + || isCloudFoundryHealthEndpointExtension(extensionBeanType); } private boolean isHealthEndpointExtension(Class extensionBeanType) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityService.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityService.java index d75725e68d6f..18ac9811b3df 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityService.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import reactor.core.publisher.Mono; import reactor.netty.http.Http11SslContextSpec; import reactor.netty.http.client.HttpClient; +import reactor.netty.tcp.SslProvider.GenericSslContextSpec; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException; @@ -69,7 +70,7 @@ protected ReactorClientHttpConnector buildTrustAllSslConnector() { return new ReactorClientHttpConnector(client); } - private Http11SslContextSpec createSslContextSpec() { + private GenericSslContextSpec createSslContextSpec() { return Http11SslContextSpec.forClient() .configure((builder) -> builder.sslProvider(SslProvider.JDK) .trustManager(InsecureTrustManagerFactory.INSTANCE)); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfiguration.java index 08a688c53bef..be7462376d11 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -111,6 +111,7 @@ public CloudFoundryInfoEndpointWebExtension cloudFoundryInfoEndpointWebExtension } @Bean + @SuppressWarnings("removal") public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping( ParameterValueMapper parameterMapper, EndpointMediaTypes endpointMediaTypes, RestTemplateBuilder restTemplateBuilder, ServletEndpointsSupplier servletEndpointsSupplier, diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundrySecurityInterceptor.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundrySecurityInterceptor.java index 01eafcf65125..c5c4b2c8e4d2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundrySecurityInterceptor.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundrySecurityInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -86,7 +86,7 @@ SecurityResponse preHandle(HttpServletRequest request, EndpointId endpointId) { return SecurityResponse.success(); } - private void check(HttpServletRequest request, EndpointId endpointId) throws Exception { + private void check(HttpServletRequest request, EndpointId endpointId) { Token token = getToken(request); this.tokenValidator.validate(token); AccessLevel accessLevel = this.cloudFoundrySecurityService.getAccessLevel(token.toString(), this.applicationId); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnAvailableEndpointCondition.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnAvailableEndpointCondition.java index 4478b0ed9426..a485aa2a4a10 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnAvailableEndpointCondition.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnAvailableEndpointCondition.java @@ -143,11 +143,8 @@ private ConditionOutcome getEnablementOutcome(Environment environment, } private Boolean isEnabledByDefault(Environment environment) { - Optional enabledByDefault = enabledByDefaultCache.get(environment); - if (enabledByDefault == null) { - enabledByDefault = Optional.ofNullable(environment.getProperty(ENABLED_BY_DEFAULT_KEY, Boolean.class)); - enabledByDefaultCache.put(environment, enabledByDefault); - } + Optional enabledByDefault = enabledByDefaultCache.computeIfAbsent(environment, + (ignore) -> Optional.ofNullable(environment.getProperty(ENABLED_BY_DEFAULT_KEY, Boolean.class))); return enabledByDefault.orElse(null); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludeExcludeEndpointFilter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludeExcludeEndpointFilter.java index d2c738b5e2ec..00affa4cfff3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludeExcludeEndpointFilter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludeExcludeEndpointFilter.java @@ -150,7 +150,7 @@ private static class EndpointPatterns { private final Set endpointIds; EndpointPatterns(String[] patterns) { - this((patterns != null) ? Arrays.asList(patterns) : (Collection) null); + this((patterns != null) ? Arrays.asList(patterns) : null); } EndpointPatterns(Collection patterns) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java index 16e6453a1c4e..2e5de5c8f4ab 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,6 +47,7 @@ public class ServletEndpointManagementContextConfiguration { @Bean + @SuppressWarnings("removal") public IncludeExcludeEndpointFilter servletExposeExcludePropertyEndpointFilter( WebEndpointProperties properties) { WebEndpointProperties.Exposure exposure = properties.getExposure(); @@ -56,6 +57,7 @@ public IncludeExcludeEndpointFilter servletExposeExclu @Configuration(proxyBeanMethods = false) @ConditionalOnClass(DispatcherServlet.class) + @SuppressWarnings("removal") public static class WebMvcServletEndpointManagementContextConfiguration { @Bean @@ -70,6 +72,7 @@ public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties p @Configuration(proxyBeanMethods = false) @ConditionalOnClass(ResourceConfig.class) @ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet") + @SuppressWarnings("removal") public static class JerseyServletEndpointManagementContextConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java index 66f1c43df22f..504e6beb8f54 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -96,6 +96,7 @@ public WebEndpointDiscoverer webEndpointDiscoverer(ParameterValueMapper paramete @Bean @ConditionalOnMissingBean(ControllerEndpointsSupplier.class) + @SuppressWarnings("removal") public ControllerEndpointDiscoverer controllerEndpointDiscoverer(ObjectProvider endpointPathMappers, ObjectProvider>> filters) { return new ControllerEndpointDiscoverer(this.applicationContext, endpointPathMappers.orderedStream().toList(), @@ -124,6 +125,7 @@ public IncludeExcludeEndpointFilter controllerExpos @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) + @SuppressWarnings("removal") static class WebEndpointServletConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.java index 24736e2647d0..81816b231d77 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -84,6 +84,7 @@ class JerseyWebEndpointManagementContextConfiguration { private static final EndpointId HEALTH_ENDPOINT_ID = EndpointId.of("health"); @Bean + @SuppressWarnings("removal") JerseyWebEndpointsResourcesRegistrar jerseyWebEndpointsResourcesRegistrar(Environment environment, WebEndpointsSupplier webEndpointsSupplier, ServletEndpointsSupplier servletEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, WebEndpointProperties webEndpointProperties) { @@ -103,7 +104,8 @@ JerseyAdditionalHealthEndpointPathsManagementResourcesRegistrar jerseyDifferentP ExposableWebEndpoint health = webEndpoints.stream() .filter((endpoint) -> endpoint.getEndpointId().equals(HEALTH_ENDPOINT_ID)) .findFirst() - .get(); + .orElseThrow( + () -> new IllegalStateException("No endpoint with id '%s' found".formatted(HEALTH_ENDPOINT_ID))); return new JerseyAdditionalHealthEndpointPathsManagementResourcesRegistrar(health, healthEndpointGroups); } @@ -123,6 +125,7 @@ private boolean shouldRegisterLinksMapping(WebEndpointProperties properties, Env /** * Register endpoints with the {@link ResourceConfig} for the management context. */ + @SuppressWarnings("removal") static class JerseyWebEndpointsResourcesRegistrar implements ManagementContextResourceConfigCustomizer { private final WebEndpointsSupplier webEndpointsSupplier; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/reactive/WebFluxEndpointManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/reactive/WebFluxEndpointManagementContextConfiguration.java index 9e117dd3b7c9..94ea4766f50f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/reactive/WebFluxEndpointManagementContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/reactive/WebFluxEndpointManagementContextConfiguration.java @@ -120,7 +120,8 @@ public AdditionalHealthEndpointPathsWebFluxHandlerMapping managementHealthEndpoi ExposableWebEndpoint health = webEndpoints.stream() .filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID)) .findFirst() - .get(); + .orElseThrow( + () -> new IllegalStateException("No endpoint with id '%s' found".formatted(HealthEndpoint.ID))); return new AdditionalHealthEndpointPathsWebFluxHandlerMapping(new EndpointMapping(""), health, groups.getAllWithAdditionalPath(WebServerNamespace.MANAGEMENT)); } @@ -162,16 +163,16 @@ static class ServerCodecConfigurerEndpointObjectMapperBeanPostProcessor implemen @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof ServerCodecConfigurer) { - process((ServerCodecConfigurer) bean); + if (bean instanceof ServerCodecConfigurer serverCodecConfigurer) { + process(serverCodecConfigurer); } return bean; } private void process(ServerCodecConfigurer configurer) { for (HttpMessageWriter writer : configurer.getWriters()) { - if (writer instanceof EncoderHttpMessageWriter) { - process(((EncoderHttpMessageWriter) writer).getEncoder()); + if (writer instanceof EncoderHttpMessageWriter encoderHttpMessageWriter) { + process((encoderHttpMessageWriter).getEncoder()); } } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/servlet/WebMvcEndpointManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/servlet/WebMvcEndpointManagementContextConfiguration.java index f271c663ab95..f90dab6cfbb2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/servlet/WebMvcEndpointManagementContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/servlet/WebMvcEndpointManagementContextConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -82,6 +82,7 @@ public class WebMvcEndpointManagementContextConfiguration { @Bean @ConditionalOnMissingBean + @SuppressWarnings("removal") public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier, ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties, @@ -115,7 +116,8 @@ public AdditionalHealthEndpointPathsWebMvcHandlerMapping managementHealthEndpoin ExposableWebEndpoint health = webEndpoints.stream() .filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID)) .findFirst() - .get(); + .orElseThrow( + () -> new IllegalStateException("No endpoint with id '%s' found".formatted(HealthEndpoint.ID))); return new AdditionalHealthEndpointPathsWebMvcHandlerMapping(health, groups.getAllWithAdditionalPath(WebServerNamespace.MANAGEMENT)); } @@ -157,8 +159,8 @@ static class EndpointObjectMapperWebMvcConfigurer implements WebMvcConfigurer { @Override public void configureMessageConverters(List> converters) { for (HttpMessageConverter converter : converters) { - if (converter instanceof MappingJackson2HttpMessageConverter) { - configure((MappingJackson2HttpMessageConverter) converter); + if (converter instanceof MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) { + configure(mappingJackson2HttpMessageConverter); } } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AbstractCompositeHealthContributorConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AbstractCompositeHealthContributorConfiguration.java index 5a7454b08a50..3b5aeda866da 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AbstractCompositeHealthContributorConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AbstractCompositeHealthContributorConfiguration.java @@ -16,12 +16,9 @@ package org.springframework.boot.actuate.autoconfigure.health; -import java.lang.reflect.Constructor; import java.util.Map; import java.util.function.Function; -import org.springframework.beans.BeanUtils; -import org.springframework.core.ResolvableType; import org.springframework.util.Assert; /** @@ -39,18 +36,6 @@ public abstract class AbstractCompositeHealthContributorConfiguration indicatorFactory; - /** - * Creates a {@code AbstractCompositeHealthContributorConfiguration} that will use - * reflection to create health indicator instances. - * @deprecated since 3.0.0 in favor of - * {@link #AbstractCompositeHealthContributorConfiguration(Function)} - */ - @Deprecated(since = "3.0.0", forRemoval = true) - protected AbstractCompositeHealthContributorConfiguration() { - this.indicatorFactory = new ReflectionIndicatorFactory( - ResolvableType.forClass(AbstractCompositeHealthContributorConfiguration.class, getClass())); - } - /** * Creates a {@code AbstractCompositeHealthContributorConfiguration} that will use the * given {@code indicatorFactory} to create health indicator instances. @@ -75,34 +60,4 @@ protected I createIndicator(B bean) { return this.indicatorFactory.apply(bean); } - private class ReflectionIndicatorFactory implements Function { - - private final Class indicatorType; - - private final Class beanType; - - ReflectionIndicatorFactory(ResolvableType type) { - this.indicatorType = type.resolveGeneric(1); - this.beanType = type.resolveGeneric(2); - } - - @Override - public I apply(B bean) { - try { - return BeanUtils.instantiateClass(getConstructor(), bean); - } - catch (Exception ex) { - throw new IllegalStateException("Unable to create health indicator %s for bean type %s" - .formatted(this.indicatorType, this.beanType), ex); - } - - } - - @SuppressWarnings("unchecked") - private Constructor getConstructor() throws NoSuchMethodException { - return (Constructor) this.indicatorType.getDeclaredConstructor(this.beanType); - } - - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfiguration.java index 7901e1307552..4b979e94d19e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,18 +36,6 @@ public abstract class CompositeHealthContributorConfiguration extends AbstractCompositeHealthContributorConfiguration { - /** - * Creates a {@code CompositeHealthContributorConfiguration} that will use reflection - * to create {@link HealthIndicator} instances. - * @deprecated since 3.0.0 in favor of - * {@link #CompositeHealthContributorConfiguration(Function)} - */ - @SuppressWarnings("removal") - @Deprecated(since = "3.0.0", forRemoval = true) - public CompositeHealthContributorConfiguration() { - super(); - } - /** * Creates a {@code CompositeHealthContributorConfiguration} that will use the given * {@code indicatorFactory} to create {@link HealthIndicator} instances. diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfiguration.java index 57b45ff1a10f..12c4ff22a88e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,18 +36,6 @@ public abstract class CompositeReactiveHealthContributorConfiguration extends AbstractCompositeHealthContributorConfiguration { - /** - * Creates a {@code CompositeReactiveHealthContributorConfiguration} that will use - * reflection to create {@link ReactiveHealthIndicator} instances. - * @deprecated since 3.0.0 in favor of - * {@link #CompositeReactiveHealthContributorConfiguration(Function)} - */ - @SuppressWarnings("removal") - @Deprecated(since = "3.0.0", forRemoval = true) - public CompositeReactiveHealthContributorConfiguration() { - super(); - } - /** * Creates a {@code CompositeReactiveHealthContributorConfiguration} that will use the * given {@code indicatorFactory} to create {@link ReactiveHealthIndicator} instances. diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointReactiveWebExtensionConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointReactiveWebExtensionConfiguration.java index 6e80745fa7e7..4a8d814ebd4e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointReactiveWebExtensionConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointReactiveWebExtensionConfiguration.java @@ -70,7 +70,8 @@ AdditionalHealthEndpointPathsWebFluxHandlerMapping healthEndpointWebFluxHandlerM ExposableWebEndpoint health = webEndpoints.stream() .filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID)) .findFirst() - .get(); + .orElseThrow( + () -> new IllegalStateException("No endpoint with id '%s' found".formatted(HealthEndpoint.ID))); return new AdditionalHealthEndpointPathsWebFluxHandlerMapping(new EndpointMapping(""), health, groups.getAllWithAdditionalPath(WebServerNamespace.SERVER)); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionConfiguration.java index b0924d928018..a973b2f0fa4c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionConfiguration.java @@ -81,7 +81,8 @@ private static ExposableWebEndpoint getHealthEndpoint(WebEndpointsSupplier webEn return webEndpoints.stream() .filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID)) .findFirst() - .get(); + .orElseThrow( + () -> new IllegalStateException("No endpoint with id '%s' found".formatted(HealthEndpoint.ID))); } @ConditionalOnBean(DispatcherServlet.class) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthProperties.java index 26106b3bcd62..db991e5676d2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthContributorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthContributorAutoConfiguration.java deleted file mode 100644 index 7f93279fde82..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthContributorAutoConfiguration.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.influx; - -import java.util.Map; - -import org.influxdb.InfluxDB; - -import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration; -import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; -import org.springframework.boot.actuate.health.HealthContributor; -import org.springframework.boot.actuate.influx.InfluxDbHealthIndicator; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration; -import org.springframework.context.annotation.Bean; - -/** - * {@link EnableAutoConfiguration Auto-configuration} for {@link InfluxDbHealthIndicator}. - * - * @author Eddú Meléndez - * @since 2.0.0 - */ -@AutoConfiguration(after = InfluxDbAutoConfiguration.class) -@ConditionalOnClass(InfluxDB.class) -@ConditionalOnBean(InfluxDB.class) -@ConditionalOnEnabledHealthIndicator("influxdb") -public class InfluxDbHealthContributorAutoConfiguration - extends CompositeHealthContributorConfiguration { - - public InfluxDbHealthContributorAutoConfiguration() { - super(InfluxDbHealthIndicator::new); - } - - @Bean - @ConditionalOnMissingBean(name = { "influxDbHealthIndicator", "influxDbHealthContributor" }) - public HealthContributor influxDbHealthContributor(Map influxDbs) { - return createContributor(influxDbs); - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/influx/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/influx/package-info.java deleted file mode 100644 index ecdac497402e..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/influx/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Auto-configuration for actuator InfluxDB concerns. - */ -package org.springframework.boot.actuate.autoconfigure.influx; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/info/InfoContributorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/info/InfoContributorAutoConfiguration.java index ae674854af1a..b26455ad43ab 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/info/InfoContributorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/info/InfoContributorAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import org.springframework.boot.actuate.info.InfoContributor; import org.springframework.boot.actuate.info.JavaInfoContributor; import org.springframework.boot.actuate.info.OsInfoContributor; +import org.springframework.boot.actuate.info.ProcessInfoContributor; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -92,4 +93,11 @@ public OsInfoContributor osInfoContributor() { return new OsInfoContributor(); } + @Bean + @ConditionalOnEnabledInfoContributor(value = "process", fallback = InfoContributorFallback.DISABLE) + @Order(DEFAULT_ORDER) + public ProcessInfoContributor processInfoContributor() { + return new ProcessInfoContributor(); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/info/InfoContributorFallback.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/info/InfoContributorFallback.java index 0f78db5cbe44..ce03c3f76a44 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/info/InfoContributorFallback.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/info/InfoContributorFallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,6 @@ public enum InfoContributorFallback { /** * Do not fall back, thereby disabling the info contributor. */ - DISABLE; + DISABLE } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/OpenTelemetryLoggingAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/OpenTelemetryLoggingAutoConfiguration.java new file mode 100644 index 000000000000..934be85562ac --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/OpenTelemetryLoggingAutoConfiguration.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.logging.opentelemetry; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.logs.LogRecordProcessor; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; +import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; +import io.opentelemetry.sdk.resources.Resource; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for OpenTelemetry logging. + * + * @author Toshiaki Maki + * @since 3.4.0 + */ +@AutoConfiguration +@ConditionalOnClass({ SdkLoggerProvider.class, OpenTelemetry.class }) +public class OpenTelemetryLoggingAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + BatchLogRecordProcessor batchLogRecordProcessor(ObjectProvider logRecordExporters) { + return BatchLogRecordProcessor.builder(LogRecordExporter.composite(logRecordExporters.orderedStream().toList())) + .build(); + } + + @Bean + @ConditionalOnMissingBean + SdkLoggerProvider otelSdkLoggerProvider(Resource resource, ObjectProvider logRecordProcessors, + ObjectProvider customizers) { + SdkLoggerProviderBuilder builder = SdkLoggerProvider.builder().setResource(resource); + logRecordProcessors.orderedStream().forEach(builder::addLogRecordProcessor); + customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); + return builder.build(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/SdkLoggerProviderBuilderCustomizer.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/SdkLoggerProviderBuilderCustomizer.java new file mode 100644 index 000000000000..d58daf3ca3ab --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/SdkLoggerProviderBuilderCustomizer.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.logging.opentelemetry; + +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; + +/** + * Callback interface that can be used to customize the {@link SdkLoggerProviderBuilder} + * that is used to create the auto-configured {@link SdkLoggerProvider}. + * + * @author Toshiaki Maki + * @since 3.4.0 + */ +@FunctionalInterface +public interface SdkLoggerProviderBuilderCustomizer { + + /** + * Customize the given {@code builder}. + * @param builder the builder to customize + */ + void customize(SdkLoggerProviderBuilder builder); + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/OtlpLoggingAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/OtlpLoggingAutoConfiguration.java new file mode 100644 index 000000000000..d398d6766d8e --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/OtlpLoggingAutoConfiguration.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.logging.opentelemetry.otlp; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Import; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for OTLP logging. + * + * @author Toshiaki Maki + * @since 3.4.0 + */ +@AutoConfiguration +@ConditionalOnClass({ SdkLoggerProvider.class, OpenTelemetry.class, OtlpHttpLogRecordExporter.class }) +@EnableConfigurationProperties(OtlpLoggingProperties.class) +@Import({ OtlpLoggingConfigurations.ConnectionDetails.class, OtlpLoggingConfigurations.Exporters.class }) +public class OtlpLoggingAutoConfiguration { + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/OtlpLoggingConfigurations.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/OtlpLoggingConfigurations.java new file mode 100644 index 000000000000..7558efc64460 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/OtlpLoggingConfigurations.java @@ -0,0 +1,89 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.logging.opentelemetry.otlp; + +import java.util.Locale; + +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporterBuilder; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configurations imported by {@link OtlpLoggingAutoConfiguration}. + * + * @author Toshiaki Maki + */ +final class OtlpLoggingConfigurations { + + private OtlpLoggingConfigurations() { + } + + @Configuration(proxyBeanMethods = false) + static class ConnectionDetails { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(prefix = "management.otlp.logging", name = "endpoint") + OtlpLoggingConnectionDetails otlpLogsConnectionDetails(OtlpLoggingProperties properties) { + return new PropertiesOtlpLoggingConnectionDetails(properties); + } + + /** + * Adapts {@link OtlpLoggingProperties} to {@link OtlpLoggingConnectionDetails}. + */ + static class PropertiesOtlpLoggingConnectionDetails implements OtlpLoggingConnectionDetails { + + private final OtlpLoggingProperties properties; + + PropertiesOtlpLoggingConnectionDetails(OtlpLoggingProperties properties) { + this.properties = properties; + } + + @Override + public String getEndpoint() { + return this.properties.getEndpoint(); + } + + } + + } + + @Configuration(proxyBeanMethods = false) + static class Exporters { + + @ConditionalOnMissingBean(value = OtlpHttpLogRecordExporter.class, + type = "io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter") + @ConditionalOnBean(OtlpLoggingConnectionDetails.class) + @Bean + OtlpHttpLogRecordExporter otlpHttpLogRecordExporter(OtlpLoggingProperties properties, + OtlpLoggingConnectionDetails connectionDetails) { + OtlpHttpLogRecordExporterBuilder builder = OtlpHttpLogRecordExporter.builder() + .setEndpoint(connectionDetails.getEndpoint()) + .setCompression(properties.getCompression().name().toLowerCase(Locale.US)) + .setTimeout(properties.getTimeout()); + properties.getHeaders().forEach(builder::addHeader); + return builder.build(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/OtlpLoggingConnectionDetails.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/OtlpLoggingConnectionDetails.java new file mode 100644 index 000000000000..f4d1dfb35a54 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/OtlpLoggingConnectionDetails.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.logging.opentelemetry.otlp; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +/** + * Details required to establish a connection to an OpenTelemetry logging service. + * + * @author Toshiaki Maki + * @since 3.4.0 + */ +public interface OtlpLoggingConnectionDetails extends ConnectionDetails { + + /** + * Address to where logs will be published. + * @return the address to where logs will be published + */ + String getEndpoint(); + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/OtlpLoggingProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/OtlpLoggingProperties.java new file mode 100644 index 000000000000..b7d484a9466a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/OtlpLoggingProperties.java @@ -0,0 +1,99 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.logging.opentelemetry.otlp; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for exporting logs using OTLP. + * + * @author Jonatan Ivanov + * @since 3.4.0 + */ +@ConfigurationProperties("management.otlp.logging") +public class OtlpLoggingProperties { + + /** + * URL to the OTel collector's HTTP API. + */ + private String endpoint; + + /** + * Call timeout for the OTel Collector to process an exported batch of data. This + * timeout spans the entire call: resolving DNS, connecting, writing the request body, + * server processing, and reading the response body. If the call requires redirects or + * retries all must complete within one timeout period. + */ + private Duration timeout = Duration.ofSeconds(10); + + /** + * Method used to compress the payload. + */ + private Compression compression = Compression.NONE; + + /** + * Custom HTTP headers you want to pass to the collector, for example auth headers. + */ + private final Map headers = new HashMap<>(); + + public String getEndpoint() { + return this.endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public Duration getTimeout() { + return this.timeout; + } + + public void setTimeout(Duration timeout) { + this.timeout = timeout; + } + + public Compression getCompression() { + return this.compression; + } + + public void setCompression(Compression compression) { + this.compression = compression; + } + + public Map getHeaders() { + return this.headers; + } + + public enum Compression { + + /** + * Gzip compression. + */ + GZIP, + + /** + * No compression. + */ + NONE + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/package-info.java new file mode 100644 index 000000000000..167fb211c096 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for OpenTelemetry logging with OTLP. + */ +package org.springframework.boot.actuate.autoconfigure.logging.opentelemetry.otlp; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/package-info.java new file mode 100644 index 000000000000..63ec3087114e --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for OpenTelemetry logging. + */ +package org.springframework.boot.actuate.autoconfigure.logging.opentelemetry; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterValue.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterValue.java index d475f8c5d685..fa429ca765b1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterValue.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterValue.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -90,7 +90,7 @@ public static MeterValue valueOf(String value) { if (duration != null) { return new MeterValue(duration); } - return new MeterValue(Double.valueOf(value)); + return new MeterValue(Double.parseDouble(value)); } /** diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfiguration.java new file mode 100644 index 000000000000..959789adfd50 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfiguration.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.metrics; + +import io.micrometer.core.aop.CountedAspect; +import io.micrometer.core.aop.MeterTagAnnotationHandler; +import io.micrometer.core.aop.TimedAspect; +import io.micrometer.core.instrument.MeterRegistry; +import org.aspectj.weaver.Advice; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAspectsAutoConfiguration.ObservationAnnotationsEnabledCondition; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Micrometer-based metrics + * aspects. + * + * @author Jonatan Ivanov + * @since 3.2.0 + */ +@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class }) +@ConditionalOnClass({ MeterRegistry.class, Advice.class }) +@Conditional(ObservationAnnotationsEnabledCondition.class) +@ConditionalOnBean(MeterRegistry.class) +public class MetricsAspectsAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + CountedAspect countedAspect(MeterRegistry registry) { + return new CountedAspect(registry); + } + + @Bean + @ConditionalOnMissingBean + TimedAspect timedAspect(MeterRegistry registry, + ObjectProvider meterTagAnnotationHandler) { + TimedAspect timedAspect = new TimedAspect(registry); + meterTagAnnotationHandler.ifAvailable(timedAspect::setMeterTagAnnotationHandler); + return timedAspect; + } + + static final class ObservationAnnotationsEnabledCondition extends AnyNestedCondition { + + ObservationAnnotationsEnabledCondition() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @ConditionalOnProperty(prefix = "micrometer.observations.annotations", name = "enabled", havingValue = "true") + static class MicrometerObservationsEnabledCondition { + + } + + @ConditionalOnProperty(prefix = "management.observations.annotations", name = "enabled", havingValue = "true") + static class ManagementObservationsEnabledCondition { + + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfiguration.java index 16eaab791d98..dfb7e73a5f61 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfiguration.java @@ -16,8 +16,11 @@ package org.springframework.boot.actuate.autoconfigure.metrics; +import java.util.List; + import io.micrometer.core.annotation.Timed; import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.binder.MeterBinder; import io.micrometer.core.instrument.config.MeterFilter; @@ -28,7 +31,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; +import org.springframework.context.event.ContextClosedEvent; import org.springframework.core.annotation.Order; /** @@ -36,6 +41,7 @@ * * @author Jon Schneider * @author Stephane Nicoll + * @author Moritz Halbritter * @since 2.0.0 */ @AutoConfiguration(before = CompositeMeterRegistryAutoConfiguration.class) @@ -64,4 +70,32 @@ public PropertiesMeterFilter propertiesMeterFilter(MetricsProperties properties) return new PropertiesMeterFilter(properties); } + @Bean + MeterRegistryCloser meterRegistryCloser(ObjectProvider meterRegistries) { + return new MeterRegistryCloser(meterRegistries.orderedStream().toList()); + } + + /** + * Ensures that {@link MeterRegistry meter registries} are closed early in the + * shutdown process. + */ + static class MeterRegistryCloser implements ApplicationListener { + + private final List meterRegistries; + + MeterRegistryCloser(List meterRegistries) { + this.meterRegistries = meterRegistries; + } + + @Override + public void onApplicationEvent(ContextClosedEvent event) { + for (MeterRegistry meterRegistry : this.meterRegistries) { + if (!meterRegistry.isClosed()) { + meterRegistry.close(); + } + } + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java index f06176bb95dc..6ed7759cd14b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import java.util.Map; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.boot.context.properties.NestedConfigurationProperty; /** @@ -115,8 +114,6 @@ public Server getServer() { public static class Client { - private final ClientRequest request = new ClientRequest(); - /** * Maximum number of unique URI tag values allowed. After the max number of * tag values is reached, metrics with additional tag values are denied by @@ -124,10 +121,6 @@ public static class Client { */ private int maxUriTags = 100; - public ClientRequest getRequest() { - return this.request; - } - public int getMaxUriTags() { return this.maxUriTags; } @@ -136,32 +129,10 @@ public void setMaxUriTags(int maxUriTags) { this.maxUriTags = maxUriTags; } - public static class ClientRequest { - - /** - * Name of the metric for sent requests. - */ - private String metricName = "http.client.requests"; - - @Deprecated(since = "3.0.0", forRemoval = true) - @DeprecatedConfigurationProperty(replacement = "management.observations.http.client.requests.name") - public String getMetricName() { - return this.metricName; - } - - @Deprecated(since = "3.0.0", forRemoval = true) - public void setMetricName(String metricName) { - this.metricName = metricName; - } - - } - } public static class Server { - private final ServerRequest request = new ServerRequest(); - /** * Maximum number of unique URI tag values allowed. After the max number of * tag values is reached, metrics with additional tag values are denied by @@ -169,10 +140,6 @@ public static class Server { */ private int maxUriTags = 100; - public ServerRequest getRequest() { - return this.request; - } - public int getMaxUriTags() { return this.maxUriTags; } @@ -181,27 +148,6 @@ public void setMaxUriTags(int maxUriTags) { this.maxUriTags = maxUriTags; } - public static class ServerRequest { - - /** - * Name of the metric for received requests. - */ - private String metricName = "http.server.requests"; - - @Deprecated(since = "3.0.0", forRemoval = true) - @DeprecatedConfigurationProperty(replacement = "management.observations.http.server.requests.name") - public String getMetricName() { - return this.metricName; - } - - @Deprecated(since = "3.0.0", forRemoval = true) - @DeprecatedConfigurationProperty(replacement = "management.observations.http.server.requests.name") - public void setMetricName(String metricName) { - this.metricName = metricName; - } - - } - } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/dynatrace/DynatraceProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/dynatrace/DynatraceProperties.java index 91c710100843..189ef09a86b5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/dynatrace/DynatraceProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/dynatrace/DynatraceProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -140,6 +140,12 @@ public static class V2 { */ private boolean useDynatraceSummaryInstruments = true; + /** + * Whether to export meter metadata (unit and description) to the Dynatrace + * backend. + */ + private boolean exportMeterMetadata = true; + public Map getDefaultDimensions() { return this.defaultDimensions; } @@ -172,6 +178,14 @@ public void setUseDynatraceSummaryInstruments(boolean useDynatraceSummaryInstrum this.useDynatraceSummaryInstruments = useDynatraceSummaryInstruments; } + public boolean isExportMeterMetadata() { + return this.exportMeterMetadata; + } + + public void setExportMeterMetadata(boolean exportMeterMetadata) { + this.exportMeterMetadata = exportMeterMetadata; + } + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/dynatrace/DynatracePropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/dynatrace/DynatracePropertiesConfigAdapter.java index 82135f989860..bbdc14db563a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/dynatrace/DynatracePropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/dynatrace/DynatracePropertiesConfigAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -95,6 +95,11 @@ public boolean useDynatraceSummaryInstruments() { return get(v2(V2::isUseDynatraceSummaryInstruments), DynatraceConfig.super::useDynatraceSummaryInstruments); } + @Override + public boolean exportMeterMetadata() { + return get(v2(V2::isExportMeterMetadata), DynatraceConfig.super::exportMeterMetadata); + } + private Function v1(Function getter) { return (properties) -> getter.apply(properties.getV1()); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsConnectionDetails.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsConnectionDetails.java new file mode 100644 index 000000000000..eeef0ae685bc --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsConnectionDetails.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +/** + * Details required to establish a connection to an OpenTelemetry Collector service. + * + * @author Eddú Meléndez + * @since 3.2.0 + */ +public interface OtlpMetricsConnectionDetails extends ConnectionDetails { + + /** + * Address to where metrics will be published. + * @return the address to where metrics will be published + */ + String getUrl(); + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java index 29e89c29e50a..c7da21f488d1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.export.ConditionalOnEnabledMetricsExport; import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryProperties; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -31,11 +32,13 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; /** * {@link EnableAutoConfiguration Auto-configuration} for exporting metrics to OTLP. * * @author Eddú Meléndez + * @author Moritz Halbritter * @since 3.0.0 */ @AutoConfiguration( @@ -44,19 +47,27 @@ @ConditionalOnBean(Clock.class) @ConditionalOnClass(OtlpMeterRegistry.class) @ConditionalOnEnabledMetricsExport("otlp") -@EnableConfigurationProperties(OtlpProperties.class) +@EnableConfigurationProperties({ OtlpProperties.class, OpenTelemetryProperties.class }) public class OtlpMetricsExportAutoConfiguration { private final OtlpProperties properties; - public OtlpMetricsExportAutoConfiguration(OtlpProperties properties) { + OtlpMetricsExportAutoConfiguration(OtlpProperties properties) { this.properties = properties; } @Bean @ConditionalOnMissingBean - public OtlpConfig otlpConfig() { - return new OtlpPropertiesConfigAdapter(this.properties); + OtlpMetricsConnectionDetails otlpMetricsConnectionDetails() { + return new PropertiesOtlpMetricsConnectionDetails(this.properties); + } + + @Bean + @ConditionalOnMissingBean + OtlpConfig otlpConfig(OpenTelemetryProperties openTelemetryProperties, + OtlpMetricsConnectionDetails connectionDetails, Environment environment) { + return new OtlpPropertiesConfigAdapter(this.properties, openTelemetryProperties, connectionDetails, + environment); } @Bean @@ -65,4 +76,22 @@ public OtlpMeterRegistry otlpMeterRegistry(OtlpConfig otlpConfig, Clock clock) { return new OtlpMeterRegistry(otlpConfig, clock); } + /** + * Adapts {@link OtlpProperties} to {@link OtlpMetricsConnectionDetails}. + */ + static class PropertiesOtlpMetricsConnectionDetails implements OtlpMetricsConnectionDetails { + + private final OtlpProperties properties; + + PropertiesOtlpMetricsConnectionDetails(OtlpProperties properties) { + this.properties = properties; + } + + @Override + public String getUrl() { + return this.properties.getUrl(); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java index 701d45c30896..e9a038d3e664 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java @@ -17,11 +17,13 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp; import java.util.Map; +import java.util.concurrent.TimeUnit; import io.micrometer.registry.otlp.AggregationTemporality; import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; /** * {@link ConfigurationProperties @ConfigurationProperties} for configuring OTLP metrics @@ -55,6 +57,11 @@ public class OtlpProperties extends StepRegistryProperties { */ private Map headers; + /** + * Time unit for exported metrics. + */ + private TimeUnit baseTimeUnit = TimeUnit.MILLISECONDS; + public String getUrl() { return this.url; } @@ -71,10 +78,13 @@ public void setAggregationTemporality(AggregationTemporality aggregationTemporal this.aggregationTemporality = aggregationTemporality; } + @Deprecated(since = "3.2.0", forRemoval = true) + @DeprecatedConfigurationProperty(replacement = "management.opentelemetry.resource-attributes", since = "3.2.0") public Map getResourceAttributes() { return this.resourceAttributes; } + @Deprecated(since = "3.2.0", forRemoval = true) public void setResourceAttributes(Map resourceAttributes) { this.resourceAttributes = resourceAttributes; } @@ -87,4 +97,12 @@ public void setHeaders(Map headers) { this.headers = headers; } + public TimeUnit getBaseTimeUnit() { + return this.baseTimeUnit; + } + + public void setBaseTimeUnit(TimeUnit baseTimeUnit) { + this.baseTimeUnit = baseTimeUnit; + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapter.java index 814298d364e3..d30e8b7eb064 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,23 +16,46 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; +import java.util.concurrent.TimeUnit; import io.micrometer.registry.otlp.AggregationTemporality; import io.micrometer.registry.otlp.OtlpConfig; import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryPropertiesConfigAdapter; +import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryProperties; +import org.springframework.core.env.Environment; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; /** * Adapter to convert {@link OtlpProperties} to an {@link OtlpConfig}. * * @author Eddú Meléndez * @author Jonatan Ivanov + * @author Moritz Halbritter */ class OtlpPropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapter implements OtlpConfig { - OtlpPropertiesConfigAdapter(OtlpProperties properties) { + /** + * Default value for application name if {@code spring.application.name} is not set. + */ + private static final String DEFAULT_APPLICATION_NAME = "unknown_service"; + + private final OpenTelemetryProperties openTelemetryProperties; + + private final OtlpMetricsConnectionDetails connectionDetails; + + private final Environment environment; + + OtlpPropertiesConfigAdapter(OtlpProperties properties, OpenTelemetryProperties openTelemetryProperties, + OtlpMetricsConnectionDetails connectionDetails, Environment environment) { super(properties); + this.connectionDetails = connectionDetails; + this.openTelemetryProperties = openTelemetryProperties; + this.environment = environment; } @Override @@ -42,7 +65,7 @@ public String prefix() { @Override public String url() { - return get(OtlpProperties::getUrl, OtlpConfig.super::url); + return get((properties) -> this.connectionDetails.getUrl(), OtlpConfig.super::url); } @Override @@ -51,8 +74,23 @@ public AggregationTemporality aggregationTemporality() { } @Override + @SuppressWarnings("removal") public Map resourceAttributes() { - return get(OtlpProperties::getResourceAttributes, OtlpConfig.super::resourceAttributes); + Map resourceAttributes = this.openTelemetryProperties.getResourceAttributes(); + Map result = new HashMap<>((!CollectionUtils.isEmpty(resourceAttributes)) ? resourceAttributes + : get(OtlpProperties::getResourceAttributes, OtlpConfig.super::resourceAttributes)); + result.computeIfAbsent("service.name", (key) -> getApplicationName()); + result.computeIfAbsent("service.group", (key) -> getApplicationGroup()); + return Collections.unmodifiableMap(result); + } + + private String getApplicationName() { + return this.environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME); + } + + private String getApplicationGroup() { + String applicationGroup = this.environment.getProperty("spring.application.group"); + return (StringUtils.hasLength(applicationGroup)) ? applicationGroup : null; } @Override @@ -60,4 +98,9 @@ public Map headers() { return get(OtlpProperties::getHeaders, OtlpConfig.super::headers); } + @Override + public TimeUnit baseTimeUnit() { + return get(OtlpProperties::getBaseTimeUnit, OtlpConfig.super::baseTimeUnit); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfiguration.java index 2b0751b980fa..1e9c04398a57 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,20 +16,11 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus; -import java.net.MalformedURLException; -import java.net.URL; -import java.time.Duration; -import java.util.Map; - import io.micrometer.core.instrument.Clock; -import io.micrometer.prometheus.PrometheusConfig; -import io.micrometer.prometheus.PrometheusMeterRegistry; -import io.prometheus.client.CollectorRegistry; -import io.prometheus.client.exemplars.DefaultExemplarSampler; -import io.prometheus.client.exemplars.ExemplarSampler; -import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier; -import io.prometheus.client.exporter.BasicAuthHttpConnectionFactory; -import io.prometheus.client.exporter.PushGateway; +import io.micrometer.prometheusmetrics.PrometheusConfig; +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.tracer.common.SpanContext; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; @@ -37,20 +28,16 @@ import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.export.ConditionalOnEnabledMetricsExport; import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; -import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager; -import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager.ShutdownOperation; import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusScrapeEndpoint; +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusSimpleclientScrapeEndpoint; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; -import org.springframework.util.StringUtils; /** * {@link EnableAutoConfiguration Auto-configuration} for exporting metrics to Prometheus. @@ -71,85 +58,33 @@ public class PrometheusMetricsExportAutoConfiguration { @Bean @ConditionalOnMissingBean - public PrometheusConfig prometheusConfig(PrometheusProperties prometheusProperties) { + PrometheusConfig prometheusConfig(PrometheusProperties prometheusProperties) { return new PrometheusPropertiesConfigAdapter(prometheusProperties); } @Bean @ConditionalOnMissingBean - public PrometheusMeterRegistry prometheusMeterRegistry(PrometheusConfig prometheusConfig, - CollectorRegistry collectorRegistry, Clock clock, ObjectProvider exemplarSamplerProvider) { - return new PrometheusMeterRegistry(prometheusConfig, collectorRegistry, clock, - exemplarSamplerProvider.getIfAvailable()); + PrometheusMeterRegistry prometheusMeterRegistry(PrometheusConfig prometheusConfig, + PrometheusRegistry prometheusRegistry, Clock clock, ObjectProvider spanContext) { + return new PrometheusMeterRegistry(prometheusConfig, prometheusRegistry, clock, spanContext.getIfAvailable()); } @Bean @ConditionalOnMissingBean - public CollectorRegistry collectorRegistry() { - return new CollectorRegistry(true); - } - - @Bean - @ConditionalOnMissingBean(ExemplarSampler.class) - @ConditionalOnBean(SpanContextSupplier.class) - public DefaultExemplarSampler exemplarSampler(SpanContextSupplier spanContextSupplier) { - return new DefaultExemplarSampler(spanContextSupplier); + PrometheusRegistry prometheusRegistry() { + return new PrometheusRegistry(); } @Configuration(proxyBeanMethods = false) @ConditionalOnAvailableEndpoint(endpoint = PrometheusScrapeEndpoint.class) - public static class PrometheusScrapeEndpointConfiguration { - - @Bean - @ConditionalOnMissingBean - public PrometheusScrapeEndpoint prometheusEndpoint(CollectorRegistry collectorRegistry) { - return new PrometheusScrapeEndpoint(collectorRegistry); - } - - } - - /** - * Configuration for Prometheus - * Pushgateway. - */ - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(PushGateway.class) - @ConditionalOnProperty(prefix = "management.prometheus.metrics.export.pushgateway", name = "enabled") - public static class PrometheusPushGatewayConfiguration { - - /** - * The fallback job name. We use 'spring' since there's a history of Prometheus - * spring integration defaulting to that name from when Prometheus integration - * didn't exist in Spring itself. - */ - private static final String FALLBACK_JOB = "spring"; + static class PrometheusScrapeEndpointConfiguration { + @SuppressWarnings("removal") @Bean - @ConditionalOnMissingBean - public PrometheusPushGatewayManager prometheusPushGatewayManager(CollectorRegistry collectorRegistry, - PrometheusProperties prometheusProperties, Environment environment) throws MalformedURLException { - PrometheusProperties.Pushgateway properties = prometheusProperties.getPushgateway(); - Duration pushRate = properties.getPushRate(); - String job = getJob(properties, environment); - Map groupingKey = properties.getGroupingKey(); - ShutdownOperation shutdownOperation = properties.getShutdownOperation(); - PushGateway pushGateway = initializePushGateway(properties.getBaseUrl()); - if (StringUtils.hasText(properties.getUsername())) { - pushGateway.setConnectionFactory( - new BasicAuthHttpConnectionFactory(properties.getUsername(), properties.getPassword())); - } - return new PrometheusPushGatewayManager(pushGateway, collectorRegistry, pushRate, job, groupingKey, - shutdownOperation); - } - - private PushGateway initializePushGateway(String url) throws MalformedURLException { - return new PushGateway(new URL(url)); - } - - private String getJob(PrometheusProperties.Pushgateway properties, Environment environment) { - String job = properties.getJob(); - job = (job != null) ? job : environment.getProperty("spring.application.name"); - return (job != null) ? job : FALLBACK_JOB; + @ConditionalOnMissingBean({ PrometheusScrapeEndpoint.class, PrometheusSimpleclientScrapeEndpoint.class }) + PrometheusScrapeEndpoint prometheusEndpoint(PrometheusRegistry prometheusRegistry, + PrometheusConfig prometheusConfig) { + return new PrometheusScrapeEndpoint(prometheusRegistry, prometheusConfig.prometheusProperties()); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusProperties.java index 0b635e345c61..ea5991ad4d26 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,10 +20,9 @@ import java.util.HashMap; import java.util.Map; -import io.micrometer.prometheus.HistogramFlavor; - import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager.ShutdownOperation; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; /** * {@link ConfigurationProperties @ConfigurationProperties} for configuring metrics export @@ -56,8 +55,14 @@ public class PrometheusProperties { /** * Histogram type for backing DistributionSummary and Timer. */ + @Deprecated(since = "3.3.0", forRemoval = true) private HistogramFlavor histogramFlavor = HistogramFlavor.Prometheus; + /** + * Additional properties to pass to the Prometheus client. + */ + private final Map properties = new HashMap<>(); + /** * Step size (i.e. reporting frequency) to use. */ @@ -71,6 +76,9 @@ public void setDescriptions(boolean descriptions) { this.descriptions = descriptions; } + @Deprecated(since = "3.3.0", forRemoval = true) + @DeprecatedConfigurationProperty(since = "3.3.0", + reason = "No longer supported. Works only when using the Prometheus simpleclient.") public HistogramFlavor getHistogramFlavor() { return this.histogramFlavor; } @@ -99,6 +107,10 @@ public Pushgateway getPushgateway() { return this.pushgateway; } + public Map getProperties() { + return this.properties; + } + /** * Configuration options for push-based interaction with Prometheus. */ @@ -210,4 +222,16 @@ public void setShutdownOperation(ShutdownOperation shutdownOperation) { } + /** + * Prometheus Histogram flavor. + * + * @deprecated since 3.3.0 for removal in 3.5.0 + */ + @Deprecated(since = "3.3.0", forRemoval = true) + public enum HistogramFlavor { + + Prometheus, VictoriaMetrics; + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusPropertiesConfigAdapter.java index 5e2b4f42f328..1d5b7aa5f022 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusPropertiesConfigAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,10 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus; import java.time.Duration; +import java.util.Map; +import java.util.Properties; -import io.micrometer.prometheus.HistogramFlavor; -import io.micrometer.prometheus.PrometheusConfig; +import io.micrometer.prometheusmetrics.PrometheusConfig; import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.PropertiesConfigAdapter; @@ -52,13 +53,26 @@ public boolean descriptions() { } @Override - public HistogramFlavor histogramFlavor() { - return get(PrometheusProperties::getHistogramFlavor, PrometheusConfig.super::histogramFlavor); + public Duration step() { + return get(PrometheusProperties::getStep, PrometheusConfig.super::step); } @Override - public Duration step() { - return get(PrometheusProperties::getStep, PrometheusConfig.super::step); + public Properties prometheusProperties() { + return get(this::fromPropertiesMap, PrometheusConfig.super::prometheusProperties); + } + + private Properties fromPropertiesMap(PrometheusProperties prometheusProperties) { + Map additionalProperties = prometheusProperties.getProperties(); + if (additionalProperties.isEmpty()) { + return null; + } + Properties properties = PrometheusConfig.super.prometheusProperties(); + if (properties == null) { + properties = new Properties(); + } + properties.putAll(additionalProperties); + return properties; } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusSimpleclientMetricsExportAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusSimpleclientMetricsExportAutoConfiguration.java new file mode 100644 index 000000000000..47f401c5002e --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusSimpleclientMetricsExportAutoConfiguration.java @@ -0,0 +1,162 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus; + +import java.net.MalformedURLException; +import java.net.URL; +import java.time.Duration; +import java.util.Map; + +import io.micrometer.core.instrument.Clock; +import io.prometheus.client.CollectorRegistry; +import io.prometheus.client.exemplars.DefaultExemplarSampler; +import io.prometheus.client.exemplars.ExemplarSampler; +import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier; +import io.prometheus.client.exporter.BasicAuthHttpConnectionFactory; +import io.prometheus.client.exporter.PushGateway; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; +import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.export.ConditionalOnEnabledMetricsExport; +import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager; +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager.ShutdownOperation; +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusScrapeEndpoint; +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusSimpleclientScrapeEndpoint; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for exporting metrics to Prometheus + * with the Prometheus simpleclient. + * + * @author Jon Schneider + * @author David J. M. Karlsen + * @author Jonatan Ivanov + * @since 2.0.0 + * @deprecated since 3.3.0 for removal in 3.5.0 in favor of + * {@link PrometheusMetricsExportAutoConfiguration} + */ +@Deprecated(since = "3.3.0", forRemoval = true) +@AutoConfiguration( + before = { CompositeMeterRegistryAutoConfiguration.class, SimpleMetricsExportAutoConfiguration.class }, + after = { MetricsAutoConfiguration.class, PrometheusMetricsExportAutoConfiguration.class }) +@ConditionalOnBean(Clock.class) +@ConditionalOnClass(io.micrometer.prometheus.PrometheusMeterRegistry.class) +@ConditionalOnEnabledMetricsExport("prometheus") +@EnableConfigurationProperties(PrometheusProperties.class) +public class PrometheusSimpleclientMetricsExportAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + io.micrometer.prometheus.PrometheusConfig simpleclientPrometheusConfig(PrometheusProperties prometheusProperties) { + return new PrometheusSimpleclientPropertiesConfigAdapter(prometheusProperties); + } + + @Bean + @ConditionalOnMissingBean + io.micrometer.prometheus.PrometheusMeterRegistry simpleclientPrometheusMeterRegistry( + io.micrometer.prometheus.PrometheusConfig prometheusConfig, CollectorRegistry collectorRegistry, + Clock clock, ObjectProvider exemplarSamplerProvider) { + return new io.micrometer.prometheus.PrometheusMeterRegistry(prometheusConfig, collectorRegistry, clock, + exemplarSamplerProvider.getIfAvailable()); + } + + @Bean + @ConditionalOnMissingBean + CollectorRegistry collectorRegistry() { + return new CollectorRegistry(true); + } + + @Bean + @ConditionalOnMissingBean(ExemplarSampler.class) + @ConditionalOnBean(SpanContextSupplier.class) + DefaultExemplarSampler exemplarSampler(SpanContextSupplier spanContextSupplier) { + return new DefaultExemplarSampler(spanContextSupplier); + } + + @SuppressWarnings("removal") + @Configuration(proxyBeanMethods = false) + @ConditionalOnAvailableEndpoint(endpoint = PrometheusSimpleclientScrapeEndpoint.class) + static class PrometheusScrapeEndpointConfiguration { + + @Bean + @ConditionalOnMissingBean({ PrometheusSimpleclientScrapeEndpoint.class, PrometheusScrapeEndpoint.class }) + PrometheusSimpleclientScrapeEndpoint prometheusEndpoint(CollectorRegistry collectorRegistry) { + return new PrometheusSimpleclientScrapeEndpoint(collectorRegistry); + } + + } + + /** + * Configuration for Prometheus + * Pushgateway. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(PushGateway.class) + @ConditionalOnProperty(prefix = "management.prometheus.metrics.export.pushgateway", name = "enabled") + static class PrometheusPushGatewayConfiguration { + + /** + * The fallback job name. We use 'spring' since there's a history of Prometheus + * spring integration defaulting to that name from when Prometheus integration + * didn't exist in Spring itself. + */ + private static final String FALLBACK_JOB = "spring"; + + @Bean + @ConditionalOnMissingBean + PrometheusPushGatewayManager prometheusPushGatewayManager(CollectorRegistry collectorRegistry, + PrometheusProperties prometheusProperties, Environment environment) throws MalformedURLException { + PrometheusProperties.Pushgateway properties = prometheusProperties.getPushgateway(); + Duration pushRate = properties.getPushRate(); + String job = getJob(properties, environment); + Map groupingKey = properties.getGroupingKey(); + ShutdownOperation shutdownOperation = properties.getShutdownOperation(); + PushGateway pushGateway = initializePushGateway(properties.getBaseUrl()); + if (StringUtils.hasText(properties.getUsername())) { + pushGateway.setConnectionFactory( + new BasicAuthHttpConnectionFactory(properties.getUsername(), properties.getPassword())); + } + return new PrometheusPushGatewayManager(pushGateway, collectorRegistry, pushRate, job, groupingKey, + shutdownOperation); + } + + private PushGateway initializePushGateway(String url) throws MalformedURLException { + return new PushGateway(new URL(url)); + } + + private String getJob(PrometheusProperties.Pushgateway properties, Environment environment) { + String job = properties.getJob(); + job = (job != null) ? job : environment.getProperty("spring.application.name"); + return (job != null) ? job : FALLBACK_JOB; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusSimpleclientPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusSimpleclientPropertiesConfigAdapter.java new file mode 100644 index 000000000000..d685f1bdcf31 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusSimpleclientPropertiesConfigAdapter.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus; + +import java.time.Duration; + +import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.PropertiesConfigAdapter; + +/** + * Adapter to convert {@link PrometheusProperties} to a + * {@link io.micrometer.prometheus.PrometheusConfig}. + * + * @author Jon Schneider + * @author Phillip Webb + */ +@SuppressWarnings({ "deprecation", "removal" }) +class PrometheusSimpleclientPropertiesConfigAdapter extends PropertiesConfigAdapter + implements io.micrometer.prometheus.PrometheusConfig { + + PrometheusSimpleclientPropertiesConfigAdapter(PrometheusProperties properties) { + super(properties); + } + + @Override + public String prefix() { + return "management.prometheus.metrics.export"; + } + + @Override + public String get(String key) { + return null; + } + + @Override + public boolean descriptions() { + return get(PrometheusProperties::isDescriptions, io.micrometer.prometheus.PrometheusConfig.super::descriptions); + } + + @Override + public io.micrometer.prometheus.HistogramFlavor histogramFlavor() { + return get(PrometheusSimpleclientPropertiesConfigAdapter::mapToMicrometerHistogramFlavor, + io.micrometer.prometheus.PrometheusConfig.super::histogramFlavor); + } + + static io.micrometer.prometheus.HistogramFlavor mapToMicrometerHistogramFlavor(PrometheusProperties properties) { + return switch (properties.getHistogramFlavor()) { + case Prometheus -> io.micrometer.prometheus.HistogramFlavor.Prometheus; + case VictoriaMetrics -> io.micrometer.prometheus.HistogramFlavor.VictoriaMetrics; + }; + } + + @Override + public Duration step() { + return get(PrometheusProperties::getStep, io.micrometer.prometheus.PrometheusConfig.super::step); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/signalfx/SignalFxProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/signalfx/SignalFxProperties.java index 3a5735537a3f..0b9555b9aecb 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/signalfx/SignalFxProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/signalfx/SignalFxProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -116,7 +116,7 @@ public enum HistogramType { /** * Delta histogram. */ - DELTA; + DELTA } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapter.java index 9ffa626b2bb9..bd898ddad050 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapter.java @@ -16,6 +16,7 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront; +import com.wavefront.sdk.common.clients.service.token.TokenService.Type; import io.micrometer.wavefront.WavefrontConfig; import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.PushRegistryPropertiesConfigAdapter; @@ -84,4 +85,9 @@ public boolean reportDayDistribution() { return get(Export::isReportDayDistribution, WavefrontConfig.super::reportDayDistribution); } + @Override + public Type apiTokenType() { + return this.properties.getWavefrontApiTokenType(); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/JerseyServerMetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/JerseyServerMetricsAutoConfiguration.java index 7bd894f08a0e..b37b87c0080d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/JerseyServerMetricsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/JerseyServerMetricsAutoConfiguration.java @@ -16,32 +16,25 @@ package org.springframework.boot.actuate.autoconfigure.metrics.jersey; -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; - -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.binder.jersey.server.AnnotationFinder; -import io.micrometer.core.instrument.binder.jersey.server.DefaultJerseyTagsProvider; -import io.micrometer.core.instrument.binder.jersey.server.JerseyTagsProvider; -import io.micrometer.core.instrument.binder.jersey.server.MetricsApplicationEventListener; import io.micrometer.core.instrument.config.MeterFilter; +import io.micrometer.observation.ObservationRegistry; +import org.glassfish.jersey.micrometer.server.JerseyObservationConvention; +import org.glassfish.jersey.micrometer.server.ObservationApplicationEventListener; import org.glassfish.jersey.server.ResourceConfig; -import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties; -import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Web.Server; import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter; -import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.Order; /** @@ -50,56 +43,38 @@ * @author Michael Weirauch * @author Michael Simons * @author Andy Wilkinson + * @author Moritz Halbritter * @since 2.1.0 */ -@AutoConfiguration(after = { MetricsAutoConfiguration.class, SimpleMetricsExportAutoConfiguration.class }) +@AutoConfiguration(after = { ObservationAutoConfiguration.class }) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) -@ConditionalOnClass({ ResourceConfig.class, MetricsApplicationEventListener.class }) -@ConditionalOnBean({ MeterRegistry.class, ResourceConfig.class }) -@EnableConfigurationProperties(MetricsProperties.class) -@SuppressWarnings("removal") +@ConditionalOnClass({ ResourceConfig.class, ObservationApplicationEventListener.class }) +@ConditionalOnBean({ ResourceConfig.class, ObservationRegistry.class }) +@EnableConfigurationProperties({ MetricsProperties.class, ObservationProperties.class }) public class JerseyServerMetricsAutoConfiguration { - private final MetricsProperties properties; - - public JerseyServerMetricsAutoConfiguration(MetricsProperties properties) { - this.properties = properties; - } + private final ObservationProperties observationProperties; - @Bean - @ConditionalOnMissingBean(JerseyTagsProvider.class) - public DefaultJerseyTagsProvider jerseyTagsProvider() { - return new DefaultJerseyTagsProvider(); + public JerseyServerMetricsAutoConfiguration(ObservationProperties observationProperties) { + this.observationProperties = observationProperties; } @Bean - public ResourceConfigCustomizer jerseyServerMetricsResourceConfigCustomizer(MeterRegistry meterRegistry, - JerseyTagsProvider tagsProvider) { - Server server = this.properties.getWeb().getServer(); - return (config) -> config.register(new MetricsApplicationEventListener(meterRegistry, tagsProvider, - server.getRequest().getMetricName(), true, new AnnotationUtilsAnnotationFinder())); + ResourceConfigCustomizer jerseyServerObservationResourceConfigCustomizer(ObservationRegistry observationRegistry, + ObjectProvider jerseyObservationConvention) { + String metricName = this.observationProperties.getHttp().getServer().getRequests().getName(); + return (config) -> config.register(new ObservationApplicationEventListener(observationRegistry, metricName, + jerseyObservationConvention.getIfAvailable())); } @Bean @Order(0) - public MeterFilter jerseyMetricsUriTagFilter() { - String metricName = this.properties.getWeb().getServer().getRequest().getMetricName(); + public MeterFilter jerseyMetricsUriTagFilter(MetricsProperties metricsProperties) { + String metricName = this.observationProperties.getHttp().getServer().getRequests().getName(); MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter( () -> String.format("Reached the maximum number of URI tags for '%s'.", metricName)); - return MeterFilter.maximumAllowableTags(metricName, "uri", this.properties.getWeb().getServer().getMaxUriTags(), - filter); - } - - /** - * An {@link AnnotationFinder} that uses {@link AnnotationUtils}. - */ - private static final class AnnotationUtilsAnnotationFinder implements AnnotationFinder { - - @Override - public A findAnnotation(AnnotatedElement annotatedElement, Class annotationType) { - return AnnotationUtils.findAnnotation(annotatedElement, annotationType); - } - + return MeterFilter.maximumAllowableTags(metricName, "uri", + metricsProperties.getWeb().getServer().getMaxUriTags(), filter); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java index 216bc4262408..b4d9760e86f7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; +import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler.IgnoredMeters; import io.micrometer.core.instrument.observation.MeterObservationHandler; import io.micrometer.observation.GlobalObservationConvention; import io.micrometer.observation.Observation; @@ -27,9 +28,11 @@ import io.micrometer.observation.ObservationHandler; import io.micrometer.observation.ObservationPredicate; import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.aop.ObservedAspect; import io.micrometer.tracing.Tracer; import io.micrometer.tracing.handler.TracingAwareMeterObservationHandler; import io.micrometer.tracing.handler.TracingObservationHandler; +import org.aspectj.weaver.Advice; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; @@ -43,6 +46,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; /** * {@link EnableAutoConfiguration Auto-configuration} for the Micrometer Observation API. @@ -50,6 +54,7 @@ * @author Moritz Halbritter * @author Brian Clozel * @author Jonatan Ivanov + * @author Vedran Pavic * @since 3.0.0 */ @AutoConfiguration(after = { CompositeMeterRegistryAutoConfiguration.class, MicrometerTracingAutoConfiguration.class }) @@ -75,6 +80,12 @@ ObservationRegistry observationRegistry() { return ObservationRegistry.create(); } + @Bean + @Order(0) + PropertiesObservationFilterPredicate propertiesObservationFilter(ObservationProperties properties) { + return new PropertiesObservationFilterPredicate(properties); + } + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(MeterRegistry.class) @ConditionalOnMissingClass("io.micrometer.tracing.Tracer") @@ -121,8 +132,10 @@ static class MeterObservationHandlerConfiguration { static class OnlyMetricsMeterObservationHandlerConfiguration { @Bean - DefaultMeterObservationHandler defaultMeterObservationHandler(MeterRegistry meterRegistry) { - return new DefaultMeterObservationHandler(meterRegistry); + DefaultMeterObservationHandler defaultMeterObservationHandler(MeterRegistry meterRegistry, + ObservationProperties properties) { + return properties.getLongTaskTimer().isEnabled() ? new DefaultMeterObservationHandler(meterRegistry) + : new DefaultMeterObservationHandler(meterRegistry, IgnoredMeters.LONG_TASK_TIMER); } } @@ -133,8 +146,10 @@ static class TracingAndMetricsObservationHandlerConfiguration { @Bean TracingAwareMeterObservationHandler tracingAwareMeterObservationHandler( - MeterRegistry meterRegistry, Tracer tracer) { - DefaultMeterObservationHandler delegate = new DefaultMeterObservationHandler(meterRegistry); + MeterRegistry meterRegistry, Tracer tracer, ObservationProperties properties) { + DefaultMeterObservationHandler delegate = properties.getLongTaskTimer().isEnabled() + ? new DefaultMeterObservationHandler(meterRegistry) + : new DefaultMeterObservationHandler(meterRegistry, IgnoredMeters.LONG_TASK_TIMER); return new TracingAwareMeterObservationHandler<>(delegate, tracer); } @@ -142,4 +157,16 @@ TracingAwareMeterObservationHandler tracingAwareMeterObserv } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(Advice.class) + static class ObservedAspectConfiguration { + + @Bean + @ConditionalOnMissingBean + ObservedAspect observedAspect(ObservationRegistry observationRegistry) { + return new ObservedAspect(observationRegistry); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationHandlerGrouping.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationHandlerGrouping.java index 163186947e8a..df964c813366 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationHandlerGrouping.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationHandlerGrouping.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.boot.actuate.autoconfigure.observation; +import java.util.ArrayList; import java.util.List; import io.micrometer.observation.ObservationHandler; @@ -30,6 +31,7 @@ * Groups {@link ObservationHandler ObservationHandlers} by type. * * @author Andy Wilkinson + * @author Moritz Halbritter */ @SuppressWarnings("rawtypes") class ObservationHandlerGrouping { @@ -46,13 +48,14 @@ class ObservationHandlerGrouping { void apply(List> handlers, ObservationConfig config) { MultiValueMap, ObservationHandler> groupings = new LinkedMultiValueMap<>(); + List> handlersWithoutCategory = new ArrayList<>(); for (ObservationHandler handler : handlers) { Class category = findCategory(handler); if (category != null) { groupings.add(category, handler); } else { - config.observationHandler(handler); + handlersWithoutCategory.add(handler); } } for (Class category : this.categories) { @@ -61,6 +64,9 @@ void apply(List> handlers, ObservationConfig config) { config.observationHandler(new FirstMatchingCompositeObservationHandler(handlerGroup)); } } + for (ObservationHandler observationHandler : handlersWithoutCategory) { + config.observationHandler(observationHandler); + } } private Class findCategory(ObservationHandler handler) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java index e4668d7f4b59..93d0e7152464 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,9 @@ package org.springframework.boot.actuate.autoconfigure.observation; +import java.util.LinkedHashMap; +import java.util.Map; + import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -23,6 +26,7 @@ * observations. * * @author Brian Clozel + * @author Moritz Halbritter * @since 3.0.0 */ @ConfigurationProperties("management.observations") @@ -30,10 +34,43 @@ public class ObservationProperties { private final Http http = new Http(); + /** + * Common key-values that are applied to every observation. + */ + private Map keyValues = new LinkedHashMap<>(); + + /** + * Whether observations starting with the specified name should be enabled. The + * longest match wins, the key 'all' can also be used to configure all observations. + */ + private Map enable = new LinkedHashMap<>(); + + private final LongTaskTimer longTaskTimer = new LongTaskTimer(); + + public Map getEnable() { + return this.enable; + } + + public void setEnable(Map enable) { + this.enable = enable; + } + public Http getHttp() { return this.http; } + public Map getKeyValues() { + return this.keyValues; + } + + public void setKeyValues(Map keyValues) { + this.keyValues = keyValues; + } + + public LongTaskTimer getLongTaskTimer() { + return this.longTaskTimer; + } + public static class Http { private final Client client = new Client(); @@ -59,10 +96,9 @@ public ClientRequests getRequests() { public static class ClientRequests { /** - * Name of the observation for client requests. If empty, will use the - * default "http.client.requests". + * Name of the observation for client requests. */ - private String name; + private String name = "http.client.requests"; public String getName() { return this.name; @@ -87,10 +123,9 @@ public ServerRequests getRequests() { public static class ServerRequests { /** - * Name of the observation for server requests. If empty, will use the - * default "http.server.requests". + * Name of the observation for server requests. */ - private String name; + private String name = "http.server.requests"; public String getName() { return this.name; @@ -106,4 +141,21 @@ public void setName(String name) { } + public static class LongTaskTimer { + + /** + * Whether to create a LongTaskTimer for every observation. + */ + private boolean enabled = true; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterPredicate.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterPredicate.java new file mode 100644 index 000000000000..1154668798af --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterPredicate.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.observation; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Supplier; + +import io.micrometer.common.KeyValues; +import io.micrometer.observation.Observation.Context; +import io.micrometer.observation.ObservationFilter; +import io.micrometer.observation.ObservationPredicate; + +import org.springframework.util.StringUtils; + +/** + * {@link ObservationFilter} to apply settings from {@link ObservationProperties}. + * + * @author Moritz Halbritter + */ +class PropertiesObservationFilterPredicate implements ObservationFilter, ObservationPredicate { + + private final ObservationFilter commonKeyValuesFilter; + + private final ObservationProperties properties; + + PropertiesObservationFilterPredicate(ObservationProperties properties) { + this.properties = properties; + this.commonKeyValuesFilter = createCommonKeyValuesFilter(properties); + } + + @Override + public Context map(Context context) { + return this.commonKeyValuesFilter.map(context); + } + + @Override + public boolean test(String name, Context context) { + return lookupWithFallbackToAll(this.properties.getEnable(), name, true); + } + + private static T lookupWithFallbackToAll(Map values, String name, T defaultValue) { + if (values.isEmpty()) { + return defaultValue; + } + return doLookup(values, name, () -> values.getOrDefault("all", defaultValue)); + } + + private static T doLookup(Map values, String name, Supplier defaultValue) { + while (StringUtils.hasLength(name)) { + T result = values.get(name); + if (result != null) { + return result; + } + int lastDot = name.lastIndexOf('.'); + name = (lastDot != -1) ? name.substring(0, lastDot) : ""; + } + return defaultValue.get(); + } + + private static ObservationFilter createCommonKeyValuesFilter(ObservationProperties properties) { + if (properties.getKeyValues().isEmpty()) { + return (context) -> context; + } + KeyValues keyValues = KeyValues.of(properties.getKeyValues().entrySet(), Entry::getKey, Entry::getValue); + return (context) -> context.addLowCardinalityKeyValues(keyValues); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/graphql/GraphQlObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/graphql/GraphQlObservationAutoConfiguration.java index 5c447db00fba..86b5ed0aee35 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/graphql/GraphQlObservationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/graphql/GraphQlObservationAutoConfiguration.java @@ -43,7 +43,6 @@ @AutoConfiguration(after = ObservationAutoConfiguration.class) @ConditionalOnBean(ObservationRegistry.class) @ConditionalOnClass({ GraphQL.class, GraphQlSource.class, Observation.class }) -@SuppressWarnings("removal") public class GraphQlObservationAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientHttpObservationConventionAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientHttpObservationConventionAdapter.java deleted file mode 100644 index 87205527f5e2..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientHttpObservationConventionAdapter.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.observation.web.client; - -import io.micrometer.common.KeyValues; -import io.micrometer.core.instrument.Tag; - -import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTagsProvider; -import org.springframework.http.client.observation.ClientRequestObservationContext; -import org.springframework.http.client.observation.ClientRequestObservationConvention; - -/** - * Adapter class that applies {@link RestTemplateExchangeTagsProvider} tags as a - * {@link ClientRequestObservationConvention}. - * - * @author Brian Clozel - */ -@SuppressWarnings({ "removal" }) -class ClientHttpObservationConventionAdapter implements ClientRequestObservationConvention { - - private final String metricName; - - private final RestTemplateExchangeTagsProvider tagsProvider; - - ClientHttpObservationConventionAdapter(String metricName, RestTemplateExchangeTagsProvider tagsProvider) { - this.metricName = metricName; - this.tagsProvider = tagsProvider; - } - - @Override - @SuppressWarnings("deprecation") - public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) { - Iterable tags = this.tagsProvider.getTags(context.getUriTemplate(), context.getCarrier(), - context.getResponse()); - return KeyValues.of(tags, Tag::getKey, Tag::getValue); - } - - @Override - public KeyValues getHighCardinalityKeyValues(ClientRequestObservationContext context) { - return KeyValues.empty(); - } - - @Override - public String getName() { - return this.metricName; - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapter.java deleted file mode 100644 index 85d8cb25dac0..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapter.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2012-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.observation.web.client; - -import io.micrometer.common.KeyValues; -import io.micrometer.core.instrument.Tag; -import io.micrometer.observation.Observation; - -import org.springframework.boot.actuate.metrics.web.reactive.client.WebClientExchangeTagsProvider; -import org.springframework.core.Conventions; -import org.springframework.web.reactive.function.client.ClientRequest; -import org.springframework.web.reactive.function.client.ClientRequestObservationContext; -import org.springframework.web.reactive.function.client.ClientRequestObservationConvention; -import org.springframework.web.reactive.function.client.WebClient; - -/** - * Adapter class that applies {@link WebClientExchangeTagsProvider} tags as a - * {@link ClientRequestObservationConvention}. - * - * @author Brian Clozel - */ -@SuppressWarnings("removal") -class ClientObservationConventionAdapter implements ClientRequestObservationConvention { - - private static final String URI_TEMPLATE_ATTRIBUTE = Conventions.getQualifiedAttributeName(WebClient.class, - "uriTemplate"); - - private final String metricName; - - private final WebClientExchangeTagsProvider tagsProvider; - - ClientObservationConventionAdapter(String metricName, WebClientExchangeTagsProvider tagsProvider) { - this.metricName = metricName; - this.tagsProvider = tagsProvider; - } - - @Override - public boolean supportsContext(Observation.Context context) { - return context instanceof ClientRequestObservationContext; - } - - @Override - public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) { - ClientRequest request = context.getRequest(); - if (request == null) { - request = context.getCarrier().build(); - } - Iterable tags = this.tagsProvider.tags(request, context.getResponse(), context.getError()); - return KeyValues.of(tags, Tag::getKey, Tag::getValue); - } - - @Override - public KeyValues getHighCardinalityKeyValues(ClientRequestObservationContext context) { - return KeyValues.empty(); - } - - @Override - public String getName() { - return this.metricName; - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/HttpClientObservationsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/HttpClientObservationsAutoConfiguration.java index 23323d25238c..60595014ef39 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/HttpClientObservationsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/HttpClientObservationsAutoConfiguration.java @@ -31,6 +31,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -48,13 +49,15 @@ * @author Stephane Nicoll * @author Raheela Aslam * @author Brian Clozel + * @author Moritz Halbritter * @since 3.0.0 */ @AutoConfiguration(after = { ObservationAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class, - RestTemplateAutoConfiguration.class, WebClientAutoConfiguration.class }) + RestTemplateAutoConfiguration.class, WebClientAutoConfiguration.class, RestClientAutoConfiguration.class }) @ConditionalOnClass(Observation.class) @ConditionalOnBean(ObservationRegistry.class) -@Import({ RestTemplateObservationConfiguration.class, WebClientObservationConfiguration.class }) +@Import({ RestTemplateObservationConfiguration.class, WebClientObservationConfiguration.class, + RestClientObservationConfiguration.class }) @EnableConfigurationProperties({ MetricsProperties.class, ObservationProperties.class }) public class HttpClientObservationsAutoConfiguration { @@ -65,13 +68,10 @@ static class MeterFilterConfiguration { @Bean @Order(0) - @SuppressWarnings("removal") MeterFilter metricsHttpClientUriTagFilter(ObservationProperties observationProperties, MetricsProperties metricsProperties) { Client clientProperties = metricsProperties.getWeb().getClient(); - String metricName = clientProperties.getRequest().getMetricName(); - String observationName = observationProperties.getHttp().getClient().getRequests().getName(); - String name = (observationName != null) ? observationName : metricName; + String name = observationProperties.getHttp().getClient().getRequests().getName(); MeterFilter denyFilter = new OnlyOnceLoggingDenyMeterFilter( () -> "Reached the maximum number of URI tags for '%s'. Are you using 'uriVariables'?" .formatted(name)); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestClientObservationConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestClientObservationConfiguration.java new file mode 100644 index 000000000000..6b97d6c65e51 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestClientObservationConfiguration.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.observation.web.client; + +import io.micrometer.observation.ObservationRegistry; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties; +import org.springframework.boot.actuate.metrics.web.client.ObservationRestClientCustomizer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.web.client.RestClientCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.observation.ClientRequestObservationConvention; +import org.springframework.http.client.observation.DefaultClientRequestObservationConvention; +import org.springframework.web.client.RestClient; + +/** + * Configure the instrumentation of {@link RestClient}. + * + * @author Moritz Halbritter + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(RestClient.class) +@ConditionalOnBean(RestClient.Builder.class) +class RestClientObservationConfiguration { + + @Bean + RestClientCustomizer observationRestClientCustomizer(ObservationRegistry observationRegistry, + ObjectProvider customConvention, + ObservationProperties observationProperties) { + String name = observationProperties.getHttp().getClient().getRequests().getName(); + ClientRequestObservationConvention observationConvention = customConvention + .getIfAvailable(() -> new DefaultClientRequestObservationConvention(name)); + return new ObservationRestClientCustomizer(observationRegistry, observationConvention); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestTemplateObservationConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestTemplateObservationConfiguration.java index 0f62f2849b19..81fb154a230a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestTemplateObservationConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestTemplateObservationConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,8 @@ import io.micrometer.observation.ObservationRegistry; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties; import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties; import org.springframework.boot.actuate.metrics.web.client.ObservationRestTemplateCustomizer; -import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTagsProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.web.client.RestTemplateBuilder; @@ -40,39 +38,16 @@ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(RestTemplateBuilder.class) -@SuppressWarnings("removal") class RestTemplateObservationConfiguration { @Bean ObservationRestTemplateCustomizer observationRestTemplateCustomizer(ObservationRegistry observationRegistry, ObjectProvider customConvention, - ObservationProperties observationProperties, MetricsProperties metricsProperties, - ObjectProvider optionalTagsProvider) { - String name = observationName(observationProperties, metricsProperties); - ClientRequestObservationConvention observationConvention = createConvention(customConvention.getIfAvailable(), - name, optionalTagsProvider.getIfAvailable()); + ObservationProperties observationProperties) { + String name = observationProperties.getHttp().getClient().getRequests().getName(); + ClientRequestObservationConvention observationConvention = customConvention + .getIfAvailable(() -> new DefaultClientRequestObservationConvention(name)); return new ObservationRestTemplateCustomizer(observationRegistry, observationConvention); } - private static String observationName(ObservationProperties observationProperties, - MetricsProperties metricsProperties) { - String metricName = metricsProperties.getWeb().getClient().getRequest().getMetricName(); - String observationName = observationProperties.getHttp().getClient().getRequests().getName(); - return (observationName != null) ? observationName : metricName; - } - - private static ClientRequestObservationConvention createConvention( - ClientRequestObservationConvention customConvention, String name, - RestTemplateExchangeTagsProvider tagsProvider) { - if (customConvention != null) { - return customConvention; - } - else if (tagsProvider != null) { - return new ClientHttpObservationConventionAdapter(name, tagsProvider); - } - else { - return new DefaultClientRequestObservationConvention(name); - } - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/WebClientObservationConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/WebClientObservationConfiguration.java index ce531912e1dc..2df9c4bf9104 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/WebClientObservationConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/WebClientObservationConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties; import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties; import org.springframework.boot.actuate.metrics.web.reactive.client.ObservationWebClientCustomizer; -import org.springframework.boot.actuate.metrics.web.reactive.client.WebClientExchangeTagsProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -37,39 +36,16 @@ */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(WebClient.class) -@SuppressWarnings("removal") class WebClientObservationConfiguration { @Bean ObservationWebClientCustomizer observationWebClientCustomizer(ObservationRegistry observationRegistry, ObjectProvider customConvention, - ObservationProperties observationProperties, ObjectProvider tagsProvider, - MetricsProperties metricsProperties) { - String name = observationName(observationProperties, metricsProperties); - ClientRequestObservationConvention observationConvention = createConvention(customConvention.getIfAvailable(), - tagsProvider.getIfAvailable(), name); + ObservationProperties observationProperties, MetricsProperties metricsProperties) { + String name = observationProperties.getHttp().getClient().getRequests().getName(); + ClientRequestObservationConvention observationConvention = customConvention + .getIfAvailable(() -> new DefaultClientRequestObservationConvention(name)); return new ObservationWebClientCustomizer(observationRegistry, observationConvention); } - private static ClientRequestObservationConvention createConvention( - ClientRequestObservationConvention customConvention, WebClientExchangeTagsProvider tagsProvider, - String name) { - if (customConvention != null) { - return customConvention; - } - else if (tagsProvider != null) { - return new ClientObservationConventionAdapter(name, tagsProvider); - } - else { - return new DefaultClientRequestObservationConvention(name); - } - } - - private static String observationName(ObservationProperties observationProperties, - MetricsProperties metricsProperties) { - String metricName = metricsProperties.getWeb().getClient().getRequest().getMetricName(); - String observationName = observationProperties.getHttp().getClient().getRequests().getName(); - return (observationName != null) ? observationName : metricName; - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapter.java deleted file mode 100644 index 43689a962a05..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapter.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.observation.web.reactive; - -import java.util.List; - -import io.micrometer.common.KeyValues; -import io.micrometer.core.instrument.Tag; - -import org.springframework.boot.actuate.metrics.web.reactive.server.DefaultWebFluxTagsProvider; -import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsContributor; -import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsProvider; -import org.springframework.http.codec.ServerCodecConfigurer; -import org.springframework.http.server.reactive.observation.ServerRequestObservationContext; -import org.springframework.http.server.reactive.observation.ServerRequestObservationConvention; -import org.springframework.web.server.adapter.DefaultServerWebExchange; -import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver; -import org.springframework.web.server.i18n.LocaleContextResolver; -import org.springframework.web.server.session.DefaultWebSessionManager; -import org.springframework.web.server.session.WebSessionManager; - -/** - * Adapter class that applies {@link WebFluxTagsProvider} tags as a - * {@link ServerRequestObservationConvention}. - * - * @author Brian Clozel - */ -@SuppressWarnings("removal") -@Deprecated(since = "3.0.0", forRemoval = true) -class ServerRequestObservationConventionAdapter implements ServerRequestObservationConvention { - - private final WebSessionManager webSessionManager = new DefaultWebSessionManager(); - - private final ServerCodecConfigurer serverCodecConfigurer = ServerCodecConfigurer.create(); - - private final LocaleContextResolver localeContextResolver = new AcceptHeaderLocaleContextResolver(); - - private final String name; - - private final WebFluxTagsProvider tagsProvider; - - ServerRequestObservationConventionAdapter(String name, WebFluxTagsProvider tagsProvider) { - this.name = name; - this.tagsProvider = tagsProvider; - } - - ServerRequestObservationConventionAdapter(String name, List contributors) { - this(name, new DefaultWebFluxTagsProvider(contributors)); - } - - @Override - public String getName() { - return this.name; - } - - @Override - public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) { - DefaultServerWebExchange serverWebExchange = new DefaultServerWebExchange(context.getCarrier(), - context.getResponse(), this.webSessionManager, this.serverCodecConfigurer, this.localeContextResolver); - serverWebExchange.getAttributes().putAll(context.getAttributes()); - Iterable tags = this.tagsProvider.httpRequestTags(serverWebExchange, context.getError()); - return KeyValues.of(tags, Tag::getKey, Tag::getValue); - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java index a998e6b5d24b..94d125f63f2a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,23 +16,16 @@ package org.springframework.boot.actuate.autoconfigure.observation.web.reactive; -import java.util.List; - import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.config.MeterFilter; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties; import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter; import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties; -import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsContributor; -import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -41,12 +34,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention; import org.springframework.http.server.reactive.observation.ServerRequestObservationConvention; -import org.springframework.web.filter.reactive.ServerHttpObservationFilter; /** * {@link EnableAutoConfiguration Auto-configuration} for instrumentation of Spring @@ -55,77 +45,37 @@ * @author Brian Clozel * @author Jon Schneider * @author Dmytro Nosan + * @author Moritz Halbritter * @since 3.0.0 */ -@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class, - SimpleMetricsExportAutoConfiguration.class, ObservationAutoConfiguration.class }) -@ConditionalOnClass(Observation.class) -@ConditionalOnBean(ObservationRegistry.class) +@AutoConfiguration(after = { SimpleMetricsExportAutoConfiguration.class, ObservationAutoConfiguration.class }) +@ConditionalOnClass({ Observation.class, MeterRegistry.class }) +@ConditionalOnBean({ ObservationRegistry.class, MeterRegistry.class }) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) @EnableConfigurationProperties({ MetricsProperties.class, ObservationProperties.class }) -@SuppressWarnings("removal") public class WebFluxObservationAutoConfiguration { - private final MetricsProperties metricsProperties; - private final ObservationProperties observationProperties; - public WebFluxObservationAutoConfiguration(MetricsProperties metricsProperties, - ObservationProperties observationProperties) { - this.metricsProperties = metricsProperties; + WebFluxObservationAutoConfiguration(ObservationProperties observationProperties) { this.observationProperties = observationProperties; } @Bean - @ConditionalOnMissingBean - @Order(Ordered.HIGHEST_PRECEDENCE + 1) - public ServerHttpObservationFilter webfluxObservationFilter(ObservationRegistry registry, - ObjectProvider customConvention, - ObjectProvider tagConfigurer, - ObjectProvider contributorsProvider) { - String observationName = this.observationProperties.getHttp().getServer().getRequests().getName(); - String metricName = this.metricsProperties.getWeb().getServer().getRequest().getMetricName(); - String name = (observationName != null) ? observationName : metricName; - WebFluxTagsProvider tagsProvider = tagConfigurer.getIfAvailable(); - List tagsContributors = contributorsProvider.orderedStream().toList(); - ServerRequestObservationConvention convention = createConvention(customConvention.getIfAvailable(), name, - tagsProvider, tagsContributors); - return new ServerHttpObservationFilter(registry, convention); - } - - private static ServerRequestObservationConvention createConvention( - ServerRequestObservationConvention customConvention, String name, WebFluxTagsProvider tagsProvider, - List tagsContributors) { - if (customConvention != null) { - return customConvention; - } - if (tagsProvider != null) { - return new ServerRequestObservationConventionAdapter(name, tagsProvider); - } - if (!tagsContributors.isEmpty()) { - return new ServerRequestObservationConventionAdapter(name, tagsContributors); - } - return new DefaultServerRequestObservationConvention(name); + @Order(0) + MeterFilter metricsHttpServerUriTagFilter(MetricsProperties metricsProperties) { + String name = this.observationProperties.getHttp().getServer().getRequests().getName(); + MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter( + () -> "Reached the maximum number of URI tags for '%s'.".formatted(name)); + return MeterFilter.maximumAllowableTags(name, "uri", metricsProperties.getWeb().getServer().getMaxUriTags(), + filter); } - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(MeterRegistry.class) - @ConditionalOnBean(MeterRegistry.class) - static class MeterFilterConfiguration { - - @Bean - @Order(0) - MeterFilter metricsHttpServerUriTagFilter(MetricsProperties metricsProperties, - ObservationProperties observationProperties) { - String observationName = observationProperties.getHttp().getServer().getRequests().getName(); - String name = (observationName != null) ? observationName - : metricsProperties.getWeb().getServer().getRequest().getMetricName(); - MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter( - () -> "Reached the maximum number of URI tags for '%s'.".formatted(name)); - return MeterFilter.maximumAllowableTags(name, "uri", metricsProperties.getWeb().getServer().getMaxUriTags(), - filter); - } - + @Bean + @ConditionalOnMissingBean(ServerRequestObservationConvention.class) + DefaultServerRequestObservationConvention defaultServerRequestObservationConvention() { + return new DefaultServerRequestObservationConvention( + this.observationProperties.getHttp().getServer().getRequests().getName()); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/ServerRequestObservationConventionAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/ServerRequestObservationConventionAdapter.java deleted file mode 100644 index df52cbca7407..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/ServerRequestObservationConventionAdapter.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.observation.web.servlet; - -import java.util.List; - -import io.micrometer.common.KeyValues; -import io.micrometer.core.instrument.Tag; -import io.micrometer.observation.Observation; - -import org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider; -import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsContributor; -import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider; -import org.springframework.http.server.observation.ServerRequestObservationContext; -import org.springframework.http.server.observation.ServerRequestObservationConvention; -import org.springframework.util.Assert; -import org.springframework.web.servlet.HandlerMapping; - -/** - * Adapter class that applies {@link WebMvcTagsProvider} tags as a - * {@link ServerRequestObservationConvention}. - * - * @author Brian Clozel - */ -@SuppressWarnings("removal") -@Deprecated(since = "3.0.0", forRemoval = true) -class ServerRequestObservationConventionAdapter implements ServerRequestObservationConvention { - - private final String observationName; - - private final WebMvcTagsProvider tagsProvider; - - ServerRequestObservationConventionAdapter(String observationName, WebMvcTagsProvider tagsProvider, - List contributors) { - Assert.state((tagsProvider != null) || (contributors != null), - "adapter should adapt to a WebMvcTagsProvider or a list of contributors"); - this.observationName = observationName; - this.tagsProvider = (tagsProvider != null) ? tagsProvider : new DefaultWebMvcTagsProvider(contributors); - } - - @Override - public String getName() { - return this.observationName; - } - - @Override - public boolean supportsContext(Observation.Context context) { - return context instanceof ServerRequestObservationContext; - } - - @Override - public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) { - Iterable tags = this.tagsProvider.getTags(context.getCarrier(), context.getResponse(), getHandler(context), - context.getError()); - return KeyValues.of(tags, Tag::getKey, Tag::getValue); - } - - private Object getHandler(ServerRequestObservationContext context) { - return context.getCarrier().getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE); - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfiguration.java index 5a70728ac125..2b4aa96c3933 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.boot.actuate.autoconfigure.observation.web.servlet; -import java.util.List; - import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.config.MeterFilter; import io.micrometer.observation.Observation; @@ -32,8 +30,6 @@ import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties; -import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsContributor; -import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -66,28 +62,16 @@ @ConditionalOnClass({ DispatcherServlet.class, Observation.class }) @ConditionalOnBean(ObservationRegistry.class) @EnableConfigurationProperties({ MetricsProperties.class, ObservationProperties.class }) -@SuppressWarnings("removal") public class WebMvcObservationAutoConfiguration { - private final MetricsProperties metricsProperties; - - private final ObservationProperties observationProperties; - - public WebMvcObservationAutoConfiguration(ObservationProperties observationProperties, - MetricsProperties metricsProperties) { - this.observationProperties = observationProperties; - this.metricsProperties = metricsProperties; - } - @Bean @ConditionalOnMissingFilterBean public FilterRegistrationBean webMvcObservationFilter(ObservationRegistry registry, ObjectProvider customConvention, - ObjectProvider customTagsProvider, - ObjectProvider contributorsProvider) { - String name = httpRequestsMetricName(this.observationProperties, this.metricsProperties); - ServerRequestObservationConvention convention = createConvention(customConvention.getIfAvailable(), name, - customTagsProvider.getIfAvailable(), contributorsProvider.orderedStream().toList()); + ObservationProperties observationProperties) { + String name = observationProperties.getHttp().getServer().getRequests().getName(); + ServerRequestObservationConvention convention = customConvention + .getIfAvailable(() -> new DefaultServerRequestObservationConvention(name)); ServerHttpObservationFilter filter = new ServerHttpObservationFilter(registry, convention); FilterRegistrationBean registration = new FilterRegistrationBean<>(filter); registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1); @@ -95,27 +79,6 @@ public FilterRegistrationBean webMvcObservationFilt return registration; } - private static ServerRequestObservationConvention createConvention( - ServerRequestObservationConvention customConvention, String name, WebMvcTagsProvider tagsProvider, - List contributors) { - if (customConvention != null) { - return customConvention; - } - else if (tagsProvider != null || contributors.size() > 0) { - return new ServerRequestObservationConventionAdapter(name, tagsProvider, contributors); - } - else { - return new DefaultServerRequestObservationConvention(name); - } - } - - private static String httpRequestsMetricName(ObservationProperties observationProperties, - MetricsProperties metricsProperties) { - String observationName = observationProperties.getHttp().getServer().getRequests().getName(); - return (observationName != null) ? observationName - : metricsProperties.getWeb().getServer().getRequest().getMetricName(); - } - @Configuration(proxyBeanMethods = false) @ConditionalOnClass(MeterRegistry.class) @ConditionalOnBean(MeterRegistry.class) @@ -123,9 +86,9 @@ static class MeterFilterConfiguration { @Bean @Order(0) - MeterFilter metricsHttpServerUriTagFilter(MetricsProperties metricsProperties, - ObservationProperties observationProperties) { - String name = httpRequestsMetricName(observationProperties, metricsProperties); + MeterFilter metricsHttpServerUriTagFilter(ObservationProperties observationProperties, + MetricsProperties metricsProperties) { + String name = observationProperties.getHttp().getServer().getRequests().getName(); MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter( () -> String.format("Reached the maximum number of URI tags for '%s'.", name)); return MeterFilter.maximumAllowableTags(name, "uri", metricsProperties.getWeb().getServer().getMaxUriTags(), diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfiguration.java new file mode 100644 index 000000000000..720e79142f61 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfiguration.java @@ -0,0 +1,93 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.opentelemetry; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.OpenTelemetrySdkBuilder; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.resources.ResourceBuilder; +import io.opentelemetry.sdk.trace.SdkTracerProvider; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for OpenTelemetry. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +@AutoConfiguration +@ConditionalOnClass(OpenTelemetrySdk.class) +@EnableConfigurationProperties(OpenTelemetryProperties.class) +public class OpenTelemetryAutoConfiguration { + + /** + * Default value for application name if {@code spring.application.name} is not set. + */ + private static final String DEFAULT_APPLICATION_NAME = "unknown_service"; + + private static final AttributeKey ATTRIBUTE_KEY_SERVICE_NAME = AttributeKey.stringKey("service.name"); + + private static final AttributeKey ATTRIBUTE_KEY_SERVICE_GROUP = AttributeKey.stringKey("service.group"); + + @Bean + @ConditionalOnMissingBean(OpenTelemetry.class) + OpenTelemetrySdk openTelemetry(ObjectProvider tracerProvider, + ObjectProvider propagators, ObjectProvider loggerProvider, + ObjectProvider meterProvider) { + OpenTelemetrySdkBuilder builder = OpenTelemetrySdk.builder(); + tracerProvider.ifAvailable(builder::setTracerProvider); + propagators.ifAvailable(builder::setPropagators); + loggerProvider.ifAvailable(builder::setLoggerProvider); + meterProvider.ifAvailable(builder::setMeterProvider); + return builder.build(); + } + + @Bean + @ConditionalOnMissingBean + Resource openTelemetryResource(Environment environment, OpenTelemetryProperties properties) { + String applicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME); + String applicationGroup = environment.getProperty("spring.application.group"); + Resource resource = Resource.getDefault() + .merge(Resource.create(Attributes.of(ATTRIBUTE_KEY_SERVICE_NAME, applicationName))); + if (StringUtils.hasLength(applicationGroup)) { + resource = resource.merge(Resource.create(Attributes.of(ATTRIBUTE_KEY_SERVICE_GROUP, applicationGroup))); + } + return resource.merge(toResource(properties)); + } + + private static Resource toResource(OpenTelemetryProperties properties) { + ResourceBuilder builder = Resource.builder(); + properties.getResourceAttributes().forEach(builder::put); + return builder.build(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryProperties.java new file mode 100644 index 000000000000..4c973ecf578b --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryProperties.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.opentelemetry; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for OpenTelemetry. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +@ConfigurationProperties(prefix = "management.opentelemetry") +public class OpenTelemetryProperties { + + /** + * Resource attributes. + */ + private Map resourceAttributes = new HashMap<>(); + + public Map getResourceAttributes() { + return this.resourceAttributes; + } + + public void setResourceAttributes(Map resourceAttributes) { + this.resourceAttributes = resourceAttributes; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/package-info.java new file mode 100644 index 000000000000..c1aab18823c6 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for OpenTelemetry. + */ +package org.springframework.boot.actuate.autoconfigure.opentelemetry; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfiguration.java new file mode 100644 index 000000000000..fca8478ced74 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfiguration.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.r2dbc; + +import io.micrometer.observation.ObservationRegistry; +import io.r2dbc.proxy.ProxyConnectionFactory; +import io.r2dbc.proxy.observation.ObservationProxyExecutionListener; +import io.r2dbc.proxy.observation.QueryObservationConvention; +import io.r2dbc.proxy.observation.QueryParametersTagProvider; +import io.r2dbc.spi.ConnectionFactory; +import io.r2dbc.spi.ConnectionFactoryOptions; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.r2dbc.ProxyConnectionFactoryCustomizer; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.r2dbc.OptionsCapableConnectionFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.core.annotation.Order; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for R2DBC observability support. + * + * @author Moritz Halbritter + * @author Tadaya Tsuyukubo + * @since 3.2.0 + */ +@AutoConfiguration(after = ObservationAutoConfiguration.class) +@ConditionalOnClass({ ConnectionFactory.class, ProxyConnectionFactory.class }) +@EnableConfigurationProperties(R2dbcObservationProperties.class) +public class R2dbcObservationAutoConfiguration { + + /** + * {@code @Order} value of the observation customizer. + */ + public static final int R2DBC_PROXY_OBSERVATION_CUSTOMIZER_ORDER = 0; + + @Bean + @Order(R2DBC_PROXY_OBSERVATION_CUSTOMIZER_ORDER) + @ConditionalOnBean(ObservationRegistry.class) + ProxyConnectionFactoryCustomizer observationProxyConnectionFactoryCustomizer(R2dbcObservationProperties properties, + ObservationRegistry observationRegistry, + ObjectProvider queryObservationConvention, + ObjectProvider queryParametersTagProvider) { + return (builder) -> { + ConnectionFactory connectionFactory = builder.getConnectionFactory(); + HostAndPort hostAndPort = extractHostAndPort(connectionFactory); + ObservationProxyExecutionListener listener = new ObservationProxyExecutionListener(observationRegistry, + connectionFactory, hostAndPort.host(), hostAndPort.port()); + listener.setIncludeParameterValues(properties.isIncludeParameterValues()); + queryObservationConvention.ifAvailable(listener::setQueryObservationConvention); + queryParametersTagProvider.ifAvailable(listener::setQueryParametersTagProvider); + builder.listener(listener); + }; + } + + private HostAndPort extractHostAndPort(ConnectionFactory connectionFactory) { + OptionsCapableConnectionFactory optionsCapableConnectionFactory = OptionsCapableConnectionFactory + .unwrapFrom(connectionFactory); + if (optionsCapableConnectionFactory == null) { + return HostAndPort.empty(); + } + ConnectionFactoryOptions options = optionsCapableConnectionFactory.getOptions(); + Object host = options.getValue(ConnectionFactoryOptions.HOST); + Object port = options.getValue(ConnectionFactoryOptions.PORT); + if (!(host instanceof String hostAsString) || !(port instanceof Integer portAsInt)) { + return HostAndPort.empty(); + } + return new HostAndPort(hostAsString, portAsInt); + } + + private record HostAndPort(String host, Integer port) { + static HostAndPort empty() { + return new HostAndPort(null, null); + } + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationProperties.java new file mode 100644 index 000000000000..4eedf3e12282 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationProperties.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.r2dbc; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for R2DBC observability. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +@ConfigurationProperties("management.observations.r2dbc") +public class R2dbcObservationProperties { + + /** + * Whether to tag actual query parameter values. + */ + private boolean includeParameterValues; + + public boolean isIncludeParameterValues() { + return this.includeParameterValues; + } + + public void setIncludeParameterValues(boolean includeParameterValues) { + this.includeParameterValues = includeParameterValues; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/sbom/SbomEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/sbom/SbomEndpointAutoConfiguration.java new file mode 100644 index 000000000000..caafaffb17ff --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/sbom/SbomEndpointAutoConfiguration.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.sbom; + +import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; +import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure; +import org.springframework.boot.actuate.sbom.SbomEndpoint; +import org.springframework.boot.actuate.sbom.SbomEndpointWebExtension; +import org.springframework.boot.actuate.sbom.SbomProperties; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.core.io.ResourceLoader; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for {@link SbomEndpoint}. + * + * @author Moritz Halbritter + * @since 3.3.0 + */ +@AutoConfiguration +@ConditionalOnAvailableEndpoint(endpoint = SbomEndpoint.class) +@EnableConfigurationProperties(SbomProperties.class) +public class SbomEndpointAutoConfiguration { + + private final SbomProperties properties; + + SbomEndpointAutoConfiguration(SbomProperties properties) { + this.properties = properties; + } + + @Bean + @ConditionalOnMissingBean + SbomEndpoint sbomEndpoint(ResourceLoader resourceLoader) { + return new SbomEndpoint(this.properties, resourceLoader); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(SbomEndpoint.class) + @ConditionalOnAvailableEndpoint(exposure = EndpointExposure.WEB) + SbomEndpointWebExtension sbomEndpointWebExtension(SbomEndpoint sbomEndpoint) { + return new SbomEndpointWebExtension(sbomEndpoint, this.properties); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/sbom/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/sbom/package-info.java new file mode 100644 index 000000000000..da6660a3dc08 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/sbom/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for actuator SBOM concerns. + */ +package org.springframework.boot.actuate.autoconfigure.sbom; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/scheduling/ScheduledTasksObservabilityAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/scheduling/ScheduledTasksObservabilityAutoConfiguration.java new file mode 100644 index 000000000000..a4014d2d3eb5 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/scheduling/ScheduledTasksObservabilityAutoConfiguration.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.scheduling; + +import io.micrometer.observation.ObservationRegistry; + +import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; + +/** + * {@link EnableAutoConfiguration Auto-configuration} to enable observability for + * scheduled tasks. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +@AutoConfiguration(after = ObservationAutoConfiguration.class) +@ConditionalOnBean(ObservationRegistry.class) +@ConditionalOnClass(ThreadPoolTaskScheduler.class) +public class ScheduledTasksObservabilityAutoConfiguration { + + @Bean + ObservabilitySchedulingConfigurer observabilitySchedulingConfigurer(ObservationRegistry observationRegistry) { + return new ObservabilitySchedulingConfigurer(observationRegistry); + } + + static final class ObservabilitySchedulingConfigurer implements SchedulingConfigurer { + + private final ObservationRegistry observationRegistry; + + ObservabilitySchedulingConfigurer(ObservationRegistry observationRegistry) { + this.observationRegistry = observationRegistry; + } + + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + taskRegistrar.setObservationRegistry(this.observationRegistry); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequest.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequest.java index 050613f1fc75..bed3b8f5c068 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequest.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -136,9 +136,7 @@ protected boolean ignoreApplicationContext(ApplicationContext applicationContext return true; } String managementContextId = applicationContext.getParent().getId() + ":management"; - if (!managementContextId.equals(applicationContext.getId())) { - return true; - } + return !managementContextId.equals(applicationContext.getId()); } return false; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfiguration.java index 7ceff5128753..e9da837d148e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.boot.actuate.autoconfigure.security.reactive; +import reactor.core.publisher.Mono; + import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration; @@ -28,10 +30,14 @@ import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration; import org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration; import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; +import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; import org.springframework.context.annotation.Bean; +import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.SecurityWebFiltersOrder; import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.userdetails.ReactiveUserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.web.cors.reactive.PreFlightRequestHandler; @@ -50,7 +56,8 @@ @AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class, after = { HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, ReactiveOAuth2ClientAutoConfiguration.class, - ReactiveOAuth2ResourceServerAutoConfiguration.class }) + ReactiveOAuth2ResourceServerAutoConfiguration.class, + ReactiveUserDetailsServiceAutoConfiguration.class }) @ConditionalOnClass({ EnableWebFluxSecurity.class, WebFilterChainProxy.class }) @ConditionalOnMissingBean({ SecurityWebFilterChain.class, WebFilterChainProxy.class }) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) @@ -69,4 +76,10 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, return http.build(); } + @Bean + @ConditionalOnMissingBean({ ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class }) + ReactiveAuthenticationManager denyAllAuthenticationManager() { + return (authentication) -> Mono.error(new UsernameNotFoundException(authentication.getName())); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfiguration.java index 3ec9589a29f5..0e514b6ea409 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,17 +16,25 @@ package org.springframework.boot.actuate.autoconfigure.session; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; +import org.springframework.boot.actuate.session.ReactiveSessionsEndpoint; import org.springframework.boot.actuate.session.SessionsEndpoint; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.session.FindByIndexNameSessionRepository; +import org.springframework.session.ReactiveFindByIndexNameSessionRepository; +import org.springframework.session.ReactiveSessionRepository; import org.springframework.session.Session; +import org.springframework.session.SessionRepository; /** * {@link EnableAutoConfiguration Auto-configuration} for {@link SessionsEndpoint}. @@ -35,15 +43,36 @@ * @since 2.0.0 */ @AutoConfiguration(after = SessionAutoConfiguration.class) -@ConditionalOnClass(FindByIndexNameSessionRepository.class) +@ConditionalOnClass(Session.class) @ConditionalOnAvailableEndpoint(endpoint = SessionsEndpoint.class) public class SessionsEndpointAutoConfiguration { - @Bean - @ConditionalOnBean(FindByIndexNameSessionRepository.class) - @ConditionalOnMissingBean - public SessionsEndpoint sessionEndpoint(FindByIndexNameSessionRepository sessionRepository) { - return new SessionsEndpoint(sessionRepository); + @Configuration(proxyBeanMethods = false) + @ConditionalOnWebApplication(type = Type.SERVLET) + @ConditionalOnBean(SessionRepository.class) + static class ServletSessionEndpointConfiguration { + + @Bean + @ConditionalOnMissingBean + SessionsEndpoint sessionEndpoint(SessionRepository sessionRepository, + ObjectProvider> indexedSessionRepository) { + return new SessionsEndpoint(sessionRepository, indexedSessionRepository.getIfAvailable()); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnWebApplication(type = Type.REACTIVE) + @ConditionalOnBean(ReactiveSessionRepository.class) + static class ReactiveSessionEndpointConfiguration { + + @Bean + @ConditionalOnMissingBean + ReactiveSessionsEndpoint sessionsEndpoint(ReactiveSessionRepository sessionRepository, + ObjectProvider> indexedSessionRepository) { + return new ReactiveSessionsEndpoint(sessionRepository, indexedSessionRepository.getIfAvailable()); + } + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java index 6bb0528d7db8..eaa754c5e1ec 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,22 +24,10 @@ import brave.Tracing; import brave.Tracing.Builder; import brave.TracingCustomizer; -import brave.baggage.BaggageField; -import brave.baggage.BaggagePropagation; -import brave.baggage.BaggagePropagation.FactoryBuilder; -import brave.baggage.BaggagePropagationConfig; -import brave.baggage.BaggagePropagationCustomizer; -import brave.baggage.CorrelationScopeConfig.SingleCorrelationField; -import brave.baggage.CorrelationScopeCustomizer; -import brave.baggage.CorrelationScopeDecorator; -import brave.context.slf4j.MDCScopeDecorator; import brave.handler.SpanHandler; import brave.propagation.CurrentTraceContext; -import brave.propagation.CurrentTraceContext.ScopeDecorator; import brave.propagation.CurrentTraceContextCustomizer; -import brave.propagation.Propagation; import brave.propagation.Propagation.Factory; -import brave.propagation.Propagation.KeyFactory; import brave.propagation.ThreadLocalCurrentTraceContext; import brave.sampler.Sampler; import io.micrometer.tracing.brave.bridge.BraveBaggageManager; @@ -53,17 +41,15 @@ import io.micrometer.tracing.exporter.SpanReporter; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.actuate.autoconfigure.tracing.TracingProperties.Baggage.Correlation; import org.springframework.boot.actuate.autoconfigure.tracing.TracingProperties.Propagation.PropagationType; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.IncompatibleConfigurationException; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.env.Environment; @@ -76,19 +62,25 @@ * @author Jonatan Ivanov * @since 3.0.0 */ -@AutoConfiguration(before = MicrometerTracingAutoConfiguration.class) +@AutoConfiguration(before = { MicrometerTracingAutoConfiguration.class, NoopTracerAutoConfiguration.class }) @ConditionalOnClass({ Tracer.class, BraveTracer.class }) @EnableConfigurationProperties(TracingProperties.class) -@ConditionalOnEnabledTracing +@Import({ BravePropagationConfigurations.PropagationWithoutBaggage.class, + BravePropagationConfigurations.PropagationWithBaggage.class, + BravePropagationConfigurations.NoPropagation.class }) public class BraveAutoConfiguration { - private static final BraveBaggageManager BRAVE_BAGGAGE_MANAGER = new BraveBaggageManager(); - /** * Default value for application name if {@code spring.application.name} is not set. */ private static final String DEFAULT_APPLICATION_NAME = "application"; + private final TracingProperties tracingProperties; + + BraveAutoConfiguration(TracingProperties tracingProperties) { + this.tracingProperties = tracingProperties; + } + @Bean @ConditionalOnMissingBean @Order(Ordered.HIGHEST_PRECEDENCE) @@ -100,22 +92,22 @@ CompositeSpanHandler compositeSpanHandler(ObjectProvider @Bean @ConditionalOnMissingBean - public Tracing braveTracing(Environment environment, TracingProperties properties, List spanHandlers, + Tracing braveTracing(Environment environment, List spanHandlers, List tracingCustomizers, CurrentTraceContext currentTraceContext, Factory propagationFactory, Sampler sampler) { - if (properties.getBrave().isSpanJoiningSupported()) { - if (properties.getPropagation().getType() != null - && properties.getPropagation().getType().contains(PropagationType.W3C)) { + if (this.tracingProperties.getBrave().isSpanJoiningSupported()) { + if (this.tracingProperties.getPropagation().getType() != null + && this.tracingProperties.getPropagation().getType().contains(PropagationType.W3C)) { throw new IncompatibleConfigurationException("management.tracing.propagation.type", "management.tracing.brave.span-joining-supported"); } - if (properties.getPropagation().getType() == null - && properties.getPropagation().getProduce().contains(PropagationType.W3C)) { + if (this.tracingProperties.getPropagation().getType() == null + && this.tracingProperties.getPropagation().getProduce().contains(PropagationType.W3C)) { throw new IncompatibleConfigurationException("management.tracing.propagation.produce", "management.tracing.brave.span-joining-supported"); } - if (properties.getPropagation().getType() == null - && properties.getPropagation().getConsume().contains(PropagationType.W3C)) { + if (this.tracingProperties.getPropagation().getType() == null + && this.tracingProperties.getPropagation().getConsume().contains(PropagationType.W3C)) { throw new IncompatibleConfigurationException("management.tracing.propagation.consume", "management.tracing.brave.span-joining-supported"); } @@ -124,7 +116,7 @@ public Tracing braveTracing(Environment environment, TracingProperties propertie Builder builder = Tracing.newBuilder() .currentTraceContext(currentTraceContext) .traceId128Bit(true) - .supportsJoin(properties.getBrave().isSpanJoiningSupported()) + .supportsJoin(this.tracingProperties.getBrave().isSpanJoiningSupported()) .propagationFactory(propagationFactory) .sampler(sampler) .localServiceName(applicationName); @@ -137,13 +129,13 @@ public Tracing braveTracing(Environment environment, TracingProperties propertie @Bean @ConditionalOnMissingBean - public brave.Tracer braveTracer(Tracing tracing) { + brave.Tracer braveTracer(Tracing tracing) { return tracing.tracer(); } @Bean @ConditionalOnMissingBean - public CurrentTraceContext braveCurrentTraceContext(List scopeDecorators, + CurrentTraceContext braveCurrentTraceContext(List scopeDecorators, List currentTraceContextCustomizers) { ThreadLocalCurrentTraceContext.Builder builder = ThreadLocalCurrentTraceContext.newBuilder(); scopeDecorators.forEach(builder::addScopeDecorator); @@ -155,14 +147,15 @@ public CurrentTraceContext braveCurrentTraceContext(List baggagePropagationCustomizers) { - // There's a chicken-and-egg problem here: to create a builder, we need a - // factory. But the CompositePropagationFactory needs data from the builder. - // We create a throw-away builder with a throw-away factory, and then copy the - // config to the real builder. - FactoryBuilder throwAwayBuilder = BaggagePropagation.newFactoryBuilder(createThrowAwayFactory()); - baggagePropagationCustomizers.orderedStream() - .forEach((customizer) -> customizer.customize(throwAwayBuilder)); - CompositePropagationFactory propagationFactory = CompositePropagationFactory.create( - this.tracingProperties.getPropagation(), BRAVE_BAGGAGE_MANAGER, - LocalBaggageFields.extractFrom(throwAwayBuilder)); - FactoryBuilder builder = BaggagePropagation.newFactoryBuilder(propagationFactory); - throwAwayBuilder.configs().forEach(builder::add); - return builder; - } - - @SuppressWarnings("deprecation") - private Factory createThrowAwayFactory() { - return new Factory() { - - @Override - public Propagation create(KeyFactory keyFactory) { - return null; - } - - }; - } - - @Bean - @Order(0) - BaggagePropagationCustomizer remoteFieldsBaggagePropagationCustomizer() { - return (builder) -> { - List remoteFields = this.tracingProperties.getBaggage().getRemoteFields(); - for (String fieldName : remoteFields) { - builder.add(BaggagePropagationConfig.SingleBaggageField.remote(BaggageField.create(fieldName))); - } - }; - } - - @Bean - @ConditionalOnMissingBean - Factory propagationFactory(BaggagePropagation.FactoryBuilder factoryBuilder) { - return factoryBuilder.build(); - } - - @Bean - @ConditionalOnMissingBean - CorrelationScopeDecorator.Builder mdcCorrelationScopeDecoratorBuilder( - ObjectProvider correlationScopeCustomizers) { - CorrelationScopeDecorator.Builder builder = MDCScopeDecorator.newBuilder(); - correlationScopeCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); - return builder; - } - - @Bean - @Order(0) - @ConditionalOnProperty(prefix = "management.tracing.baggage.correlation", name = "enabled", - matchIfMissing = true) - CorrelationScopeCustomizer correlationFieldsCorrelationScopeCustomizer() { - return (builder) -> { - Correlation correlationProperties = this.tracingProperties.getBaggage().getCorrelation(); - for (String field : correlationProperties.getFields()) { - BaggageField baggageField = BaggageField.create(field); - SingleCorrelationField correlationField = SingleCorrelationField.newBuilder(baggageField) - .flushOnUpdate() - .build(); - builder.add(correlationField); - } - }; - } - - @Bean - @ConditionalOnMissingBean(CorrelationScopeDecorator.class) - ScopeDecorator correlationScopeDecorator(CorrelationScopeDecorator.Builder builder) { - return builder.build(); - } - - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BravePropagationConfigurations.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BravePropagationConfigurations.java new file mode 100644 index 000000000000..974c93a86f37 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BravePropagationConfigurations.java @@ -0,0 +1,181 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import java.util.List; + +import brave.baggage.BaggageField; +import brave.baggage.BaggagePropagation; +import brave.baggage.BaggagePropagation.FactoryBuilder; +import brave.baggage.BaggagePropagationConfig; +import brave.baggage.BaggagePropagationCustomizer; +import brave.baggage.CorrelationScopeConfig.SingleCorrelationField; +import brave.baggage.CorrelationScopeCustomizer; +import brave.baggage.CorrelationScopeDecorator; +import brave.context.slf4j.MDCScopeDecorator; +import brave.propagation.CurrentTraceContext.ScopeDecorator; +import brave.propagation.Propagation; +import brave.propagation.Propagation.Factory; +import io.micrometer.tracing.brave.bridge.BraveBaggageManager; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.autoconfigure.tracing.TracingProperties.Baggage.Correlation; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; + +/** + * Brave propagation configurations. They are imported by {@link BraveAutoConfiguration}. + * + * @author Moritz Halbritter + */ +class BravePropagationConfigurations { + + /** + * Propagates traces but no baggage. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(value = "management.tracing.baggage.enabled", havingValue = "false") + static class PropagationWithoutBaggage { + + @Bean + @ConditionalOnMissingBean(Factory.class) + @ConditionalOnEnabledTracing + CompositePropagationFactory propagationFactory(TracingProperties properties) { + return CompositePropagationFactory.create(properties.getPropagation()); + } + + } + + /** + * Propagates traces and baggage. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(value = "management.tracing.baggage.enabled", matchIfMissing = true) + @EnableConfigurationProperties(TracingProperties.class) + static class PropagationWithBaggage { + + private final TracingProperties tracingProperties; + + PropagationWithBaggage(TracingProperties tracingProperties) { + this.tracingProperties = tracingProperties; + } + + @Bean + @ConditionalOnMissingBean + BaggagePropagation.FactoryBuilder propagationFactoryBuilder( + ObjectProvider baggagePropagationCustomizers) { + // There's a chicken-and-egg problem here: to create a builder, we need a + // factory. But the CompositePropagationFactory needs data from the builder. + // We create a throw-away builder with a throw-away factory, and then copy the + // config to the real builder. + FactoryBuilder throwAwayBuilder = BaggagePropagation.newFactoryBuilder(createThrowAwayFactory()); + baggagePropagationCustomizers.orderedStream() + .forEach((customizer) -> customizer.customize(throwAwayBuilder)); + CompositePropagationFactory propagationFactory = CompositePropagationFactory.create( + this.tracingProperties.getPropagation(), + new BraveBaggageManager(this.tracingProperties.getBaggage().getTagFields()), + LocalBaggageFields.extractFrom(throwAwayBuilder)); + FactoryBuilder builder = BaggagePropagation.newFactoryBuilder(propagationFactory); + throwAwayBuilder.configs().forEach(builder::add); + return builder; + } + + private Factory createThrowAwayFactory() { + return new Factory() { + + @Override + public Propagation get() { + return null; + } + + }; + } + + @Bean + BaggagePropagationCustomizer remoteFieldsBaggagePropagationCustomizer() { + return (builder) -> { + List remoteFields = this.tracingProperties.getBaggage().getRemoteFields(); + for (String fieldName : remoteFields) { + builder.add(BaggagePropagationConfig.SingleBaggageField.remote(BaggageField.create(fieldName))); + } + List localFields = this.tracingProperties.getBaggage().getLocalFields(); + for (String localFieldName : localFields) { + builder.add(BaggagePropagationConfig.SingleBaggageField.local(BaggageField.create(localFieldName))); + } + }; + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledTracing + Factory propagationFactory(BaggagePropagation.FactoryBuilder factoryBuilder) { + return factoryBuilder.build(); + } + + @Bean + @ConditionalOnMissingBean + CorrelationScopeDecorator.Builder mdcCorrelationScopeDecoratorBuilder( + ObjectProvider correlationScopeCustomizers) { + CorrelationScopeDecorator.Builder builder = MDCScopeDecorator.newBuilder(); + correlationScopeCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); + return builder; + } + + @Bean + @Order(0) + @ConditionalOnProperty(prefix = "management.tracing.baggage.correlation", name = "enabled", + matchIfMissing = true) + CorrelationScopeCustomizer correlationFieldsCorrelationScopeCustomizer() { + return (builder) -> { + Correlation correlationProperties = this.tracingProperties.getBaggage().getCorrelation(); + for (String field : correlationProperties.getFields()) { + BaggageField baggageField = BaggageField.create(field); + SingleCorrelationField correlationField = SingleCorrelationField.newBuilder(baggageField) + .flushOnUpdate() + .build(); + builder.add(correlationField); + } + }; + } + + @Bean + @ConditionalOnMissingBean(CorrelationScopeDecorator.class) + ScopeDecorator correlationScopeDecorator(CorrelationScopeDecorator.Builder builder) { + return builder.build(); + } + + } + + /** + * Propagates neither traces nor baggage. + */ + @Configuration(proxyBeanMethods = false) + static class NoPropagation { + + @Bean + @ConditionalOnMissingBean(Factory.class) + CompositePropagationFactory noopPropagationFactory() { + return CompositePropagationFactory.noop(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/CompositePropagationFactory.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/CompositePropagationFactory.java index c4ccc0f301c2..8847a258fa1d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/CompositePropagationFactory.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/CompositePropagationFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,11 +17,11 @@ package org.springframework.boot.actuate.autoconfigure.tracing; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.function.Predicate; import java.util.stream.Stream; -import brave.internal.propagation.StringPropagationAdapter; import brave.propagation.B3Propagation; import brave.propagation.Propagation; import brave.propagation.Propagation.Factory; @@ -70,9 +70,8 @@ public boolean requires128BitTraceId() { } @Override - @SuppressWarnings("deprecation") - public Propagation create(Propagation.KeyFactory keyFactory) { - return StringPropagationAdapter.create(this.propagation, keyFactory); + public Propagation get() { + return this.propagation; } @Override @@ -84,6 +83,14 @@ public TraceContext decorate(TraceContext context) { .orElse(context); } + /** + * Creates a new {@link CompositePropagationFactory} which doesn't do any propagation. + * @return the {@link CompositePropagationFactory} + */ + static CompositePropagationFactory noop() { + return new CompositePropagationFactory(Collections.emptyList(), Collections.emptyList()); + } + /** * Creates a new {@link CompositePropagationFactory}. * @param properties the propagation properties @@ -136,7 +143,7 @@ Propagation.Factory map(PropagationType type) { * @return the B3 propagation factory */ private Propagation.Factory b3Single() { - return B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.SINGLE_NO_PARENT).build(); + return B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.SINGLE).build(); } /** diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/ConditionalOnEnabledTracing.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/ConditionalOnEnabledTracing.java index 311dd867b5cd..c18a7d46f1b6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/ConditionalOnEnabledTracing.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/ConditionalOnEnabledTracing.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,13 +22,15 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Conditional; /** * {@link Conditional @Conditional} that checks whether tracing is enabled. It matches if * the value of the {@code management.tracing.enabled} property is {@code true} or if it - * is not configured. + * is not configured. If the {@link #value() tracing exporter name} is set, the + * {@code management..tracing.export.enabled} property can be used to control the + * behavior for the specific tracing exporter. In that case, the exporter specific + * property takes precedence over the global property. * * @author Moritz Halbritter * @since 3.0.0 @@ -36,7 +38,14 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Documented -@ConditionalOnProperty(prefix = "management.tracing", name = "enabled", matchIfMissing = true) +@Conditional(OnEnabledTracingCondition.class) public @interface ConditionalOnEnabledTracing { + /** + * Name of the tracing exporter. + * @return the name of the tracing exporter + * @since 3.4.0 + */ + String value() default ""; + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessor.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessor.java new file mode 100644 index 000000000000..b0479bd29c3f --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessor.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.boot.logging.LoggingSystem; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertySource; +import org.springframework.util.ClassUtils; + +/** + * {@link EnvironmentPostProcessor} to add a {@link PropertySource} to support log + * correlation IDs when Micrometer Tracing is present. Adds support for the + * {@value LoggingSystem#EXPECT_CORRELATION_ID_PROPERTY} property by delegating to + * {@code management.tracing.enabled}. + * + * @author Jonatan Ivanov + * @author Phillip Webb + */ +class LogCorrelationEnvironmentPostProcessor implements EnvironmentPostProcessor { + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + if (ClassUtils.isPresent("io.micrometer.tracing.Tracer", application.getClassLoader())) { + environment.getPropertySources().addLast(new LogCorrelationPropertySource(this, environment)); + } + } + + /** + * Log correlation {@link PropertySource}. + */ + private static class LogCorrelationPropertySource extends EnumerablePropertySource { + + private static final String NAME = "logCorrelation"; + + private final Environment environment; + + LogCorrelationPropertySource(Object source, Environment environment) { + super(NAME, source); + this.environment = environment; + } + + @Override + public String[] getPropertyNames() { + return new String[] { LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY }; + } + + @Override + public Object getProperty(String name) { + if (name.equals(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY)) { + return this.environment.getProperty("management.tracing.enabled", Boolean.class, Boolean.TRUE); + } + return null; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java index e91e41a5b057..ce31cd4d5b88 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,30 +16,48 @@ package org.springframework.boot.actuate.autoconfigure.tracing; +import io.micrometer.common.annotation.ValueExpressionResolver; import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.annotation.DefaultNewSpanParser; +import io.micrometer.tracing.annotation.ImperativeMethodInvocationProcessor; +import io.micrometer.tracing.annotation.MethodInvocationProcessor; +import io.micrometer.tracing.annotation.NewSpanParser; +import io.micrometer.tracing.annotation.SpanAspect; +import io.micrometer.tracing.annotation.SpanTagAnnotationHandler; import io.micrometer.tracing.handler.DefaultTracingObservationHandler; import io.micrometer.tracing.handler.PropagatingReceiverTracingObservationHandler; import io.micrometer.tracing.handler.PropagatingSenderTracingObservationHandler; import io.micrometer.tracing.propagation.Propagator; +import org.aspectj.weaver.Advice; +import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.SimpleEvaluationContext; /** * {@link EnableAutoConfiguration Auto-configuration} for the Micrometer Tracing API. * * @author Moritz Halbritter + * @author Jonatan Ivanov * @since 3.0.0 */ @AutoConfiguration @ConditionalOnClass(Tracer.class) -@ConditionalOnEnabledTracing +@ConditionalOnBean(Tracer.class) public class MicrometerTracingAutoConfiguration { /** @@ -61,7 +79,6 @@ public class MicrometerTracingAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnBean(Tracer.class) @Order(DEFAULT_TRACING_OBSERVATION_HANDLER_ORDER) public DefaultTracingObservationHandler defaultTracingObservationHandler(Tracer tracer) { return new DefaultTracingObservationHandler(tracer); @@ -69,7 +86,7 @@ public DefaultTracingObservationHandler defaultTracingObservationHandler(Tracer @Bean @ConditionalOnMissingBean - @ConditionalOnBean({ Tracer.class, Propagator.class }) + @ConditionalOnBean(Propagator.class) @Order(SENDER_TRACING_OBSERVATION_HANDLER_ORDER) public PropagatingSenderTracingObservationHandler propagatingSenderTracingObservationHandler(Tracer tracer, Propagator propagator) { @@ -78,11 +95,79 @@ public PropagatingSenderTracingObservationHandler propagatingSenderTracingObs @Bean @ConditionalOnMissingBean - @ConditionalOnBean({ Tracer.class, Propagator.class }) + @ConditionalOnBean(Propagator.class) @Order(RECEIVER_TRACING_OBSERVATION_HANDLER_ORDER) public PropagatingReceiverTracingObservationHandler propagatingReceiverTracingObservationHandler(Tracer tracer, Propagator propagator) { return new PropagatingReceiverTracingObservationHandler<>(tracer, propagator); } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(Advice.class) + @Conditional(ObservationAnnotationsEnabledCondition.class) + static class SpanAspectConfiguration { + + @Bean + @ConditionalOnMissingBean(NewSpanParser.class) + DefaultNewSpanParser newSpanParser() { + return new DefaultNewSpanParser(); + } + + @Bean + @ConditionalOnMissingBean + SpanTagAnnotationHandler spanTagAnnotationHandler(BeanFactory beanFactory) { + ValueExpressionResolver valueExpressionResolver = new SpelTagValueExpressionResolver(); + return new SpanTagAnnotationHandler(beanFactory::getBean, (ignored) -> valueExpressionResolver); + } + + @Bean + @ConditionalOnMissingBean(MethodInvocationProcessor.class) + ImperativeMethodInvocationProcessor imperativeMethodInvocationProcessor(NewSpanParser newSpanParser, + Tracer tracer, SpanTagAnnotationHandler spanTagAnnotationHandler) { + return new ImperativeMethodInvocationProcessor(newSpanParser, tracer, spanTagAnnotationHandler); + } + + @Bean + @ConditionalOnMissingBean + SpanAspect spanAspect(MethodInvocationProcessor methodInvocationProcessor) { + return new SpanAspect(methodInvocationProcessor); + } + + } + + private static final class SpelTagValueExpressionResolver implements ValueExpressionResolver { + + @Override + public String resolve(String expression, Object parameter) { + try { + SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); + ExpressionParser expressionParser = new SpelExpressionParser(); + Expression expressionToEvaluate = expressionParser.parseExpression(expression); + return expressionToEvaluate.getValue(context, parameter, String.class); + } + catch (Exception ex) { + throw new IllegalStateException("Unable to evaluate SpEL expression '%s'".formatted(expression), ex); + } + } + + } + + static final class ObservationAnnotationsEnabledCondition extends AnyNestedCondition { + + ObservationAnnotationsEnabledCondition() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @ConditionalOnProperty(prefix = "micrometer.observations.annotations", name = "enabled", havingValue = "true") + static class MicrometerObservationsEnabledCondition { + + } + + @ConditionalOnProperty(prefix = "management.observations.annotations", name = "enabled", havingValue = "true") + static class ManagementObservationsEnabledCondition { + + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/NoopTracerAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/NoopTracerAutoConfiguration.java new file mode 100644 index 000000000000..ee2f65f8025a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/NoopTracerAutoConfiguration.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import io.micrometer.tracing.Tracer; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for a no-op implementation of + * {@link Tracer}. + * + * @author Moritz Halbritter + * @since 3.2.1 + */ +@AutoConfiguration(before = MicrometerTracingAutoConfiguration.class) +@ConditionalOnClass(Tracer.class) +@ConditionalOnMissingBean(Tracer.class) +public class NoopTracerAutoConfiguration { + + @Bean + Tracer noopTracer() { + return Tracer.NOOP; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OnEnabledTracingCondition.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OnEnabledTracingCondition.java new file mode 100644 index 000000000000..ab29ce57ed5b --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OnEnabledTracingCondition.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import java.util.Map; + +import org.springframework.boot.autoconfigure.condition.ConditionMessage; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.util.StringUtils; + +/** + * {@link SpringBootCondition} to check whether tracing is enabled. + * + * @author Moritz Halbritter + * @see ConditionalOnEnabledTracing + */ +class OnEnabledTracingCondition extends SpringBootCondition { + + private static final String GLOBAL_PROPERTY = "management.tracing.enabled"; + + private static final String EXPORTER_PROPERTY = "management.%s.tracing.export.enabled"; + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + String tracingExporter = getExporterName(metadata); + if (StringUtils.hasLength(tracingExporter)) { + Boolean exporterTracingEnabled = context.getEnvironment() + .getProperty(EXPORTER_PROPERTY.formatted(tracingExporter), Boolean.class); + if (exporterTracingEnabled != null) { + return new ConditionOutcome(exporterTracingEnabled, + ConditionMessage.forCondition(ConditionalOnEnabledTracing.class) + .because(EXPORTER_PROPERTY.formatted(tracingExporter) + " is " + exporterTracingEnabled)); + } + } + Boolean globalTracingEnabled = context.getEnvironment().getProperty(GLOBAL_PROPERTY, Boolean.class); + if (globalTracingEnabled != null) { + return new ConditionOutcome(globalTracingEnabled, + ConditionMessage.forCondition(ConditionalOnEnabledTracing.class) + .because(GLOBAL_PROPERTY + " is " + globalTracingEnabled)); + } + return ConditionOutcome.match(ConditionMessage.forCondition(ConditionalOnEnabledTracing.class) + .because("tracing is enabled by default")); + } + + private static String getExporterName(AnnotatedTypeMetadata metadata) { + Map attributes = metadata.getAnnotationAttributes(ConditionalOnEnabledTracing.class.getName()); + if (attributes == null) { + return null; + } + return (String) attributes.get("value"); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java index 60a15e904f61..7db17f72146c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.springframework.boot.actuate.autoconfigure.tracing; -import java.util.Collections; import java.util.List; import io.micrometer.tracing.SpanCustomizer; @@ -32,24 +31,23 @@ import io.micrometer.tracing.otel.bridge.OtelSpanCustomizer; import io.micrometer.tracing.otel.bridge.OtelTracer; import io.micrometer.tracing.otel.bridge.OtelTracer.EventPublisher; -import io.micrometer.tracing.otel.bridge.Slf4JBaggageEventListener; import io.micrometer.tracing.otel.bridge.Slf4JEventListener; -import io.micrometer.tracing.otel.propagation.BaggageTextMapPropagator; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.ContextStorage; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.context.propagation.TextMapPropagator; -import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; import io.opentelemetry.sdk.trace.SpanProcessor; import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder; import io.opentelemetry.sdk.trace.export.SpanExporter; import io.opentelemetry.sdk.trace.samplers.Sampler; -import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.SpringBootVersion; @@ -57,56 +55,45 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; +import org.springframework.context.annotation.Import; +import org.springframework.util.CollectionUtils; /** - * {@link EnableAutoConfiguration Auto-configuration} for OpenTelemetry. + * {@link EnableAutoConfiguration Auto-configuration} for OpenTelemetry tracing. * * @author Moritz Halbritter * @author Marcin Grzejszczak * @author Yanming Zhou * @since 3.0.0 */ -@AutoConfiguration(before = MicrometerTracingAutoConfiguration.class) -@ConditionalOnEnabledTracing +@AutoConfiguration(value = "openTelemetryTracingAutoConfiguration", + before = { MicrometerTracingAutoConfiguration.class, NoopTracerAutoConfiguration.class }) @ConditionalOnClass({ OtelTracer.class, SdkTracerProvider.class, OpenTelemetry.class }) @EnableConfigurationProperties(TracingProperties.class) +@Import({ OpenTelemetryPropagationConfigurations.PropagationWithoutBaggage.class, + OpenTelemetryPropagationConfigurations.PropagationWithBaggage.class, + OpenTelemetryPropagationConfigurations.NoPropagation.class }) public class OpenTelemetryAutoConfiguration { - /** - * Default value for application name if {@code spring.application.name} is not set. - */ - private static final String DEFAULT_APPLICATION_NAME = "application"; + private static final Log logger = LogFactory.getLog(OpenTelemetryAutoConfiguration.class); private final TracingProperties tracingProperties; OpenTelemetryAutoConfiguration(TracingProperties tracingProperties) { this.tracingProperties = tracingProperties; + if (!CollectionUtils.isEmpty(this.tracingProperties.getBaggage().getLocalFields())) { + logger.warn("Local fields are not supported when using OpenTelemetry!"); + } } @Bean @ConditionalOnMissingBean - OpenTelemetry openTelemetry(SdkTracerProvider sdkTracerProvider, ContextPropagators contextPropagators) { - return OpenTelemetrySdk.builder() - .setTracerProvider(sdkTracerProvider) - .setPropagators(contextPropagators) - .build(); - } - - @Bean - @ConditionalOnMissingBean - SdkTracerProvider otelSdkTracerProvider(Environment environment, ObjectProvider spanProcessors, - Sampler sampler, ObjectProvider customizers) { - String applicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME); - Resource springResource = Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName)); - SdkTracerProviderBuilder builder = SdkTracerProvider.builder() - .setSampler(sampler) - .setResource(Resource.getDefault().merge(springResource)); - spanProcessors.orderedStream().forEach(builder::addSpanProcessor); + SdkTracerProvider otelSdkTracerProvider(Resource resource, SpanProcessors spanProcessors, Sampler sampler, + ObjectProvider customizers) { + SdkTracerProviderBuilder builder = SdkTracerProvider.builder().setSampler(sampler).setResource(resource); + spanProcessors.forEach(builder::addSpanProcessor); customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); return builder.build(); } @@ -125,14 +112,26 @@ Sampler otelSampler() { } @Bean - SpanProcessor otelSpanProcessor(ObjectProvider spanExporters, + @ConditionalOnMissingBean + SpanProcessors spanProcessors(ObjectProvider spanProcessors) { + return SpanProcessors.of(spanProcessors.orderedStream().toList()); + } + + @Bean + BatchSpanProcessor otelSpanProcessor(SpanExporters spanExporters, ObjectProvider spanExportingPredicates, ObjectProvider spanReporters, - ObjectProvider spanFilters) { - return BatchSpanProcessor - .builder(new CompositeSpanExporter(spanExporters.orderedStream().toList(), - spanExportingPredicates.orderedStream().toList(), spanReporters.orderedStream().toList(), - spanFilters.orderedStream().toList())) - .build(); + ObjectProvider spanFilters, ObjectProvider meterProvider) { + BatchSpanProcessorBuilder builder = BatchSpanProcessor + .builder(new CompositeSpanExporter(spanExporters.list(), spanExportingPredicates.orderedStream().toList(), + spanReporters.orderedStream().toList(), spanFilters.orderedStream().toList())); + meterProvider.ifAvailable(builder::setMeterProvider); + return builder.build(); + } + + @Bean + @ConditionalOnMissingBean + SpanExporters spanExporters(ObjectProvider spanExporters) { + return SpanExporters.of(spanExporters.orderedStream().toList()); } @Bean @@ -145,9 +144,10 @@ Tracer otelTracer(OpenTelemetry openTelemetry) { @ConditionalOnMissingBean(io.micrometer.tracing.Tracer.class) OtelTracer micrometerOtelTracer(Tracer tracer, EventPublisher eventPublisher, OtelCurrentTraceContext otelCurrentTraceContext) { + List remoteFields = this.tracingProperties.getBaggage().getRemoteFields(); + List tagFields = this.tracingProperties.getBaggage().getTagFields(); return new OtelTracer(tracer, otelCurrentTraceContext, eventPublisher, - new OtelBaggageManager(otelCurrentTraceContext, this.tracingProperties.getBaggage().getRemoteFields(), - Collections.emptyList())); + new OtelBaggageManager(otelCurrentTraceContext, remoteFields, tagFields)); } @Bean @@ -181,45 +181,6 @@ OtelSpanCustomizer otelSpanCustomizer() { return new OtelSpanCustomizer(); } - @Configuration(proxyBeanMethods = false) - @ConditionalOnProperty(prefix = "management.tracing.baggage", name = "enabled", matchIfMissing = true) - static class BaggageConfiguration { - - private final TracingProperties tracingProperties; - - BaggageConfiguration(TracingProperties tracingProperties) { - this.tracingProperties = tracingProperties; - } - - @Bean - TextMapPropagator textMapPropagatorWithBaggage(OtelCurrentTraceContext otelCurrentTraceContext) { - List remoteFields = this.tracingProperties.getBaggage().getRemoteFields(); - BaggageTextMapPropagator baggagePropagator = new BaggageTextMapPropagator(remoteFields, - new OtelBaggageManager(otelCurrentTraceContext, remoteFields, Collections.emptyList())); - return CompositeTextMapPropagator.create(this.tracingProperties.getPropagation(), baggagePropagator); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = "management.tracing.baggage.correlation", name = "enabled", - matchIfMissing = true) - Slf4JBaggageEventListener otelSlf4JBaggageEventListener() { - return new Slf4JBaggageEventListener(this.tracingProperties.getBaggage().getCorrelation().getFields()); - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnProperty(prefix = "management.tracing.baggage", name = "enabled", havingValue = "false") - static class NoBaggageConfiguration { - - @Bean - TextMapPropagator textMapPropagator(TracingProperties properties) { - return CompositeTextMapPropagator.create(properties.getPropagation(), null); - } - - } - static class OTelEventPublisher implements EventPublisher { private final List listeners; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryPropagationConfigurations.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryPropagationConfigurations.java new file mode 100644 index 000000000000..e35e132a5592 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryPropagationConfigurations.java @@ -0,0 +1,105 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import java.util.List; + +import io.micrometer.tracing.otel.bridge.OtelBaggageManager; +import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; +import io.micrometer.tracing.otel.bridge.Slf4JBaggageEventListener; +import io.micrometer.tracing.otel.propagation.BaggageTextMapPropagator; +import io.opentelemetry.context.propagation.TextMapPropagator; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * OpenTelemetry propagation configurations. They are imported by + * {@link OpenTelemetryAutoConfiguration}. + * + * @author Moritz Halbritter + */ +class OpenTelemetryPropagationConfigurations { + + /** + * Propagates traces but no baggage. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(prefix = "management.tracing.baggage", name = "enabled", havingValue = "false") + @EnableConfigurationProperties(TracingProperties.class) + static class PropagationWithoutBaggage { + + @Bean + @ConditionalOnEnabledTracing + TextMapPropagator textMapPropagator(TracingProperties properties) { + return CompositeTextMapPropagator.create(properties.getPropagation(), null); + } + + } + + /** + * Propagates traces and baggage. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(prefix = "management.tracing.baggage", name = "enabled", matchIfMissing = true) + @EnableConfigurationProperties(TracingProperties.class) + static class PropagationWithBaggage { + + private final TracingProperties tracingProperties; + + PropagationWithBaggage(TracingProperties tracingProperties) { + this.tracingProperties = tracingProperties; + } + + @Bean + @ConditionalOnEnabledTracing + TextMapPropagator textMapPropagatorWithBaggage(OtelCurrentTraceContext otelCurrentTraceContext) { + List remoteFields = this.tracingProperties.getBaggage().getRemoteFields(); + List tagFields = this.tracingProperties.getBaggage().getTagFields(); + BaggageTextMapPropagator baggagePropagator = new BaggageTextMapPropagator(remoteFields, + new OtelBaggageManager(otelCurrentTraceContext, remoteFields, tagFields)); + return CompositeTextMapPropagator.create(this.tracingProperties.getPropagation(), baggagePropagator); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(prefix = "management.tracing.baggage.correlation", name = "enabled", + matchIfMissing = true) + Slf4JBaggageEventListener otelSlf4JBaggageEventListener() { + return new Slf4JBaggageEventListener(this.tracingProperties.getBaggage().getCorrelation().getFields()); + } + + } + + /** + * Propagates neither traces nor baggage. + */ + @Configuration(proxyBeanMethods = false) + static class NoPropagation { + + @Bean + @ConditionalOnMissingBean + TextMapPropagator noopTextMapPropagator() { + return TextMapPropagator.noop(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExporters.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExporters.java new file mode 100644 index 000000000000..a44f8ce0e035 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExporters.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Spliterator; + +import io.opentelemetry.sdk.trace.export.SpanExporter; + +import org.springframework.util.Assert; + +/** + * A collection of {@link SpanExporter span exporters}. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +@FunctionalInterface +public interface SpanExporters extends Iterable { + + /** + * Returns the list of {@link SpanExporter span exporters}. + * @return the list of span exporters + */ + List list(); + + @Override + default Iterator iterator() { + return list().iterator(); + } + + @Override + default Spliterator spliterator() { + return list().spliterator(); + } + + /** + * Constructs a {@link SpanExporters} instance with the given {@link SpanExporter span + * exporters}. + * @param spanExporters the span exporters + * @return the constructed {@link SpanExporters} instance + */ + static SpanExporters of(SpanExporter... spanExporters) { + return of(Arrays.asList(spanExporters)); + } + + /** + * Constructs a {@link SpanExporters} instance with the given list of + * {@link SpanExporter span exporters}. + * @param spanExporters the list of span exporters + * @return the constructed {@link SpanExporters} instance + */ + static SpanExporters of(Collection spanExporters) { + Assert.notNull(spanExporters, "SpanExporters must not be null"); + List copy = List.copyOf(spanExporters); + return () -> copy; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessors.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessors.java new file mode 100644 index 000000000000..ca8c55498d07 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessors.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Spliterator; + +import io.opentelemetry.sdk.trace.SpanProcessor; + +import org.springframework.util.Assert; + +/** + * A collection of {@link SpanProcessor span processors}. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +@FunctionalInterface +public interface SpanProcessors extends Iterable { + + /** + * Returns the list of {@link SpanProcessor span processors}. + * @return the list of span processors + */ + List list(); + + @Override + default Iterator iterator() { + return list().iterator(); + } + + @Override + default Spliterator spliterator() { + return list().spliterator(); + } + + /** + * Constructs a {@link SpanProcessors} instance with the given {@link SpanProcessor + * span processors}. + * @param spanProcessors the span processors + * @return the constructed {@link SpanProcessors} instance + */ + static SpanProcessors of(SpanProcessor... spanProcessors) { + return of(Arrays.asList(spanProcessors)); + } + + /** + * Constructs a {@link SpanProcessors} instance with the given list of + * {@link SpanProcessor span processors}. + * @param spanProcessors the list of span processors + * @return the constructed {@link SpanProcessors} instance + */ + static SpanProcessors of(Collection spanProcessors) { + Assert.notNull(spanProcessors, "SpanProcessors must not be null"); + List copy = List.copyOf(spanProcessors); + return () -> copy; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java index c38ef94d1253..175ff03a8175 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java @@ -103,6 +103,17 @@ public static class Baggage { */ private List remoteFields = new ArrayList<>(); + /** + * List of fields that should be accessible within the JVM process but not + * propagated over the wire. Local fields are not supported with OpenTelemetry. + */ + private List localFields = new ArrayList<>(); + + /** + * List of fields that should automatically become tags. + */ + private List tagFields = new ArrayList<>(); + public boolean isEnabled() { return this.enabled; } @@ -123,10 +134,26 @@ public List getRemoteFields() { return this.remoteFields; } + public List getLocalFields() { + return this.localFields; + } + + public List getTagFields() { + return this.tagFields; + } + public void setRemoteFields(List remoteFields) { this.remoteFields = remoteFields; } + public void setLocalFields(List localFields) { + this.localFields = localFields; + } + + public void setTagFields(List tagFields) { + this.tagFields = tagFields; + } + public static class Correlation { /** @@ -241,7 +268,7 @@ public enum PropagationType { * B3 * multiple headers propagation. */ - B3_MULTI; + B3_MULTI } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java index c9493f0d0d99..abb3253f2f99 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java @@ -16,22 +16,17 @@ package org.springframework.boot.actuate.autoconfigure.tracing.otlp; -import java.util.Map.Entry; - import io.micrometer.tracing.otel.bridge.OtelTracer; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; -import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.sdk.trace.SdkTracerProvider; -import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; /** * {@link EnableAutoConfiguration Auto-configuration} for OTLP. Brave does not support @@ -45,26 +40,14 @@ * define an {@link OtlpGrpcSpanExporter} and this auto-configuration will back off. * * @author Jonatan Ivanov + * @author Moritz Halbritter + * @author Eddú Meléndez * @since 3.1.0 */ @AutoConfiguration -@ConditionalOnEnabledTracing @ConditionalOnClass({ OtelTracer.class, SdkTracerProvider.class, OpenTelemetry.class, OtlpHttpSpanExporter.class }) @EnableConfigurationProperties(OtlpProperties.class) +@Import({ OtlpTracingConfigurations.ConnectionDetails.class, OtlpTracingConfigurations.Exporters.class }) public class OtlpAutoConfiguration { - @Bean - @ConditionalOnMissingBean(value = OtlpHttpSpanExporter.class, - type = "io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter") - OtlpHttpSpanExporter otlpHttpSpanExporter(OtlpProperties properties) { - OtlpHttpSpanExporterBuilder builder = OtlpHttpSpanExporter.builder() - .setEndpoint(properties.getEndpoint()) - .setTimeout(properties.getTimeout()) - .setCompression(properties.getCompression().name().toLowerCase()); - for (Entry header : properties.getHeaders().entrySet()) { - builder.addHeader(header.getKey(), header.getValue()); - } - return builder.build(); - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java index efb1a32f7553..371de8491146 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java @@ -34,7 +34,7 @@ public class OtlpProperties { /** * URL to the OTel collector's HTTP API. */ - private String endpoint = "http://localhost:4318/v1/traces"; + private String endpoint; /** * Call timeout for the OTel Collector to process an exported batch of data. This diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingConfigurations.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingConfigurations.java new file mode 100644 index 000000000000..142696717172 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingConfigurations.java @@ -0,0 +1,90 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.otlp; + +import java.util.Map.Entry; + +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder; + +import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configurations imported by {@link OtlpAutoConfiguration}. + * + * @author Moritz Halbritter + */ +class OtlpTracingConfigurations { + + @Configuration(proxyBeanMethods = false) + static class ConnectionDetails { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(prefix = "management.otlp.tracing", name = "endpoint") + OtlpTracingConnectionDetails otlpTracingConnectionDetails(OtlpProperties properties) { + return new PropertiesOtlpTracingConnectionDetails(properties); + } + + /** + * Adapts {@link OtlpProperties} to {@link OtlpTracingConnectionDetails}. + */ + static class PropertiesOtlpTracingConnectionDetails implements OtlpTracingConnectionDetails { + + private final OtlpProperties properties; + + PropertiesOtlpTracingConnectionDetails(OtlpProperties properties) { + this.properties = properties; + } + + @Override + public String getUrl() { + return this.properties.getEndpoint(); + } + + } + + } + + @Configuration(proxyBeanMethods = false) + static class Exporters { + + @Bean + @ConditionalOnMissingBean(value = OtlpHttpSpanExporter.class, + type = "io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter") + @ConditionalOnBean(OtlpTracingConnectionDetails.class) + @ConditionalOnEnabledTracing("otlp") + OtlpHttpSpanExporter otlpHttpSpanExporter(OtlpProperties properties, + OtlpTracingConnectionDetails connectionDetails) { + OtlpHttpSpanExporterBuilder builder = OtlpHttpSpanExporter.builder() + .setEndpoint(connectionDetails.getUrl()) + .setTimeout(properties.getTimeout()) + .setCompression(properties.getCompression().name().toLowerCase()); + for (Entry header : properties.getHeaders().entrySet()) { + builder.addHeader(header.getKey(), header.getValue()); + } + return builder.build(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingConnectionDetails.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingConnectionDetails.java new file mode 100644 index 000000000000..a84b11d64da3 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingConnectionDetails.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.otlp; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +/** + * Details required to establish a connection to an OpenTelemetry service. + * + * @author Eddú Meléndez + * @since 3.2.0 + */ +public interface OtlpTracingConnectionDetails extends ConnectionDetails { + + /** + * Address to where tracing will be published. + * @return the address to where tracing will be published + */ + String getUrl(); + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfiguration.java index 9032a0712ad5..b36f8f014e79 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,11 +18,10 @@ import io.micrometer.tracing.Span; import io.micrometer.tracing.Tracer; -import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier; +import io.prometheus.metrics.tracer.common.SpanContext; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusMetricsExportAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing; import org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -42,43 +41,42 @@ @AutoConfiguration(before = PrometheusMetricsExportAutoConfiguration.class, after = MicrometerTracingAutoConfiguration.class) @ConditionalOnBean(Tracer.class) -@ConditionalOnClass({ Tracer.class, SpanContextSupplier.class }) -@ConditionalOnEnabledTracing +@ConditionalOnClass({ Tracer.class, SpanContext.class }) public class PrometheusExemplarsAutoConfiguration { @Bean @ConditionalOnMissingBean - SpanContextSupplier spanContextSupplier(ObjectProvider tracerProvider) { - return new LazyTracingSpanContextSupplier(tracerProvider); + SpanContext spanContext(ObjectProvider tracerProvider) { + return new LazyTracingSpanContext(tracerProvider); } /** * Since the MeterRegistry can depend on the {@link Tracer} (Exemplars) and the * {@link Tracer} can depend on the MeterRegistry (recording metrics), this - * {@link SpanContextSupplier} breaks the cycle by lazily loading the {@link Tracer}. + * {@link SpanContext} breaks the cycle by lazily loading the {@link Tracer}. */ - static class LazyTracingSpanContextSupplier implements SpanContextSupplier { + static class LazyTracingSpanContext implements SpanContext { private final SingletonSupplier tracer; - LazyTracingSpanContextSupplier(ObjectProvider tracerProvider) { + LazyTracingSpanContext(ObjectProvider tracerProvider) { this.tracer = SingletonSupplier.of(tracerProvider::getObject); } @Override - public String getTraceId() { + public String getCurrentTraceId() { Span currentSpan = currentSpan(); return (currentSpan != null) ? currentSpan.context().traceId() : null; } @Override - public String getSpanId() { + public String getCurrentSpanId() { Span currentSpan = currentSpan(); return (currentSpan != null) ? currentSpan.context().spanId() : null; } @Override - public boolean isSampled() { + public boolean isCurrentSpanSampled() { Span currentSpan = currentSpan(); if (currentSpan == null) { return false; @@ -87,6 +85,10 @@ public boolean isSampled() { return sampled != null && sampled; } + @Override + public void markCurrentSpanAsExemplar() { + } + private Span currentSpan() { return this.tracer.obtain().currentSpan(); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusSimpleclientExemplarsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusSimpleclientExemplarsAutoConfiguration.java new file mode 100644 index 000000000000..9dcc65aa831e --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusSimpleclientExemplarsAutoConfiguration.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.prometheus; + +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; +import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusSimpleclientMetricsExportAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.util.function.SingletonSupplier; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Prometheus Exemplars with + * Micrometer Tracing. + * + * @author Jonatan Ivanov + * @since 3.0.0 + * @deprecated since 3.3.0 for removal in 3.5.0 + */ +@SuppressWarnings("removal") +@Deprecated(forRemoval = true, since = "3.3.0") +@AutoConfiguration(before = PrometheusSimpleclientMetricsExportAutoConfiguration.class, + after = MicrometerTracingAutoConfiguration.class) +@ConditionalOnBean(Tracer.class) +@ConditionalOnClass({ Tracer.class, SpanContextSupplier.class }) +public class PrometheusSimpleclientExemplarsAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + SpanContextSupplier spanContextSupplier(ObjectProvider tracerProvider) { + return new LazyTracingSpanContextSupplier(tracerProvider); + } + + /** + * Since the MeterRegistry can depend on the {@link Tracer} (Exemplars) and the + * {@link Tracer} can depend on the MeterRegistry (recording metrics), this + * {@link SpanContextSupplier} breaks the cycle by lazily loading the {@link Tracer}. + */ + static class LazyTracingSpanContextSupplier implements SpanContextSupplier { + + private final SingletonSupplier tracer; + + LazyTracingSpanContextSupplier(ObjectProvider tracerProvider) { + this.tracer = SingletonSupplier.of(tracerProvider::getObject); + } + + @Override + public String getTraceId() { + Span currentSpan = currentSpan(); + return (currentSpan != null) ? currentSpan.context().traceId() : null; + } + + @Override + public String getSpanId() { + Span currentSpan = currentSpan(); + return (currentSpan != null) ? currentSpan.context().spanId() : null; + } + + @Override + public boolean isSampled() { + Span currentSpan = currentSpan(); + if (currentSpan == null) { + return false; + } + Boolean sampled = currentSpan.context().sampled(); + return sampled != null && sampled; + } + + private Span currentSpan() { + return this.tracer.obtain().currentSpan(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfiguration.java index 81a0dd0863c3..2ba31d288ca1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,7 +52,6 @@ @AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class, WavefrontAutoConfiguration.class }) @ConditionalOnClass({ WavefrontSender.class, WavefrontSpanHandler.class }) -@ConditionalOnEnabledTracing @EnableConfigurationProperties(WavefrontProperties.class) @Import(WavefrontSenderConfiguration.class) public class WavefrontTracingAutoConfiguration { @@ -60,6 +59,7 @@ public class WavefrontTracingAutoConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnBean(WavefrontSender.class) + @ConditionalOnEnabledTracing("wavefront") WavefrontSpanHandler wavefrontSpanHandler(WavefrontProperties properties, WavefrontSender wavefrontSender, SpanMetrics spanMetrics, ApplicationTags applicationTags) { return new WavefrontSpanHandler(properties.getSender().getMaxQueueSize(), wavefrontSender, spanMetrics, @@ -96,6 +96,7 @@ static class WavefrontBrave { @Bean @ConditionalOnMissingBean + @ConditionalOnEnabledTracing("wavefront") WavefrontBraveSpanHandler wavefrontBraveSpanHandler(WavefrontSpanHandler wavefrontSpanHandler) { return new WavefrontBraveSpanHandler(wavefrontSpanHandler); } @@ -108,6 +109,7 @@ static class WavefrontOpenTelemetry { @Bean @ConditionalOnMissingBean + @ConditionalOnEnabledTracing("wavefront") WavefrontOtelSpanExporter wavefrontOtelSpanExporter(WavefrontSpanHandler wavefrontSpanHandler) { return new WavefrontOtelSpanExporter(wavefrontSpanHandler); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/HttpSender.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/HttpSender.java index ac7ba066671e..91ddc66b2774 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/HttpSender.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/HttpSender.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,135 +18,81 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.UncheckedIOException; -import java.util.Collections; +import java.net.URI; import java.util.List; import java.util.zip.GZIPOutputStream; -import zipkin2.Call; -import zipkin2.CheckResult; -import zipkin2.codec.Encoding; -import zipkin2.reporter.BytesMessageEncoder; -import zipkin2.reporter.ClosedSenderException; -import zipkin2.reporter.Sender; +import zipkin2.reporter.BaseHttpSender; +import zipkin2.reporter.BytesMessageSender; +import zipkin2.reporter.Encoding; +import zipkin2.reporter.HttpEndpointSupplier.Factory; import org.springframework.http.HttpHeaders; import org.springframework.util.unit.DataSize; /** - * A Zipkin {@link Sender} that uses an HTTP client to send JSON spans. Supports automatic - * compression with gzip. + * A Zipkin {@link BytesMessageSender} that uses an HTTP client to send JSON spans. + * Supports automatic compression with gzip. * * @author Moritz Halbritter * @author Stefan Bratanov */ -abstract class HttpSender extends Sender { +abstract class HttpSender extends BaseHttpSender { - private static final DataSize MESSAGE_MAX_SIZE = DataSize.ofKilobytes(512); - - private volatile boolean closed; - - @Override - public Encoding encoding() { - return Encoding.JSON; - } + /** + * Only use gzip compression on data which is bigger than this in bytes. + */ + private static final DataSize COMPRESSION_THRESHOLD = DataSize.ofKilobytes(1); - @Override - public int messageMaxBytes() { - return (int) MESSAGE_MAX_SIZE.toBytes(); + HttpSender(Encoding encoding, Factory endpointSupplierFactory, String endpoint) { + super(encoding, endpointSupplierFactory, endpoint); } @Override - public int messageSizeInBytes(List encodedSpans) { - return encoding().listSizeInBytes(encodedSpans); + protected URI newEndpoint(String endpoint) { + return URI.create(endpoint); } @Override - public int messageSizeInBytes(int encodedSizeInBytes) { - return encoding().listSizeInBytes(encodedSizeInBytes); + protected byte[] newBody(List list) { + return this.encoding.encode(list); } @Override - public CheckResult check() { - try { - sendSpans(Collections.emptyList()).execute(); - return CheckResult.OK; + protected void postSpans(URI endpoint, byte[] body) throws IOException { + HttpHeaders headers = getDefaultHeaders(); + if (needsCompression(body)) { + body = compress(body); + headers.set("Content-Encoding", "gzip"); } - catch (IOException | RuntimeException ex) { - return CheckResult.failed(ex); - } - } - - @Override - public void close() throws IOException { - this.closed = true; + postSpans(endpoint, headers, body); } /** - * The returned {@link HttpPostCall} will send span(s) as a POST to a zipkin endpoint - * when executed. - * @param batchedEncodedSpans list of encoded spans as a byte array - * @return an instance of a Zipkin {@link Call} which can be executed + * This will send span(s) as a POST to a zipkin endpoint. + * @param endpoint the POST endpoint. For example, http://localhost:9411/api/v2/spans. + * @param headers headers for the POST request + * @param body list of possibly gzipped, encoded spans. */ - protected abstract HttpPostCall sendSpans(byte[] batchedEncodedSpans); + abstract void postSpans(URI endpoint, HttpHeaders headers, byte[] body) throws IOException; - @Override - public Call sendSpans(List encodedSpans) { - if (this.closed) { - throw new ClosedSenderException(); - } - return sendSpans(BytesMessageEncoder.JSON.encode(encodedSpans)); + HttpHeaders getDefaultHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.set("b3", "0"); + headers.set("Content-Type", this.encoding.mediaType()); + return headers; } - abstract static class HttpPostCall extends Call.Base { - - /** - * Only use gzip compression on data which is bigger than this in bytes. - */ - private static final DataSize COMPRESSION_THRESHOLD = DataSize.ofKilobytes(1); - - private final byte[] body; - - HttpPostCall(byte[] body) { - this.body = body; - } - - protected byte[] getBody() { - if (needsCompression()) { - return compress(this.body); - } - return this.body; - } - - protected byte[] getUncompressedBody() { - return this.body; - } - - protected HttpHeaders getDefaultHeaders() { - HttpHeaders headers = new HttpHeaders(); - headers.set("b3", "0"); - headers.set("Content-Type", "application/json"); - if (needsCompression()) { - headers.set("Content-Encoding", "gzip"); - } - return headers; - } - - private boolean needsCompression() { - return this.body.length > COMPRESSION_THRESHOLD.toBytes(); - } + private boolean needsCompression(byte[] body) { + return body.length > COMPRESSION_THRESHOLD.toBytes(); + } - private byte[] compress(byte[] input) { - ByteArrayOutputStream result = new ByteArrayOutputStream(); - try (GZIPOutputStream gzip = new GZIPOutputStream(result)) { - gzip.write(input); - } - catch (IOException ex) { - throw new UncheckedIOException(ex); - } - return result.toByteArray(); + private byte[] compress(byte[] input) throws IOException { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + try (GZIPOutputStream gzip = new GZIPOutputStream(result)) { + gzip.write(input); } - + return result.toByteArray(); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfiguration.java index 971b9d514ec1..dca170f32f28 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,10 @@ package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; -import zipkin2.Span; -import zipkin2.codec.BytesEncoder; -import zipkin2.codec.SpanBytesEncoder; -import zipkin2.reporter.Sender; +import zipkin2.reporter.Encoding; -import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing; import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.BraveConfiguration; import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.OpenTelemetryConfiguration; -import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.ReporterConfiguration; import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.SenderConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -45,10 +40,8 @@ * @since 3.0.0 */ @AutoConfiguration(after = RestTemplateAutoConfiguration.class) -@ConditionalOnClass(Sender.class) -@Import({ SenderConfiguration.class, ReporterConfiguration.class, BraveConfiguration.class, - OpenTelemetryConfiguration.class }) -@ConditionalOnEnabledTracing +@ConditionalOnClass(Encoding.class) +@Import({ SenderConfiguration.class, BraveConfiguration.class, OpenTelemetryConfiguration.class }) @EnableConfigurationProperties(ZipkinProperties.class) public class ZipkinAutoConfiguration { @@ -60,8 +53,11 @@ PropertiesZipkinConnectionDetails zipkinConnectionDetails(ZipkinProperties prope @Bean @ConditionalOnMissingBean - public BytesEncoder spanBytesEncoder() { - return SpanBytesEncoder.JSON_V2; + Encoding encoding(ZipkinProperties properties) { + return switch (properties.getEncoding()) { + case JSON -> Encoding.JSON; + case PROTO3 -> Encoding.PROTO3; + }; } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurations.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurations.java index 722b502befa5..38aaf0d0f6e2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurations.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurations.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,26 @@ package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; +import java.net.http.HttpClient; +import java.net.http.HttpClient.Builder; + +import brave.Tag; +import brave.Tags; +import brave.handler.MutableSpan; import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter; import zipkin2.Span; -import zipkin2.codec.BytesEncoder; -import zipkin2.reporter.AsyncReporter; -import zipkin2.reporter.Reporter; -import zipkin2.reporter.Sender; -import zipkin2.reporter.brave.ZipkinSpanHandler; +import zipkin2.reporter.BytesEncoder; +import zipkin2.reporter.BytesMessageSender; +import zipkin2.reporter.Encoding; +import zipkin2.reporter.HttpEndpointSupplier; +import zipkin2.reporter.HttpEndpointSuppliers; +import zipkin2.reporter.SpanBytesEncoder; +import zipkin2.reporter.brave.AsyncZipkinSpanHandler; +import zipkin2.reporter.brave.MutableSpanBytesEncoder; import zipkin2.reporter.urlconnection.URLConnectionSender; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -47,7 +57,7 @@ class ZipkinConfigurations { @Configuration(proxyBeanMethods = false) @Import({ UrlConnectionSenderConfiguration.class, WebClientSenderConfiguration.class, - RestTemplateSenderConfiguration.class }) + RestTemplateSenderConfiguration.class, HttpClientSenderConfiguration.class }) static class SenderConfiguration { } @@ -58,15 +68,20 @@ static class SenderConfiguration { static class UrlConnectionSenderConfiguration { @Bean - @ConditionalOnMissingBean(Sender.class) - URLConnectionSender urlConnectionSender(ZipkinProperties properties, - ObjectProvider connectionDetailsProvider) { + @ConditionalOnMissingBean(BytesMessageSender.class) + URLConnectionSender urlConnectionSender(ZipkinProperties properties, Encoding encoding, + ObjectProvider connectionDetailsProvider, + ObjectProvider endpointSupplierFactoryProvider) { ZipkinConnectionDetails connectionDetails = connectionDetailsProvider .getIfAvailable(() -> new PropertiesZipkinConnectionDetails(properties)); + HttpEndpointSupplier.Factory endpointSupplierFactory = endpointSupplierFactoryProvider + .getIfAvailable(HttpEndpointSuppliers::constantFactory); URLConnectionSender.Builder builder = URLConnectionSender.newBuilder(); builder.connectTimeout((int) properties.getConnectTimeout().toMillis()); builder.readTimeout((int) properties.getReadTimeout().toMillis()); + builder.endpointSupplierFactory(endpointSupplierFactory); builder.endpoint(connectionDetails.getSpanEndpoint()); + builder.encoding(encoding); return builder.build(); } @@ -78,19 +93,25 @@ URLConnectionSender urlConnectionSender(ZipkinProperties properties, static class RestTemplateSenderConfiguration { @Bean - @ConditionalOnMissingBean(Sender.class) - ZipkinRestTemplateSender restTemplateSender(ZipkinProperties properties, + @ConditionalOnMissingBean(BytesMessageSender.class) + @SuppressWarnings("removal") + ZipkinRestTemplateSender restTemplateSender(ZipkinProperties properties, Encoding encoding, ObjectProvider customizers, - ObjectProvider connectionDetailsProvider) { + ObjectProvider connectionDetailsProvider, + ObjectProvider endpointSupplierFactoryProvider) { ZipkinConnectionDetails connectionDetails = connectionDetailsProvider .getIfAvailable(() -> new PropertiesZipkinConnectionDetails(properties)); + HttpEndpointSupplier.Factory endpointSupplierFactory = endpointSupplierFactoryProvider + .getIfAvailable(HttpEndpointSuppliers::constantFactory); RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder() .setConnectTimeout(properties.getConnectTimeout()) .setReadTimeout(properties.getReadTimeout()); restTemplateBuilder = applyCustomizers(restTemplateBuilder, customizers); - return new ZipkinRestTemplateSender(connectionDetails.getSpanEndpoint(), restTemplateBuilder.build()); + return new ZipkinRestTemplateSender(encoding, endpointSupplierFactory, connectionDetails.getSpanEndpoint(), + restTemplateBuilder.build()); } + @SuppressWarnings("removal") private RestTemplateBuilder applyCustomizers(RestTemplateBuilder restTemplateBuilder, ObjectProvider customizers) { Iterable orderedCustomizers = () -> customizers.orderedStream() @@ -110,53 +131,86 @@ private RestTemplateBuilder applyCustomizers(RestTemplateBuilder restTemplateBui static class WebClientSenderConfiguration { @Bean - @ConditionalOnMissingBean(Sender.class) - ZipkinWebClientSender webClientSender(ZipkinProperties properties, + @ConditionalOnMissingBean(BytesMessageSender.class) + @SuppressWarnings("removal") + ZipkinWebClientSender webClientSender(ZipkinProperties properties, Encoding encoding, ObjectProvider customizers, - ObjectProvider connectionDetailsProvider) { + ObjectProvider connectionDetailsProvider, + ObjectProvider endpointSupplierFactoryProvider) { ZipkinConnectionDetails connectionDetails = connectionDetailsProvider .getIfAvailable(() -> new PropertiesZipkinConnectionDetails(properties)); + HttpEndpointSupplier.Factory endpointSupplierFactory = endpointSupplierFactoryProvider + .getIfAvailable(HttpEndpointSuppliers::constantFactory); WebClient.Builder builder = WebClient.builder(); customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); - return new ZipkinWebClientSender(connectionDetails.getSpanEndpoint(), builder.build()); + return new ZipkinWebClientSender(encoding, endpointSupplierFactory, connectionDetails.getSpanEndpoint(), + builder.build(), properties.getConnectTimeout().plus(properties.getReadTimeout())); } } @Configuration(proxyBeanMethods = false) - static class ReporterConfiguration { + @ConditionalOnClass(HttpClient.class) + @EnableConfigurationProperties(ZipkinProperties.class) + static class HttpClientSenderConfiguration { @Bean - @ConditionalOnMissingBean(Reporter.class) - @ConditionalOnBean(Sender.class) - AsyncReporter spanReporter(Sender sender, BytesEncoder encoder) { - return AsyncReporter.builder(sender).build(encoder); + @ConditionalOnMissingBean(BytesMessageSender.class) + ZipkinHttpClientSender httpClientSender(ZipkinProperties properties, Encoding encoding, + ObjectProvider customizers, + ObjectProvider connectionDetailsProvider, + ObjectProvider endpointSupplierFactoryProvider) { + ZipkinConnectionDetails connectionDetails = connectionDetailsProvider + .getIfAvailable(() -> new PropertiesZipkinConnectionDetails(properties)); + HttpEndpointSupplier.Factory endpointSupplierFactory = endpointSupplierFactoryProvider + .getIfAvailable(HttpEndpointSuppliers::constantFactory); + Builder httpClientBuilder = HttpClient.newBuilder().connectTimeout(properties.getConnectTimeout()); + customizers.orderedStream().forEach((customizer) -> customizer.customize(httpClientBuilder)); + return new ZipkinHttpClientSender(encoding, endpointSupplierFactory, connectionDetails.getSpanEndpoint(), + httpClientBuilder.build(), properties.getReadTimeout()); } } @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(ZipkinSpanHandler.class) + @ConditionalOnClass(AsyncZipkinSpanHandler.class) static class BraveConfiguration { + @Bean + @ConditionalOnMissingBean(value = MutableSpan.class, parameterizedContainer = BytesEncoder.class) + BytesEncoder mutableSpanBytesEncoder(Encoding encoding, + ObjectProvider> throwableTagProvider) { + Tag throwableTag = throwableTagProvider.getIfAvailable(() -> Tags.ERROR); + return MutableSpanBytesEncoder.create(encoding, throwableTag); + } + @Bean @ConditionalOnMissingBean - @ConditionalOnBean(Reporter.class) - ZipkinSpanHandler zipkinSpanHandler(Reporter spanReporter) { - return (ZipkinSpanHandler) ZipkinSpanHandler.newBuilder(spanReporter).build(); + @ConditionalOnBean(BytesMessageSender.class) + @ConditionalOnEnabledTracing("zipkin") + AsyncZipkinSpanHandler asyncZipkinSpanHandler(BytesMessageSender sender, + BytesEncoder mutableSpanBytesEncoder) { + return AsyncZipkinSpanHandler.newBuilder(sender).build(mutableSpanBytesEncoder); } } @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(ZipkinSpanExporter.class) + @ConditionalOnClass({ ZipkinSpanExporter.class, Span.class }) static class OpenTelemetryConfiguration { + @Bean + @ConditionalOnMissingBean(value = Span.class, parameterizedContainer = BytesEncoder.class) + BytesEncoder spanBytesEncoder(Encoding encoding) { + return SpanBytesEncoder.forEncoding(encoding); + } + @Bean @ConditionalOnMissingBean - @ConditionalOnBean(Sender.class) - ZipkinSpanExporter zipkinSpanExporter(BytesEncoder encoder, Sender sender) { - return ZipkinSpanExporter.builder().setEncoder(encoder).setSender(sender).build(); + @ConditionalOnBean(BytesMessageSender.class) + @ConditionalOnEnabledTracing("zipkin") + ZipkinSpanExporter zipkinSpanExporter(BytesMessageSender sender, BytesEncoder spanBytesEncoder) { + return ZipkinSpanExporter.builder().setSender(sender).setEncoder(spanBytesEncoder).build(); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConnectionDetails.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConnectionDetails.java index f5cc8ce43c00..9d27cc70e7e3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConnectionDetails.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConnectionDetails.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,15 @@ package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; +import zipkin2.reporter.HttpEndpointSupplier; + import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; /** * Details required to establish a connection to a Zipkin server. + *

+ * Note: {@linkplain #getSpanEndpoint()} is only read once and passed to a bean of type + * {@link HttpEndpointSupplier.Factory} which defaults to no-op (constant). * * @author Moritz Halbritter * @since 3.1.0 diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpClientBuilderCustomizer.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpClientBuilderCustomizer.java new file mode 100644 index 000000000000..73ed2e46f17b --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpClientBuilderCustomizer.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; + +import java.net.http.HttpClient; + +/** + * Callback interface that can be implemented by beans wishing to customize the + * {@link HttpClient.Builder} used to send spans to Zipkin. + * + * @author Moritz Halbritter + * @since 3.3.0 + */ +@FunctionalInterface +public interface ZipkinHttpClientBuilderCustomizer { + + /** + * Customize the http client builder. + * @param httpClient the http client builder to customize + */ + void customize(HttpClient.Builder httpClient); + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpClientSender.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpClientSender.java new file mode 100644 index 000000000000..2f982a54da20 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpClientSender.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpRequest.Builder; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandlers; +import java.time.Duration; + +import zipkin2.reporter.Encoding; +import zipkin2.reporter.HttpEndpointSupplier.Factory; + +import org.springframework.http.HttpHeaders; + +/** + * A {@link HttpSender} which uses the JDK {@link HttpClient} for HTTP communication. + * + * @author Moritz Halbritter + */ +class ZipkinHttpClientSender extends HttpSender { + + private final HttpClient httpClient; + + private final Duration readTimeout; + + ZipkinHttpClientSender(Encoding encoding, Factory endpointSupplierFactory, String endpoint, HttpClient httpClient, + Duration readTimeout) { + super(encoding, endpointSupplierFactory, endpoint); + this.httpClient = httpClient; + this.readTimeout = readTimeout; + } + + @Override + void postSpans(URI endpoint, HttpHeaders headers, byte[] body) throws IOException { + Builder request = HttpRequest.newBuilder() + .POST(BodyPublishers.ofByteArray(body)) + .uri(endpoint) + .timeout(this.readTimeout); + headers.forEach((name, values) -> values.forEach((value) -> request.header(name, value))); + try { + HttpResponse response = this.httpClient.send(request.build(), BodyHandlers.discarding()); + if (response.statusCode() / 100 != 2) { + throw new IOException("Expected HTTP status 2xx, got %d".formatted(response.statusCode())); + } + } + catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new IOException("Got interrupted while sending spans", ex); + } + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinProperties.java index 088ae8ee42b1..9245edaeb6e4 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,11 @@ public class ZipkinProperties { */ private String endpoint = "http://localhost:9411/api/v2/spans"; + /** + * How to encode the POST body to the Zipkin API. + */ + private Encoding encoding = Encoding.JSON; + /** * Connection timeout for requests to Zipkin. */ @@ -52,6 +57,14 @@ public void setEndpoint(String endpoint) { this.endpoint = endpoint; } + public Encoding getEncoding() { + return this.encoding; + } + + public void setEncoding(Encoding encoding) { + this.encoding = encoding; + } + public Duration getConnectTimeout() { return this.connectTimeout; } @@ -68,4 +81,21 @@ public void setReadTimeout(Duration readTimeout) { this.readTimeout = readTimeout; } + /** + * Zipkin message encoding. + */ + public enum Encoding { + + /** + * JSON. + */ + JSON, + + /** + * Protocol Buffers v3. + */ + PROTO3 + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateBuilderCustomizer.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateBuilderCustomizer.java index eef1ec385b91..d07600238270 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateBuilderCustomizer.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateBuilderCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,8 +24,11 @@ * * @author Marcin Grzejszczak * @since 3.0.0 + * @deprecated since 3.3.0 for removal in 3.5.0 in favor of + * {@link ZipkinHttpClientBuilderCustomizer} */ @FunctionalInterface +@Deprecated(since = "3.3.0", forRemoval = true) public interface ZipkinRestTemplateBuilderCustomizer { /** diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateSender.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateSender.java index d29e9eee60ff..69246a7be194 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateSender.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateSender.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,13 @@ package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; -import zipkin2.Call; -import zipkin2.Callback; +import java.net.URI; + +import zipkin2.reporter.Encoding; +import zipkin2.reporter.HttpEndpointSupplier.Factory; import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.web.client.RestTemplate; @@ -29,57 +32,21 @@ * @author Moritz Halbritter * @author Stefan Bratanov */ +@Deprecated(since = "3.3.0", forRemoval = true) class ZipkinRestTemplateSender extends HttpSender { - private final String endpoint; - private final RestTemplate restTemplate; - ZipkinRestTemplateSender(String endpoint, RestTemplate restTemplate) { - this.endpoint = endpoint; + ZipkinRestTemplateSender(Encoding encoding, Factory endpointSupplierFactory, String endpoint, + RestTemplate restTemplate) { + super(encoding, endpointSupplierFactory, endpoint); this.restTemplate = restTemplate; } @Override - public HttpPostCall sendSpans(byte[] batchedEncodedSpans) { - return new RestTemplateHttpPostCall(this.endpoint, batchedEncodedSpans, this.restTemplate); - } - - private static class RestTemplateHttpPostCall extends HttpPostCall { - - private final String endpoint; - - private final RestTemplate restTemplate; - - RestTemplateHttpPostCall(String endpoint, byte[] body, RestTemplate restTemplate) { - super(body); - this.endpoint = endpoint; - this.restTemplate = restTemplate; - } - - @Override - public Call clone() { - return new RestTemplateHttpPostCall(this.endpoint, getUncompressedBody(), this.restTemplate); - } - - @Override - protected Void doExecute() { - HttpEntity request = new HttpEntity<>(getBody(), getDefaultHeaders()); - this.restTemplate.exchange(this.endpoint, HttpMethod.POST, request, Void.class); - return null; - } - - @Override - protected void doEnqueue(Callback callback) { - try { - doExecute(); - callback.onSuccess(null); - } - catch (Exception ex) { - callback.onError(ex); - } - } - + void postSpans(URI endpoint, HttpHeaders headers, byte[] body) { + HttpEntity request = new HttpEntity<>(body, headers); + this.restTemplate.exchange(endpoint, HttpMethod.POST, request, Void.class); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientBuilderCustomizer.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientBuilderCustomizer.java index bd6be6e93324..206315772140 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientBuilderCustomizer.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientBuilderCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,8 +25,11 @@ * * @author Marcin Grzejszczak * @since 3.0.0 + * @deprecated since 3.3.0 for removal in 3.5.0 in favor of + * {@link ZipkinHttpClientBuilderCustomizer} */ @FunctionalInterface +@Deprecated(since = "3.3.0", forRemoval = true) public interface ZipkinWebClientBuilderCustomizer { /** diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientSender.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientSender.java index b9992bd5754d..9c3ffe388797 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientSender.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientSender.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,76 +16,45 @@ package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; -import reactor.core.publisher.Mono; -import zipkin2.Call; -import zipkin2.Callback; +import java.net.URI; +import java.time.Duration; + +import zipkin2.reporter.Encoding; +import zipkin2.reporter.HttpEndpointSupplier.Factory; import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; import org.springframework.web.reactive.function.client.WebClient; /** * An {@link HttpSender} which uses {@link WebClient} for HTTP communication. * * @author Stefan Bratanov + * @author Moritz Halbritter */ +@Deprecated(since = "3.3.0", forRemoval = true) class ZipkinWebClientSender extends HttpSender { - private final String endpoint; - private final WebClient webClient; - ZipkinWebClientSender(String endpoint, WebClient webClient) { - this.endpoint = endpoint; + private final Duration timeout; + + ZipkinWebClientSender(Encoding encoding, Factory endpointSupplierFactory, String endpoint, WebClient webClient, + Duration timeout) { + super(encoding, endpointSupplierFactory, endpoint); this.webClient = webClient; + this.timeout = timeout; } @Override - public HttpPostCall sendSpans(byte[] batchedEncodedSpans) { - return new WebClientHttpPostCall(this.endpoint, batchedEncodedSpans, this.webClient); - } - - private static class WebClientHttpPostCall extends HttpPostCall { - - private final String endpoint; - - private final WebClient webClient; - - WebClientHttpPostCall(String endpoint, byte[] body, WebClient webClient) { - super(body); - this.endpoint = endpoint; - this.webClient = webClient; - } - - @Override - public Call clone() { - return new WebClientHttpPostCall(this.endpoint, getUncompressedBody(), this.webClient); - } - - @Override - protected Void doExecute() { - sendRequest().block(); - return null; - } - - @Override - protected void doEnqueue(Callback callback) { - sendRequest().subscribe((entity) -> callback.onSuccess(null), callback::onError); - } - - private Mono> sendRequest() { - return this.webClient.post() - .uri(this.endpoint) - .headers(this::addDefaultHeaders) - .bodyValue(getBody()) - .retrieve() - .toBodilessEntity(); - } - - private void addDefaultHeaders(HttpHeaders headers) { - headers.addAll(getDefaultHeaders()); - } - + void postSpans(URI endpoint, HttpHeaders headers, byte[] body) { + this.webClient.post() + .uri(endpoint) + .headers((h) -> h.addAll(headers)) + .bodyValue(body) + .retrieve() + .toBodilessEntity() + .timeout(this.timeout) + .block(); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontProperties.java index 19c66c0071c5..ce313c3360d1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontProperties.java @@ -25,6 +25,8 @@ import java.util.Map; import java.util.Set; +import com.wavefront.sdk.common.clients.service.token.TokenService.Type; + import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.PushRegistryProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; @@ -57,6 +59,11 @@ public class WavefrontProperties { */ private String apiToken; + /** + * Type of the API token. + */ + private TokenType apiTokenType; + /** * Application configuration. */ @@ -132,7 +139,7 @@ public URI getEffectiveUri() { * @return the API token */ public String getApiTokenOrThrow() { - if (this.apiToken == null && !usesProxy()) { + if (this.apiTokenType != TokenType.NO_TOKEN && this.apiToken == null && !usesProxy()) { throw new InvalidConfigurationPropertyValueException("management.wavefront.api-token", null, "This property is mandatory whenever publishing directly to the Wavefront API"); } @@ -167,6 +174,31 @@ public void setTraceDerivedCustomTagKeys(Set traceDerivedCustomTagKeys) this.traceDerivedCustomTagKeys = traceDerivedCustomTagKeys; } + public TokenType getApiTokenType() { + return this.apiTokenType; + } + + public void setApiTokenType(TokenType apiTokenType) { + this.apiTokenType = apiTokenType; + } + + /** + * Returns the {@link Type Wavefront token type}. + * @return the Wavefront token type + * @since 3.2.0 + */ + public Type getWavefrontApiTokenType() { + if (this.apiTokenType == null) { + return usesProxy() ? Type.NO_TOKEN : Type.WAVEFRONT_API_TOKEN; + } + return switch (this.apiTokenType) { + case NO_TOKEN -> Type.NO_TOKEN; + case WAVEFRONT_API_TOKEN -> Type.WAVEFRONT_API_TOKEN; + case CSP_API_TOKEN -> Type.CSP_API_TOKEN; + case CSP_CLIENT_CREDENTIALS -> Type.CSP_CLIENT_CREDENTIALS; + }; + } + public static class Application { /** @@ -385,4 +417,30 @@ public void setReportDayDistribution(boolean reportDayDistribution) { } + /** + * Wavefront token type. + * + * @since 3.2.0 + */ + public enum TokenType { + + /** + * No token. + */ + NO_TOKEN, + /** + * Wavefront API token. + */ + WAVEFRONT_API_TOKEN, + /** + * CSP API token. + */ + CSP_API_TOKEN, + /** + * CSP client credentials. + */ + CSP_CLIENT_CREDENTIALS + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontSenderConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontSenderConfiguration.java index 6cb11ae31df3..b7501a015e7c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontSenderConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontSenderConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,13 +21,17 @@ import com.wavefront.sdk.common.WavefrontSender; import com.wavefront.sdk.common.clients.WavefrontClient.Builder; +import org.springframework.boot.actuate.autoconfigure.metrics.export.ConditionalOnEnabledMetricsExport; import org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront.WavefrontMetricsExportAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing; import org.springframework.boot.actuate.autoconfigure.tracing.wavefront.WavefrontTracingAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.util.unit.DataSize; @@ -46,8 +50,10 @@ public class WavefrontSenderConfiguration { @Bean @ConditionalOnMissingBean + @Conditional(WavefrontTracingOrMetricsCondition.class) public WavefrontSender wavefrontSender(WavefrontProperties properties) { - Builder builder = new Builder(properties.getEffectiveUri().toString(), properties.getApiTokenOrThrow()); + Builder builder = new Builder(properties.getEffectiveUri().toString(), properties.getWavefrontApiTokenType(), + properties.getApiTokenOrThrow()); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); WavefrontProperties.Sender sender = properties.getSender(); map.from(sender.getMaxQueueSize()).to(builder::maxQueueSize); @@ -57,4 +63,22 @@ public WavefrontSender wavefrontSender(WavefrontProperties properties) { return builder.build(); } + static final class WavefrontTracingOrMetricsCondition extends AnyNestedCondition { + + WavefrontTracingOrMetricsCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnEnabledTracing("wavefront") + static class TracingCondition { + + } + + @ConditionalOnEnabledMetricsExport("wavefront") + static class MetricsCondition { + + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/ManagementContextFactory.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/ManagementContextFactory.java index 0871218563fb..4b98cdfc51a7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/ManagementContextFactory.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/ManagementContextFactory.java @@ -60,8 +60,8 @@ public ConfigurableApplicationContext createManagementContext(ApplicationContext Environment parentEnvironment = parentContext.getEnvironment(); ConfigurableEnvironment childEnvironment = ApplicationContextFactory.DEFAULT .createEnvironment(this.webApplicationType); - if (parentEnvironment instanceof ConfigurableEnvironment) { - childEnvironment.setConversionService(((ConfigurableEnvironment) parentEnvironment).getConversionService()); + if (parentEnvironment instanceof ConfigurableEnvironment configurableEnvironment) { + childEnvironment.setConversionService((configurableEnvironment).getConversionService()); } ConfigurableApplicationContext managementContext = ApplicationContextFactory.DEFAULT .create(this.webApplicationType); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java index 657f9dbda20d..d28ec5d41561 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,8 +26,10 @@ import org.springframework.boot.actuate.autoconfigure.web.server.ManagementWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.boot.autoconfigure.web.embedded.JettyVirtualThreadsWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.JettyWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.NettyWebServerFactoryCustomizer; +import org.springframework.boot.autoconfigure.web.embedded.TomcatVirtualThreadsWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.UndertowWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryCustomizer; @@ -76,8 +78,10 @@ static class ReactiveManagementWebServerFactoryCustomizer ReactiveManagementWebServerFactoryCustomizer(ListableBeanFactory beanFactory) { super(beanFactory, ReactiveWebServerFactoryCustomizer.class, TomcatWebServerFactoryCustomizer.class, - TomcatReactiveWebServerFactoryCustomizer.class, JettyWebServerFactoryCustomizer.class, - UndertowWebServerFactoryCustomizer.class, NettyWebServerFactoryCustomizer.class); + TomcatReactiveWebServerFactoryCustomizer.class, + TomcatVirtualThreadsWebServerFactoryCustomizer.class, JettyWebServerFactoryCustomizer.class, + JettyVirtualThreadsWebServerFactoryCustomizer.class, UndertowWebServerFactoryCustomizer.class, + NettyWebServerFactoryCustomizer.class); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ChildManagementContextInitializer.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ChildManagementContextInitializer.java index a4d423dfce39..e580d5dfcd1c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ChildManagementContextInitializer.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ChildManagementContextInitializer.java @@ -33,12 +33,14 @@ import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.context.event.ApplicationFailedEvent; import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext; -import org.springframework.boot.web.context.WebServerInitializedEvent; +import org.springframework.boot.web.context.WebServerApplicationContext; +import org.springframework.boot.web.context.WebServerGracefulShutdownLifecycle; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.SmartLifecycle; import org.springframework.context.annotation.AnnotationConfigRegistry; import org.springframework.context.aot.ApplicationContextAotGenerator; import org.springframework.context.event.ContextClosedEvent; @@ -55,8 +57,7 @@ * @author Andy Wilkinson * @author Phillip Webb */ -class ChildManagementContextInitializer - implements ApplicationListener, BeanRegistrationAotProcessor { +class ChildManagementContextInitializer implements BeanRegistrationAotProcessor, SmartLifecycle { private final ManagementContextFactory managementContextFactory; @@ -64,6 +65,8 @@ class ChildManagementContextInitializer private final ApplicationContextInitializer applicationContextInitializer; + private volatile ConfigurableApplicationContext managementContext; + ChildManagementContextInitializer(ManagementContextFactory managementContextFactory, ApplicationContext parentContext) { this(managementContextFactory, parentContext, null); @@ -79,14 +82,38 @@ private ChildManagementContextInitializer(ManagementContextFactory managementCon } @Override - public void onApplicationEvent(WebServerInitializedEvent event) { - if (event.getApplicationContext().equals(this.parentContext)) { + public void start() { + if (!(this.parentContext instanceof WebServerApplicationContext)) { + return; + } + if (this.managementContext == null) { ConfigurableApplicationContext managementContext = createManagementContext(); registerBeans(managementContext); managementContext.refresh(); + this.managementContext = managementContext; + } + else { + this.managementContext.start(); + } + } + + @Override + public void stop() { + if (this.managementContext != null) { + this.managementContext.stop(); } } + @Override + public boolean isRunning() { + return this.managementContext != null && this.managementContext.isRunning(); + } + + @Override + public int getPhase() { + return WebServerGracefulShutdownLifecycle.SMART_LIFECYCLE_PHASE + 512; + } + @Override public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { Assert.isInstanceOf(ConfigurableApplicationContext.class, this.parentContext); @@ -217,8 +244,8 @@ private void propagateCloseIfNecessary(ApplicationContext applicationContext) { } static void addIfPossible(ApplicationContext parentContext, ConfigurableApplicationContext childContext) { - if (parentContext instanceof ConfigurableApplicationContext) { - add((ConfigurableApplicationContext) parentContext, childContext); + if (parentContext instanceof ConfigurableApplicationContext configurableApplicationContext) { + add(configurableApplicationContext, childContext); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpoint.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpoint.java index da3da1131938..2759ca4d1345 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpoint.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ * * @author Dave Syer * @author Scott Frederick + * @author Moritz Halbritter * @since 2.0.0 */ @Controller @@ -72,6 +73,7 @@ private ErrorAttributeOptions getErrorAttributeOptions(ServletWebRequest request if (includeBindingErrors(request)) { options = options.including(Include.BINDING_ERRORS); } + options = includePath(request) ? options.including(Include.PATH) : options.excluding(Include.PATH); return options; } @@ -79,7 +81,7 @@ private boolean includeStackTrace(ServletWebRequest request) { return switch (this.errorProperties.getIncludeStacktrace()) { case ALWAYS -> true; case ON_PARAM -> getBooleanParameter(request, "trace"); - default -> false; + case NEVER -> false; }; } @@ -87,7 +89,7 @@ private boolean includeMessage(ServletWebRequest request) { return switch (this.errorProperties.getIncludeMessage()) { case ALWAYS -> true; case ON_PARAM -> getBooleanParameter(request, "message"); - default -> false; + case NEVER -> false; }; } @@ -95,7 +97,15 @@ private boolean includeBindingErrors(ServletWebRequest request) { return switch (this.errorProperties.getIncludeBindingErrors()) { case ALWAYS -> true; case ON_PARAM -> getBooleanParameter(request, "errors"); - default -> false; + case NEVER -> false; + }; + } + + private boolean includePath(ServletWebRequest request) { + return switch (this.errorProperties.getIncludePath()) { + case ALWAYS -> true; + case ON_PARAM -> getBooleanParameter(request, "path"); + case NEVER -> false; }; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ServletManagementChildContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ServletManagementChildContextConfiguration.java index 44de3225efc5..71beda39b6ba 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ServletManagementChildContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ServletManagementChildContextConfiguration.java @@ -39,7 +39,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.autoconfigure.web.embedded.JettyVirtualThreadsWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.JettyWebServerFactoryCustomizer; +import org.springframework.boot.autoconfigure.web.embedded.TomcatVirtualThreadsWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.UndertowWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryCustomizer; @@ -122,7 +124,8 @@ static class ServletManagementWebServerFactoryCustomizer ServletManagementWebServerFactoryCustomizer(ListableBeanFactory beanFactory) { super(beanFactory, ServletWebServerFactoryCustomizer.class, TomcatServletWebServerFactoryCustomizer.class, - TomcatWebServerFactoryCustomizer.class, JettyWebServerFactoryCustomizer.class, + TomcatWebServerFactoryCustomizer.class, TomcatVirtualThreadsWebServerFactoryCustomizer.class, + JettyWebServerFactoryCustomizer.class, JettyVirtualThreadsWebServerFactoryCustomizer.class, UndertowServletWebServerFactoryCustomizer.class, UndertowWebServerFactoryCustomizer.class); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 7dfd03e8103a..c69880ebfd08 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -321,6 +321,12 @@ "description": "Whether to enable Operating System info.", "defaultValue": false }, + { + "name": "management.info.process.enabled", + "type": "java.lang.Boolean", + "description": "Whether to enable process info.", + "defaultValue": false + }, { "name": "management.metrics.binders.files.enabled", "type": "java.lang.Boolean", @@ -1999,11 +2005,19 @@ "reason": "Should be applied at the ObservationRegistry level." } }, + { + "name": "management.metrics.web.client.request.metric-name", + "type": "java.lang.String", + "deprecation": { + "replacement": "management.observations.http.client.requests.name", + "level": "error" + } + }, { "name": "management.metrics.web.client.requests-metric-name", "type": "java.lang.String", "deprecation": { - "replacement": "management.metrics.web.client.request.metric-name", + "replacement": "management.observations.http.client.requests.name", "level": "error" } }, @@ -2049,18 +2063,45 @@ "reason": "Not needed anymore, direct instrumentation in Spring MVC." } }, + { + "name": "management.metrics.web.server.request.metric-name", + "type": "java.lang.String", + "deprecation": { + "replacement": "management.observations.http.server.requests.name", + "level": "error" + } + }, { "name": "management.metrics.web.server.requests-metric-name", "type": "java.lang.String", "deprecation": { - "replacement": "management.metrics.web.server.request.metric-name", + "replacement": "management.observations.http.server.requests.name", "level": "error" } }, + { + "name": "management.observations.annotations.enabled", + "type": "java.lang.Boolean", + "description": "Whether auto-configuration of Micrometer annotations is enabled.", + "defaultValue": false + }, + { + "name": "management.otlp.logging.compression", + "defaultValue": "none" + }, + { + "name": "management.otlp.metrics.export.base-time-unit", + "defaultValue": "milliseconds" + }, { "name": "management.otlp.tracing.compression", "defaultValue": "none" }, + { + "name": "management.otlp.tracing.export.enabled", + "type": "java.lang.Boolean", + "description": "Whether auto-configuration of tracing is enabled to export OTLP traces." + }, { "name": "management.prometheus.metrics.export.histogram-flavor", "defaultValue": "prometheus" @@ -2141,6 +2182,10 @@ "description": "SSL protocol to use.", "defaultValue": "TLS" }, + { + "name": "management.server.ssl.server-name-bundles", + "description": "Mapping of host names to SSL bundles for SNI configuration." + }, { "name": "management.server.ssl.trust-certificate", "description": "Path to a PEM-encoded SSL certificate authority file." @@ -2221,6 +2266,30 @@ "defaultValue": [ "W3C" ] + }, + { + "name": "management.wavefront.tracing.export.enabled", + "type": "java.lang.Boolean", + "description": "Whether auto-configuration of tracing is enabled to export Wavefront traces." + }, + { + "name": "management.zipkin.tracing.encoding", + "defaultValue": [ + "JSON" + ] + }, + { + "name": "management.zipkin.tracing.export.enabled", + "type": "java.lang.Boolean", + "description": "Whether auto-configuration of tracing is enabled to export Zipkin traces." + }, + { + "name": "micrometer.observations.annotations.enabled", + "type": "java.lang.Boolean", + "deprecation": { + "level": "error", + "replacement": "management.observations.annotations.enabled" + } } ], "hints": [ diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories index 4ecd02ee5689..619e3846a726 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories @@ -1,4 +1,8 @@ # Failure Analyzers org.springframework.boot.diagnostics.FailureAnalyzer=\ -org.springframework.boot.actuate.autoconfigure.metrics.ValidationFailureAnalyzer,\ -org.springframework.boot.actuate.autoconfigure.health.NoSuchHealthContributorFailureAnalyzer +org.springframework.boot.actuate.autoconfigure.health.NoSuchHealthContributorFailureAnalyzer,\ +org.springframework.boot.actuate.autoconfigure.metrics.ValidationFailureAnalyzer + +# Environment Post Processors +org.springframework.boot.env.EnvironmentPostProcessor=\ +org.springframework.boot.actuate.autoconfigure.tracing.LogCorrelationEnvironmentPostProcessor diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index affbe7607f9e..2a382e63bb31 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -25,7 +25,6 @@ org.springframework.boot.actuate.autoconfigure.flyway.FlywayEndpointAutoConfigur org.springframework.boot.actuate.autoconfigure.hazelcast.HazelcastHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration -org.springframework.boot.actuate.autoconfigure.influx.InfluxDbHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.info.InfoContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.integration.IntegrationGraphEndpointAutoConfiguration @@ -35,6 +34,8 @@ org.springframework.boot.actuate.autoconfigure.ldap.LdapHealthContributorAutoCon org.springframework.boot.actuate.autoconfigure.liquibase.LiquibaseEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.logging.LogFileWebEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.logging.LoggersEndpointAutoConfiguration +org.springframework.boot.actuate.autoconfigure.logging.opentelemetry.OpenTelemetryLoggingAutoConfiguration +org.springframework.boot.actuate.autoconfigure.logging.opentelemetry.otlp.OtlpLoggingAutoConfiguration org.springframework.boot.actuate.autoconfigure.mail.MailHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.management.HeapDumpWebEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.management.ThreadDumpEndpointAutoConfiguration @@ -43,6 +44,7 @@ org.springframework.boot.actuate.autoconfigure.metrics.JvmMetricsAutoConfigurati org.springframework.boot.actuate.autoconfigure.metrics.KafkaMetricsAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.Log4J2MetricsAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.LogbackMetricsAutoConfiguration +org.springframework.boot.actuate.autoconfigure.metrics.MetricsAspectsAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.MetricsEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.SystemMetricsAutoConfiguration @@ -63,6 +65,7 @@ org.springframework.boot.actuate.autoconfigure.metrics.export.kairos.KairosMetri org.springframework.boot.actuate.autoconfigure.metrics.export.newrelic.NewRelicMetricsExportAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsExportAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusMetricsExportAutoConfiguration +org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusSimpleclientMetricsExportAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.export.signalfx.SignalFxMetricsExportAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.export.stackdriver.StackdriverMetricsExportAutoConfiguration @@ -88,11 +91,15 @@ org.springframework.boot.actuate.autoconfigure.data.mongo.MongoReactiveHealthCon org.springframework.boot.actuate.autoconfigure.neo4j.Neo4jHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration org.springframework.boot.actuate.autoconfigure.observation.web.servlet.WebMvcObservationAutoConfiguration +org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration org.springframework.boot.actuate.autoconfigure.quartz.QuartzEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.r2dbc.ConnectionFactoryHealthContributorAutoConfiguration +org.springframework.boot.actuate.autoconfigure.r2dbc.R2dbcObservationAutoConfiguration org.springframework.boot.actuate.autoconfigure.data.redis.RedisHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.data.redis.RedisReactiveHealthContributorAutoConfiguration +org.springframework.boot.actuate.autoconfigure.sbom.SbomEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.scheduling.ScheduledTasksEndpointAutoConfiguration +org.springframework.boot.actuate.autoconfigure.scheduling.ScheduledTasksObservabilityAutoConfiguration org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration org.springframework.boot.actuate.autoconfigure.session.SessionsEndpointAutoConfiguration @@ -100,9 +107,11 @@ org.springframework.boot.actuate.autoconfigure.startup.StartupEndpointAutoConfig org.springframework.boot.actuate.autoconfigure.system.DiskSpaceHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfiguration org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration +org.springframework.boot.actuate.autoconfigure.tracing.NoopTracerAutoConfiguration org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpAutoConfiguration org.springframework.boot.actuate.autoconfigure.tracing.prometheus.PrometheusExemplarsAutoConfiguration +org.springframework.boot.actuate.autoconfigure.tracing.prometheus.PrometheusSimpleclientExemplarsAutoConfiguration org.springframework.boot.actuate.autoconfigure.tracing.wavefront.WavefrontTracingAutoConfiguration org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinAutoConfiguration org.springframework.boot.actuate.autoconfigure.wavefront.WavefrontAutoConfiguration @@ -111,4 +120,4 @@ org.springframework.boot.actuate.autoconfigure.web.exchanges.HttpExchangesEndpoi org.springframework.boot.actuate.autoconfigure.web.mappings.MappingsEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.web.reactive.ReactiveManagementContextAutoConfiguration org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration -org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration \ No newline at end of file +org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtensionTests.java index e6a689d0c48d..59a5b0d87ae0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtensionTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtensionTests.java @@ -35,10 +35,13 @@ import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; -import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; import static org.assertj.core.api.Assertions.assertThat; @@ -52,15 +55,14 @@ class CloudFoundryReactiveHealthEndpointWebExtensionTests { private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() .withPropertyValues("VCAP_APPLICATION={}") .withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class, - ReactiveUserDetailsServiceAutoConfiguration.class, WebFluxAutoConfiguration.class, - JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, + WebFluxAutoConfiguration.class, JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfigurationTests.WebClientCustomizerConfig.class, WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfiguration.class)) - .withUserConfiguration(TestHealthIndicator.class); + .withUserConfiguration(TestHealthIndicator.class, UserDetailsServiceConfiguration.class); @Test void healthComponentsAlwaysPresent() { @@ -82,4 +84,15 @@ public Health health() { } + @Configuration(proxyBeanMethods = false) + static class UserDetailsServiceConfiguration { + + @Bean + MapReactiveUserDetailsService userDetailsService() { + return new MapReactiveUserDetailsService( + User.withUsername("alice").password("secret").roles("admin").build()); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java index 2f4c99c0bc3a..e9424f872c66 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java @@ -50,7 +50,6 @@ import org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; -import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; @@ -61,6 +60,8 @@ import org.springframework.http.HttpMethod; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.test.util.ReflectionTestUtils; @@ -84,15 +85,16 @@ class ReactiveCloudFoundryActuatorAutoConfigurationTests { private static final String V3_JSON = ApiVersion.V3.getProducedMimeType().toString(); private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class, - ReactiveUserDetailsServiceAutoConfiguration.class, WebFluxAutoConfiguration.class, - JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, WebClientCustomizerConfig.class, - WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class, - EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, - HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, - InfoContributorAutoConfiguration.class, InfoEndpointAutoConfiguration.class, - ProjectInfoAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfiguration.class)); + .withConfiguration( + AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class, WebFluxAutoConfiguration.class, + JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, WebClientCustomizerConfig.class, + WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class, + EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, + InfoContributorAutoConfiguration.class, InfoEndpointAutoConfiguration.class, + ProjectInfoAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfiguration.class)) + .withUserConfiguration(UserDetailsServiceConfiguration.class); private static final String BASE_PATH = "/cloudfoundryapplication"; @@ -358,4 +360,15 @@ WebClientCustomizer webClientCustomizer() { } + @Configuration(proxyBeanMethods = false) + static class UserDetailsServiceConfiguration { + + @Bean + MapReactiveUserDetailsService userDetailsService() { + return new MapReactiveUserDetailsService( + User.withUsername("alice").password("secret").roles("admin").build()); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityServiceTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityServiceTests.java index 29258b9f20aa..ac73c0fc21ca 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityServiceTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityServiceTests.java @@ -51,13 +51,11 @@ class ReactiveCloudFoundrySecurityServiceTests { private MockWebServer server; - private WebClient.Builder builder; - @BeforeEach void setup() { this.server = new MockWebServer(); - this.builder = WebClient.builder().baseUrl(this.server.url("/").toString()); - this.securityService = new ReactiveCloudFoundrySecurityService(this.builder, CLOUD_CONTROLLER, false); + WebClient.Builder builder = WebClient.builder().baseUrl(this.server.url("/").toString()); + this.securityService = new ReactiveCloudFoundrySecurityService(builder, CLOUD_CONTROLLER, false); } @AfterEach @@ -183,7 +181,7 @@ void fetchTokenKeysWhenNoKeysReturnedFromUAA() throws Exception { response.setHeader("Content-Type", "application/json"); }); StepVerifier.create(this.securityService.fetchTokenKeys()) - .consumeNextWith((tokenKeys) -> assertThat(tokenKeys).hasSize(0)) + .consumeNextWith((tokenKeys) -> assertThat(tokenKeys).isEmpty()) .expectComplete() .verify(); expectRequest((request) -> assertThat(request.getPath()).isEqualTo("/my-cloud-controller.com/info")); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java index dbd0b28dd2c5..ea064bf689ac 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,9 @@ import java.util.Arrays; import java.util.Collection; +import java.util.List; +import jakarta.servlet.Filter; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; @@ -43,6 +45,7 @@ import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.context.ApplicationContext; import org.springframework.http.HttpMethod; @@ -51,14 +54,12 @@ import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import org.springframework.web.client.RestTemplate; import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.filter.CompositeFilter; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; /** * Tests for {@link CloudFoundryActuatorAutoConfiguration}. @@ -105,8 +106,8 @@ void cloudfoundryapplicationProducesActuatorMediaType() { .withPropertyValues("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", "vcap.application.cf_api:https://my-cloud-controller.com") .run((context) -> { - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); - mockMvc.perform(get("/cloudfoundryapplication")).andExpect(header().string("Content-Type", V3_JSON)); + MockMvcTester mvc = MockMvcTester.from(context); + assertThat(mvc.get().uri("/cloudfoundryapplication")).hasHeader("Content-Type", V3_JSON); }); } @@ -173,9 +174,7 @@ void cloudFoundryPathsIgnoredBySpringSecurity() { this.contextRunner.withBean(TestEndpoint.class, TestEndpoint::new) .withPropertyValues("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id") .run((context) -> { - FilterChainProxy securityFilterChain = (FilterChainProxy) context - .getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN); - SecurityFilterChain chain = securityFilterChain.getFilterChains().get(0); + SecurityFilterChain chain = getSecurityFilterChain(context); assertThat(chain.getFilters()).isEmpty(); MockHttpServletRequest request = new MockHttpServletRequest(); testCloudFoundrySecurity(request, BASE_PATH, chain); @@ -189,6 +188,27 @@ void cloudFoundryPathsIgnoredBySpringSecurity() { }); } + private SecurityFilterChain getSecurityFilterChain(AssertableWebApplicationContext context) { + Filter springSecurityFilterChain = context.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN, Filter.class); + FilterChainProxy filterChainProxy = getFilterChainProxy(springSecurityFilterChain); + SecurityFilterChain securityFilterChain = filterChainProxy.getFilterChains().get(0); + return securityFilterChain; + } + + private FilterChainProxy getFilterChainProxy(Filter filter) { + if (filter instanceof FilterChainProxy filterChainProxy) { + return filterChainProxy; + } + if (filter instanceof CompositeFilter) { + List filters = (List) ReflectionTestUtils.getField(filter, "filters"); + return (FilterChainProxy) filters.stream() + .filter(FilterChainProxy.class::isInstance) + .findFirst() + .orElseThrow(); + } + throw new IllegalStateException("No FilterChainProxy found"); + } + private static void testCloudFoundrySecurity(MockHttpServletRequest request, String servletPath, SecurityFilterChain chain) { request.setServletPath(servletPath); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfigurationTests.java index 0df0cf10d083..8258a3f1f5c4 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,7 @@ * @author Phillip Webb * @author Madhura Bhave */ +@SuppressWarnings("removal") class ServletEndpointManagementContextConfigurationTests { private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java index bb5833d34591..a8ec185b7560 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -95,6 +95,7 @@ void webApplicationSupportCustomPathMatcher() { } @Test + @SuppressWarnings("removal") void webApplicationConfiguresEndpointDiscoverer() { this.contextRunner.run((context) -> { assertThat(context).hasSingleBean(ControllerEndpointDiscoverer.class); @@ -109,11 +110,13 @@ void webApplicationConfiguresExposeExcludePropertyEndpointFilter() { } @Test + @SuppressWarnings("removal") void contextShouldConfigureServletEndpointDiscoverer() { this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ServletEndpointDiscoverer.class)); } @Test + @SuppressWarnings("removal") void contextWhenNotServletShouldNotConfigureServletEndpointDiscoverer() { new ApplicationContextRunner().withConfiguration(CONFIGURATIONS) .run((context) -> assertThat(context).doesNotHaveBean(ServletEndpointDiscoverer.class)); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AuditEventsEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AuditEventsEndpointDocumentationTests.java index aaf3805f91d0..3e908b0ceb1c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AuditEventsEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AuditEventsEndpointDocumentationTests.java @@ -26,11 +26,12 @@ import org.springframework.boot.actuate.audit.AuditEvent; import org.springframework.boot.actuate.audit.AuditEventRepository; import org.springframework.boot.actuate.audit.AuditEventsEndpoint; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; @@ -39,8 +40,6 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing {@link AuditEventsEndpoint}. @@ -49,17 +48,16 @@ */ class AuditEventsEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { - @MockBean + @MockitoBean private AuditEventRepository repository; @Test - void allAuditEvents() throws Exception { + void allAuditEvents() { String queryTimestamp = "2017-11-07T09:37Z"; given(this.repository.find(any(), any(), any())) .willReturn(List.of(new AuditEvent("alice", "logout", Collections.emptyMap()))); - this.mockMvc.perform(get("/actuator/auditevents").param("after", queryTimestamp)) - .andExpect(status().isOk()) - .andDo(document("auditevents/all", + assertThat(this.mvc.get().uri("/actuator/auditevents").param("after", queryTimestamp)).hasStatusOk() + .apply(document("auditevents/all", responseFields(fieldWithPath("events").description("An array of audit events."), fieldWithPath("events.[].timestamp") .description("The timestamp of when the event occurred."), @@ -68,17 +66,18 @@ void allAuditEvents() throws Exception { } @Test - void filteredAuditEvents() throws Exception { + void filteredAuditEvents() { OffsetDateTime now = OffsetDateTime.now(); String queryTimestamp = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(now); given(this.repository.find("alice", now.toInstant(), "logout")) .willReturn(List.of(new AuditEvent("alice", "logout", Collections.emptyMap()))); - this.mockMvc - .perform(get("/actuator/auditevents").param("principal", "alice") - .param("after", queryTimestamp) - .param("type", "logout")) - .andExpect(status().isOk()) - .andDo(document("auditevents/filtered", + assertThat(this.mvc.get() + .uri("/actuator/auditevents") + .param("principal", "alice") + .param("after", queryTimestamp) + .param("type", "logout")) + .hasStatusOk() + .apply(document("auditevents/filtered", queryParameters( parameterWithName("after").description( "Restricts the events to those that occurred after the given time. Optional."), diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/BeansEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/BeansEndpointDocumentationTests.java index 4eb3fb8bcd4e..882d5f57a06c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/BeansEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/BeansEndpointDocumentationTests.java @@ -33,12 +33,11 @@ import org.springframework.restdocs.payload.ResponseFieldsSnippet; import org.springframework.util.CollectionUtils; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing {@link BeansEndpoint}. @@ -48,7 +47,7 @@ class BeansEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { @Test - void beans() throws Exception { + void beans() { List beanFields = List.of(fieldWithPath("aliases").description("Names of any aliases."), fieldWithPath("scope").description("Scope of the bean."), fieldWithPath("type").description("Fully qualified type of the bean."), @@ -60,9 +59,8 @@ void beans() throws Exception { fieldWithPath("contexts").description("Application contexts keyed by id."), parentIdField(), fieldWithPath("contexts.*.beans").description("Beans in the application context keyed by name.")) .andWithPrefix("contexts.*.beans.*.", beanFields); - this.mockMvc.perform(get("/actuator/beans")) - .andExpect(status().isOk()) - .andDo(document("beans", + assertThat(this.mvc.get().uri("/actuator/beans")).hasStatusOk() + .apply(document("beans", preprocessResponse( limit(this::isIndependentBean, "contexts", getApplicationContext().getId(), "beans")), responseFields)); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/CachesEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/CachesEndpointDocumentationTests.java index 44cf427a2967..7f51ce42c411 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/CachesEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/CachesEndpointDocumentationTests.java @@ -30,17 +30,16 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; import org.springframework.restdocs.payload.FieldDescriptor; import org.springframework.restdocs.request.ParameterDescriptor; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing the {@link CachesEndpoint} @@ -59,10 +58,9 @@ class CachesEndpointDocumentationTests extends MockMvcEndpointDocumentationTests .optional()); @Test - void allCaches() throws Exception { - this.mockMvc.perform(get("/actuator/caches")) - .andExpect(status().isOk()) - .andDo(MockMvcRestDocumentation.document("caches/all", + void allCaches() { + assertThat(this.mvc.get().uri("/actuator/caches")).hasStatusOk() + .apply(MockMvcRestDocumentation.document("caches/all", responseFields(fieldWithPath("cacheManagers").description("Cache managers keyed by id."), fieldWithPath("cacheManagers.*.caches") .description("Caches in the application context keyed by name.")) @@ -71,25 +69,23 @@ void allCaches() throws Exception { } @Test - void namedCache() throws Exception { - this.mockMvc.perform(get("/actuator/caches/cities")) - .andExpect(status().isOk()) - .andDo(MockMvcRestDocumentation.document("caches/named", queryParameters(queryParameters), + void namedCache() { + assertThat(this.mvc.get().uri("/actuator/caches/cities")).hasStatusOk() + .apply(MockMvcRestDocumentation.document("caches/named", queryParameters(queryParameters), responseFields(levelFields))); } @Test - void evictAllCaches() throws Exception { - this.mockMvc.perform(delete("/actuator/caches")) - .andExpect(status().isNoContent()) - .andDo(MockMvcRestDocumentation.document("caches/evict-all")); + void evictAllCaches() { + assertThat(this.mvc.delete().uri("/actuator/caches")).hasStatus(HttpStatus.NO_CONTENT) + .apply(MockMvcRestDocumentation.document("caches/evict-all")); } @Test - void evictNamedCache() throws Exception { - this.mockMvc.perform(delete("/actuator/caches/countries?cacheManager=anotherCacheManager")) - .andExpect(status().isNoContent()) - .andDo(MockMvcRestDocumentation.document("caches/evict-named", queryParameters(queryParameters))); + void evictNamedCache() { + assertThat(this.mvc.delete().uri("/actuator/caches/countries?cacheManager=anotherCacheManager")) + .hasStatus(HttpStatus.NO_CONTENT) + .apply(MockMvcRestDocumentation.document("caches/evict-named", queryParameters(queryParameters))); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConditionsReportEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConditionsReportEndpointDocumentationTests.java index 58586569d147..63cf79092eba 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConditionsReportEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConditionsReportEndpointDocumentationTests.java @@ -31,11 +31,10 @@ import org.springframework.restdocs.payload.FieldDescriptor; import org.springframework.restdocs.payload.JsonFieldType; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing {@link ConditionsReportEndpoint}. @@ -64,9 +63,8 @@ void conditions() throws Exception { .optional()); FieldDescriptor unconditionalClassesField = fieldWithPath("contexts.*.unconditionalClasses") .description("Names of unconditional auto-configuration classes if any."); - this.mockMvc.perform(get("/actuator/conditions")) - .andExpect(status().isOk()) - .andDo(MockMvcRestDocumentation.document("conditions", + assertThat(this.mvc.get().uri("/actuator/conditions")).hasStatusOk() + .apply(MockMvcRestDocumentation.document("conditions", preprocessResponse(limit("contexts", getApplicationContext().getId(), "positiveMatches"), limit("contexts", getApplicationContext().getId(), "negativeMatches")), responseFields(fieldWithPath("contexts").description("Application contexts keyed by id.")) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConfigurationPropertiesReportEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConfigurationPropertiesReportEndpointDocumentationTests.java index d29a789351b6..edba8a0c740e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConfigurationPropertiesReportEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConfigurationPropertiesReportEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,12 +27,11 @@ import org.springframework.context.annotation.Import; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing @@ -44,10 +43,9 @@ class ConfigurationPropertiesReportEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { @Test - void configProps() throws Exception { - this.mockMvc.perform(get("/actuator/configprops")) - .andExpect(status().isOk()) - .andDo(MockMvcRestDocumentation.document("configprops/all", + void configProps() { + assertThat(this.mvc.get().uri("/actuator/configprops")).hasStatusOk() + .apply(MockMvcRestDocumentation.document("configprops/all", preprocessResponse(limit("contexts", getApplicationContext().getId(), "beans")), responseFields(fieldWithPath("contexts").description("Application contexts keyed by id."), fieldWithPath("contexts.*.beans.*") @@ -62,10 +60,9 @@ void configProps() throws Exception { } @Test - void configPropsFilterByPrefix() throws Exception { - this.mockMvc.perform(get("/actuator/configprops/spring.jackson")) - .andExpect(status().isOk()) - .andDo(MockMvcRestDocumentation.document("configprops/prefixed", + void configPropsFilterByPrefix() { + assertThat(this.mvc.get().uri("/actuator/configprops/spring.jackson")).hasStatusOk() + .apply(MockMvcRestDocumentation.document("configprops/prefixed", preprocessResponse(limit("contexts", getApplicationContext().getId(), "beans")), responseFields(fieldWithPath("contexts").description("Application contexts keyed by id."), fieldWithPath("contexts.*.beans.*") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/EnvironmentEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/EnvironmentEndpointDocumentationTests.java index 061f6e8eaf8e..6b8cda6ea7b1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/EnvironmentEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/EnvironmentEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,13 +44,12 @@ import org.springframework.restdocs.payload.FieldDescriptor; import org.springframework.test.context.TestPropertySource; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; import static org.springframework.restdocs.operation.preprocess.Preprocessors.replacePattern; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing the {@link EnvironmentEndpoint}. @@ -64,6 +63,9 @@ class EnvironmentEndpointDocumentationTests extends MockMvcEndpointDocumentation private static final FieldDescriptor activeProfiles = fieldWithPath("activeProfiles") .description("Names of the active profiles, if any."); + private static final FieldDescriptor defaultProfiles = fieldWithPath("defaultProfiles") + .description("Names of the default profiles, if any."); + private static final FieldDescriptor propertySources = fieldWithPath("propertySources") .description("Property sources in order of precedence."); @@ -71,15 +73,14 @@ class EnvironmentEndpointDocumentationTests extends MockMvcEndpointDocumentation .description("Name of the property source."); @Test - void env() throws Exception { - this.mockMvc.perform(get("/actuator/env")) - .andExpect(status().isOk()) - .andDo(document("env/all", + void env() { + assertThat(this.mvc.get().uri("/actuator/env")).hasStatusOk() + .apply(document("env/all", preprocessResponse( replacePattern(Pattern.compile( "org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/"), ""), filterProperties()), - responseFields(activeProfiles, propertySources, propertySourceName, + responseFields(activeProfiles, defaultProfiles, propertySources, propertySourceName, fieldWithPath("propertySources.[].properties") .description("Properties in the property source keyed by property name."), fieldWithPath("propertySources.[].properties.*.value") @@ -90,10 +91,9 @@ void env() throws Exception { } @Test - void singlePropertyFromEnv() throws Exception { - this.mockMvc.perform(get("/actuator/env/com.example.cache.max-size")) - .andExpect(status().isOk()) - .andDo(document("env/single", + void singlePropertyFromEnv() { + assertThat(this.mvc.get().uri("/actuator/env/com.example.cache.max-size")).hasStatusOk() + .apply(document("env/single", preprocessResponse(replacePattern(Pattern .compile("org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/"), "")), responseFields( @@ -101,7 +101,7 @@ void singlePropertyFromEnv() throws Exception { .optional(), fieldWithPath("property.source").description("Name of the source of the property."), fieldWithPath("property.value").description("Value of the property."), activeProfiles, - propertySources, propertySourceName, + defaultProfiles, propertySources, propertySourceName, fieldWithPath("propertySources.[].property") .description("Property in the property source, if any.") .optional(), diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/FlywayEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/FlywayEndpointDocumentationTests.java index 591b40e72318..6f41357d8a07 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/FlywayEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/FlywayEndpointDocumentationTests.java @@ -35,10 +35,9 @@ import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; import org.springframework.restdocs.payload.FieldDescriptor; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing the {@link FlywayEndpoint}. @@ -48,10 +47,9 @@ class FlywayEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { @Test - void flyway() throws Exception { - this.mockMvc.perform(get("/actuator/flyway")) - .andExpect(status().isOk()) - .andDo(MockMvcRestDocumentation.document("flyway", + void flyway() { + assertThat(this.mvc.get().uri("/actuator/flyway")).hasStatusOk() + .apply(MockMvcRestDocumentation.document("flyway", responseFields(fieldWithPath("contexts").description("Application contexts keyed by id"), fieldWithPath("contexts.*.flywayBeans.*.migrations") .description("Migrations performed by the Flyway instance, keyed by Flyway bean name.")) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java index cc6920b3bf36..c0457082015b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java @@ -52,12 +52,11 @@ import org.springframework.restdocs.payload.FieldDescriptor; import org.springframework.util.unit.DataSize; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing the {@link HealthEndpoint}. @@ -72,7 +71,7 @@ class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentationTests subsectionWithPath("details").description("Details of the health of a specific part of the application.")); @Test - void health() throws Exception { + void health() { FieldDescriptor status = fieldWithPath("status").description("Overall status of the application."); FieldDescriptor components = fieldWithPath("components").description("The components that make up the health."); FieldDescriptor componentStatus = fieldWithPath("components.*.status") @@ -84,24 +83,21 @@ void health() throws Exception { .description("Details of the health of a specific part of the application. " + "Presence is controlled by `management.endpoint.health.show-details`.") .optional(); - this.mockMvc.perform(get("/actuator/health").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andDo(document("health", + assertThat(this.mvc.get().uri("/actuator/health").accept(MediaType.APPLICATION_JSON)).hasStatusOk() + .apply(document("health", responseFields(status, components, componentStatus, nestedComponents, componentDetails))); } @Test - void healthComponent() throws Exception { - this.mockMvc.perform(get("/actuator/health/db").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andDo(document("health/component", responseFields(componentFields))); + void healthComponent() { + assertThat(this.mvc.get().uri("/actuator/health/db").accept(MediaType.APPLICATION_JSON)).hasStatusOk() + .apply(document("health/component", responseFields(componentFields))); } @Test - void healthComponentInstance() throws Exception { - this.mockMvc.perform(get("/actuator/health/broker/us1").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andDo(document("health/instance", responseFields(componentFields))); + void healthComponentInstance() { + assertThat(this.mvc.get().uri("/actuator/health/broker/us1").accept(MediaType.APPLICATION_JSON)).hasStatusOk() + .apply(document("health/instance", responseFields(componentFields))); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HeapDumpWebEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HeapDumpWebEndpointDocumentationTests.java index fc0f19fc1b0a..d82fccef53f7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HeapDumpWebEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HeapDumpWebEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,9 +32,8 @@ import org.springframework.restdocs.operation.Operation; import org.springframework.util.FileCopyUtils; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing the {@link HeapDumpWebEndpoint}. @@ -44,10 +43,9 @@ class HeapDumpWebEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { @Test - void heapDump() throws Exception { - this.mockMvc.perform(get("/actuator/heapdump")) - .andExpect(status().isOk()) - .andDo(document("heapdump", new CurlRequestSnippet(CliDocumentation.multiLineFormat()) { + void heapDump() { + assertThat(this.mvc.get().uri("/actuator/heapdump")).hasStatusOk() + .apply(document("heapdump", new CurlRequestSnippet(CliDocumentation.multiLineFormat()) { @Override protected Map createModel(Operation operation) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HttpExchangesEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HttpExchangesEndpointDocumentationTests.java index 95a91984ce75..be184eb11dd8 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HttpExchangesEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HttpExchangesEndpointDocumentationTests.java @@ -35,20 +35,19 @@ import org.springframework.boot.actuate.web.exchanges.Include; import org.springframework.boot.actuate.web.exchanges.RecordableHttpRequest; import org.springframework.boot.actuate.web.exchanges.RecordableHttpResponse; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.http.HttpHeaders; import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing {@link HttpExchangesEndpoint}. @@ -57,11 +56,11 @@ */ class HttpExchangesEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { - @MockBean + @MockitoBean private HttpExchangeRepository repository; @Test - void httpExchanges() throws Exception { + void httpExchanges() { RecordableHttpRequest request = mock(RecordableHttpRequest.class); given(request.getUri()).willReturn(URI.create("https://api.example.com")); given(request.getMethod()).willReturn("GET"); @@ -79,9 +78,8 @@ void httpExchanges() throws Exception { HttpExchange exchange = HttpExchange.start(start, request) .finish(end, response, () -> principal, () -> UUID.randomUUID().toString(), EnumSet.allOf(Include.class)); given(this.repository.findAll()).willReturn(List.of(exchange)); - this.mockMvc.perform(get("/actuator/httpexchanges")) - .andExpect(status().isOk()) - .andDo(document("httpexchanges", responseFields( + assertThat(this.mvc.get().uri("/actuator/httpexchanges")).hasStatusOk() + .apply(document("httpexchanges", responseFields( fieldWithPath("exchanges").description("An array of HTTP request-response exchanges."), fieldWithPath("exchanges.[].timestamp").description("Timestamp of when the exchange occurred."), fieldWithPath("exchanges.[].principal").description("Principal of the exchange, if any.") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/InfoEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/InfoEndpointDocumentationTests.java index 39987ab5c5c3..63b760f6e5b6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/InfoEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/InfoEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,11 +34,10 @@ import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; import org.springframework.restdocs.payload.JsonFieldType; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing the {@link InfoEndpoint}. @@ -48,10 +47,9 @@ class InfoEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { @Test - void info() throws Exception { - this.mockMvc.perform(get("/actuator/info")) - .andExpect(status().isOk()) - .andDo(MockMvcRestDocumentation.document("info", + void info() { + assertThat(this.mvc.get().uri("/actuator/info")).hasStatusOk() + .apply(MockMvcRestDocumentation.document("info", responseFields(beneathPath("git"), fieldWithPath("branch").description("Name of the Git branch, if any."), fieldWithPath("commit").description("Details of the Git commit, if any."), diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/IntegrationGraphEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/IntegrationGraphEndpointDocumentationTests.java index 36e7fb1e65a7..32b0b2a15c20 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/IntegrationGraphEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/IntegrationGraphEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,13 +22,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; import org.springframework.integration.config.EnableIntegration; import org.springframework.integration.graph.IntegrationGraphServer; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.assertj.core.api.Assertions.assertThat; /** * Tests for generating documentation describing the {@link IntegrationGraphEndpoint}. @@ -38,17 +37,15 @@ class IntegrationGraphEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { @Test - void graph() throws Exception { - this.mockMvc.perform(get("/actuator/integrationgraph")) - .andExpect(status().isOk()) - .andDo(MockMvcRestDocumentation.document("integrationgraph/graph")); + void graph() { + assertThat(this.mvc.get().uri("/actuator/integrationgraph")).hasStatusOk() + .apply(MockMvcRestDocumentation.document("integrationgraph/graph")); } @Test - void rebuild() throws Exception { - this.mockMvc.perform(post("/actuator/integrationgraph")) - .andExpect(status().isNoContent()) - .andDo(MockMvcRestDocumentation.document("integrationgraph/rebuild")); + void rebuild() { + assertThat(this.mvc.post().uri("/actuator/integrationgraph")).hasStatus(HttpStatus.NO_CONTENT) + .apply(MockMvcRestDocumentation.document("integrationgraph/rebuild")); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LiquibaseEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LiquibaseEndpointDocumentationTests.java index cd12c81c2ca9..042e9e1e03ca 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LiquibaseEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LiquibaseEndpointDocumentationTests.java @@ -32,10 +32,9 @@ import org.springframework.restdocs.payload.FieldDescriptor; import org.springframework.restdocs.payload.JsonFieldType; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing the {@link LiquibaseEndpoint}. @@ -45,12 +44,11 @@ class LiquibaseEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { @Test - void liquibase() throws Exception { + void liquibase() { FieldDescriptor changeSetsField = fieldWithPath("contexts.*.liquibaseBeans.*.changeSets") .description("Change sets made by the Liquibase beans, keyed by bean name."); - this.mockMvc.perform(get("/actuator/liquibase")) - .andExpect(status().isOk()) - .andDo(MockMvcRestDocumentation.document("liquibase", + assertThat(this.mvc.get().uri("/actuator/liquibase")).hasStatusOk() + .apply(MockMvcRestDocumentation.document("liquibase", responseFields(fieldWithPath("contexts").description("Application contexts keyed by id"), changeSetsField) .andWithPrefix("contexts.*.liquibaseBeans.*.changeSets[].", getChangeSetFieldDescriptors()) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LogFileWebEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LogFileWebEndpointDocumentationTests.java index cb80ee01f239..84ddd459bb90 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LogFileWebEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LogFileWebEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,11 +23,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; import org.springframework.mock.env.MockEnvironment; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.assertj.core.api.Assertions.assertThat; /** * Tests for generating documentation describing the {@link LogFileWebEndpoint}. @@ -37,17 +37,16 @@ class LogFileWebEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { @Test - void logFile() throws Exception { - this.mockMvc.perform(get("/actuator/logfile")) - .andExpect(status().isOk()) - .andDo(MockMvcRestDocumentation.document("logfile/entire")); + void logFile() { + assertThat(this.mvc.get().uri("/actuator/logfile")).hasStatusOk() + .apply(MockMvcRestDocumentation.document("logfile/entire")); } @Test - void logFileRange() throws Exception { - this.mockMvc.perform(get("/actuator/logfile").header("Range", "bytes=0-1023")) - .andExpect(status().isPartialContent()) - .andDo(MockMvcRestDocumentation.document("logfile/range")); + void logFileRange() { + assertThat(this.mvc.get().uri("/actuator/logfile").header("Range", "bytes=0-1023")) + .hasStatus(HttpStatus.PARTIAL_CONTENT) + .apply(MockMvcRestDocumentation.document("logfile/range")); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LoggersEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LoggersEndpointDocumentationTests.java index acc1b10a2169..94c212d2d12c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LoggersEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LoggersEndpointDocumentationTests.java @@ -29,23 +29,22 @@ import org.springframework.boot.logging.LoggerConfiguration; import org.springframework.boot.logging.LoggerGroups; import org.springframework.boot.logging.LoggingSystem; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; import org.springframework.restdocs.payload.FieldDescriptor; import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing the {@link LoggersEndpoint}. @@ -63,21 +62,20 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest .type(JsonFieldType.STRING) .optional(), fieldWithPath("members").description("Loggers that are part of this group")); - @MockBean + @MockitoBean private LoggingSystem loggingSystem; @Autowired private LoggerGroups loggerGroups; @Test - void allLoggers() throws Exception { + void allLoggers() { given(this.loggingSystem.getSupportedLogLevels()).willReturn(EnumSet.allOf(LogLevel.class)); given(this.loggingSystem.getLoggerConfigurations()) .willReturn(List.of(new LoggerConfiguration("ROOT", LogLevel.INFO, LogLevel.INFO), new LoggerConfiguration("com.example", LogLevel.DEBUG, LogLevel.DEBUG))); - this.mockMvc.perform(get("/actuator/loggers")) - .andExpect(status().isOk()) - .andDo(MockMvcRestDocumentation.document("loggers/all", + assertThat(this.mvc.get().uri("/actuator/loggers")).hasStatusOk() + .apply(MockMvcRestDocumentation.document("loggers/all", responseFields(fieldWithPath("levels").description("Levels support by the logging system."), fieldWithPath("loggers").description("Loggers keyed by name."), fieldWithPath("groups").description("Logger groups keyed by name")) @@ -86,31 +84,30 @@ void allLoggers() throws Exception { } @Test - void logger() throws Exception { + void logger() { given(this.loggingSystem.getLoggerConfiguration("com.example")) .willReturn(new LoggerConfiguration("com.example", LogLevel.INFO, LogLevel.INFO)); - this.mockMvc.perform(get("/actuator/loggers/com.example")) - .andExpect(status().isOk()) - .andDo(MockMvcRestDocumentation.document("loggers/single", responseFields(levelFields))); + assertThat(this.mvc.get().uri("/actuator/loggers/com.example")).hasStatusOk() + .apply(MockMvcRestDocumentation.document("loggers/single", responseFields(levelFields))); } @Test - void loggerGroups() throws Exception { + void loggerGroups() { this.loggerGroups.get("test").configureLogLevel(LogLevel.INFO, (member, level) -> { }); - this.mockMvc.perform(get("/actuator/loggers/test")) - .andExpect(status().isOk()) - .andDo(MockMvcRestDocumentation.document("loggers/group", responseFields(groupLevelFields))); + assertThat(this.mvc.get().uri("/actuator/loggers/test")).hasStatusOk() + .apply(MockMvcRestDocumentation.document("loggers/group", responseFields(groupLevelFields))); resetLogger(); } @Test - void setLogLevel() throws Exception { - this.mockMvc - .perform(post("/actuator/loggers/com.example").content("{\"configuredLevel\":\"debug\"}") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNoContent()) - .andDo(MockMvcRestDocumentation.document("loggers/set", + void setLogLevel() { + assertThat(this.mvc.post() + .uri("/actuator/loggers/com.example") + .content("{\"configuredLevel\":\"debug\"}") + .contentType(MediaType.APPLICATION_JSON)) + .hasStatus(HttpStatus.NO_CONTENT) + .apply(MockMvcRestDocumentation.document("loggers/set", requestFields(fieldWithPath("configuredLevel") .description("Level for the logger. May be omitted to clear the level.") .optional()))); @@ -118,12 +115,13 @@ void setLogLevel() throws Exception { } @Test - void setLogLevelOfLoggerGroup() throws Exception { - this.mockMvc - .perform(post("/actuator/loggers/test").content("{\"configuredLevel\":\"debug\"}") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNoContent()) - .andDo(MockMvcRestDocumentation.document("loggers/setGroup", + void setLogLevelOfLoggerGroup() { + assertThat(this.mvc.post() + .uri("/actuator/loggers/test") + .content("{\"configuredLevel\":\"debug\"}") + .contentType(MediaType.APPLICATION_JSON)) + .hasStatus(HttpStatus.NO_CONTENT) + .apply(MockMvcRestDocumentation.document("loggers/setGroup", requestFields(fieldWithPath("configuredLevel") .description("Level for the logger group. May be omitted to clear the level of the loggers.") .optional()))); @@ -138,11 +136,12 @@ private void resetLogger() { } @Test - void clearLogLevel() throws Exception { - this.mockMvc - .perform(post("/actuator/loggers/com.example").content("{}").contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNoContent()) - .andDo(MockMvcRestDocumentation.document("loggers/clear")); + void clearLogLevel() { + assertThat(this.mvc.post() + .uri("/actuator/loggers/com.example") + .content("{}") + .contentType(MediaType.APPLICATION_JSON)).hasStatus(HttpStatus.NO_CONTENT) + .apply(MockMvcRestDocumentation.document("loggers/clear")); then(this.loggingSystem).should().setLogLevel("com.example", null); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MetricsEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MetricsEndpointDocumentationTests.java index 3faa23d9ab66..feadcd13cc56 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MetricsEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MetricsEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,13 +26,12 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing the {@link MetricsEndpoint}. @@ -42,18 +41,16 @@ class MetricsEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { @Test - void metricNames() throws Exception { - this.mockMvc.perform(get("/actuator/metrics")) - .andExpect(status().isOk()) - .andDo(document("metrics/names", + void metricNames() { + assertThat(this.mvc.get().uri("/actuator/metrics")).hasStatusOk() + .apply(document("metrics/names", responseFields(fieldWithPath("names").description("Names of the known metrics.")))); } @Test - void metric() throws Exception { - this.mockMvc.perform(get("/actuator/metrics/jvm.memory.max")) - .andExpect(status().isOk()) - .andDo(document("metrics/metric", + void metric() { + assertThat(this.mvc.get().uri("/actuator/metrics/jvm.memory.max")).hasStatusOk() + .apply(document("metrics/metric", responseFields(fieldWithPath("name").description("Name of the metric"), fieldWithPath("description").description("Description of the metric"), fieldWithPath("baseUnit").description("Base unit of the metric"), @@ -67,12 +64,12 @@ void metric() throws Exception { } @Test - void metricWithTags() throws Exception { - this.mockMvc - .perform(get("/actuator/metrics/jvm.memory.max").param("tag", "area:nonheap") - .param("tag", "id:Compressed Class Space")) - .andExpect(status().isOk()) - .andDo(document("metrics/metric-with-tags", queryParameters( + void metricWithTags() { + assertThat(this.mvc.get() + .uri("/actuator/metrics/jvm.memory.max") + .param("tag", "area:nonheap") + .param("tag", "id:Compressed Class Space")).hasStatusOk() + .apply(document("metrics/metric-with-tags", queryParameters( parameterWithName("tag").description("A tag to use for drill-down in the form `name:value`.")))); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MockMvcEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MockMvcEndpointDocumentationTests.java index fbfabbc3f4fe..cd61894880ab 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MockMvcEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MockMvcEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,13 +24,12 @@ import org.springframework.restdocs.RestDocumentationContextProvider; import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import org.springframework.web.context.WebApplicationContext; /** * Abstract base class for tests that generate endpoint documentation using Spring REST - * Docs and {@link MockMvc}. + * Docs and {@link MockMvcTester}. * * @author Andy Wilkinson */ @@ -38,16 +37,17 @@ @SpringBootTest public abstract class MockMvcEndpointDocumentationTests extends AbstractEndpointDocumentationTests { - protected MockMvc mockMvc; + protected MockMvcTester mvc; @Autowired private WebApplicationContext applicationContext; @BeforeEach void setup(RestDocumentationContextProvider restDocumentation) { - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.applicationContext) - .apply(MockMvcRestDocumentation.documentationConfiguration(restDocumentation).uris()) - .build(); + this.mvc = MockMvcTester.from(this.applicationContext, + (builder) -> builder + .apply(MockMvcRestDocumentation.documentationConfiguration(restDocumentation).uris()) + .build()); } protected WebApplicationContext getApplicationContext() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/PrometheusScrapeEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/PrometheusScrapeEndpointDocumentationTests.java index 5976155a27b4..d744c03fc69e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/PrometheusScrapeEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/PrometheusScrapeEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,13 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation; +import java.util.Properties; + import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics; -import io.micrometer.prometheus.PrometheusMeterRegistry; -import io.prometheus.client.CollectorRegistry; +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; import io.prometheus.client.exporter.common.TextFormat; +import io.prometheus.metrics.model.registry.PrometheusRegistry; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusScrapeEndpoint; @@ -28,12 +30,10 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing the {@link PrometheusScrapeEndpoint}. @@ -44,25 +44,28 @@ class PrometheusScrapeEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { @Test - void prometheus() throws Exception { - this.mockMvc.perform(get("/actuator/prometheus")).andExpect(status().isOk()).andDo(document("prometheus/all")); + void prometheus() { + assertThat(this.mvc.get().uri("/actuator/prometheus")).hasStatusOk().apply(document("prometheus/all")); } @Test - void prometheusOpenmetrics() throws Exception { - this.mockMvc.perform(get("/actuator/prometheus").accept(TextFormat.CONTENT_TYPE_OPENMETRICS_100)) - .andExpect(status().isOk()) - .andExpect(header().string("Content-Type", "application/openmetrics-text;version=1.0.0;charset=utf-8")) - .andDo(document("prometheus/openmetrics")); + void prometheusOpenmetrics() { + assertThat(this.mvc.get().uri("/actuator/prometheus").accept(TextFormat.CONTENT_TYPE_OPENMETRICS_100)) + .satisfies((result) -> { + assertThat(result).hasStatusOk() + .headers() + .hasValue("Content-Type", "application/openmetrics-text;version=1.0.0;charset=utf-8"); + assertThat(result).apply(document("prometheus/openmetrics")); + }); } @Test - void filteredPrometheus() throws Exception { - this.mockMvc - .perform(get("/actuator/prometheus").param("includedNames", - "jvm_memory_used_bytes,jvm_memory_committed_bytes")) - .andExpect(status().isOk()) - .andDo(document("prometheus/names", + void filteredPrometheus() { + assertThat(this.mvc.get() + .uri("/actuator/prometheus") + .param("includedNames", "jvm_memory_used_bytes,jvm_memory_committed_bytes")) + .hasStatusOk() + .apply(document("prometheus/names", queryParameters(parameterWithName("includedNames") .description("Restricts the samples to those that match the names. Optional.") .optional()))); @@ -74,11 +77,11 @@ static class TestConfiguration { @Bean PrometheusScrapeEndpoint endpoint() { - CollectorRegistry collectorRegistry = new CollectorRegistry(true); - PrometheusMeterRegistry meterRegistry = new PrometheusMeterRegistry((key) -> null, collectorRegistry, + PrometheusRegistry prometheusRegistry = new PrometheusRegistry(); + PrometheusMeterRegistry meterRegistry = new PrometheusMeterRegistry((key) -> null, prometheusRegistry, Clock.SYSTEM); new JvmMemoryMetrics().bindTo(meterRegistry); - return new PrometheusScrapeEndpoint(collectorRegistry); + return new PrometheusScrapeEndpoint(prometheusRegistry, new Properties()); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/PrometheusSimpleclientScrapeEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/PrometheusSimpleclientScrapeEndpointDocumentationTests.java new file mode 100644 index 000000000000..6a06bf418a91 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/PrometheusSimpleclientScrapeEndpointDocumentationTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics; +import io.prometheus.client.CollectorRegistry; +import io.prometheus.client.exporter.common.TextFormat; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusSimpleclientScrapeEndpoint; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; + +/** + * Tests for generating documentation describing the + * {@link PrometheusSimpleclientScrapeEndpoint}. + * + * @author Andy Wilkinson + * @author Johnny Lim + */ +class PrometheusSimpleclientScrapeEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { + + @Test + void prometheus() { + assertThat(this.mvc.get().uri("/actuator/prometheus")).hasStatusOk() + .apply(document("prometheus-simpleclient/all")); + } + + @Test + void prometheusOpenmetrics() { + assertThat(this.mvc.get().uri("/actuator/prometheus").accept(TextFormat.CONTENT_TYPE_OPENMETRICS_100)) + .satisfies((result) -> { + assertThat(result).hasStatusOk() + .headers() + .hasValue("Content-Type", "application/openmetrics-text;version=1.0.0;charset=utf-8"); + assertThat(result).apply(document("prometheus-simpleclient/openmetrics")); + }); + + } + + @Test + void filteredPrometheus() { + assertThat(this.mvc.get() + .uri("/actuator/prometheus") + .param("includedNames", "jvm_memory_used_bytes,jvm_memory_committed_bytes")) + .hasStatusOk() + .apply(document("prometheus-simpleclient/names", + queryParameters(parameterWithName("includedNames") + .description("Restricts the samples to those that match the names. Optional.") + .optional()))); + } + + @Configuration(proxyBeanMethods = false) + @Import(BaseDocumentationConfiguration.class) + static class TestConfiguration { + + @Bean + @SuppressWarnings({ "removal", "deprecation" }) + PrometheusSimpleclientScrapeEndpoint endpoint() { + CollectorRegistry collectorRegistry = new CollectorRegistry(true); + io.micrometer.prometheus.PrometheusMeterRegistry meterRegistry = new io.micrometer.prometheus.PrometheusMeterRegistry( + (key) -> null, collectorRegistry, Clock.SYSTEM); + new JvmMemoryMetrics().bindTo(meterRegistry); + return new PrometheusSimpleclientScrapeEndpoint(collectorRegistry); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/QuartzEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/QuartzEndpointDocumentationTests.java index fd51daf5d113..4f8a04060040 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/QuartzEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/QuartzEndpointDocumentationTests.java @@ -54,16 +54,17 @@ import org.springframework.boot.actuate.endpoint.Show; import org.springframework.boot.actuate.quartz.QuartzEndpoint; import org.springframework.boot.actuate.quartz.QuartzEndpointWebExtension; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.restdocs.payload.FieldDescriptor; import org.springframework.restdocs.payload.JsonFieldType; import org.springframework.scheduling.quartz.DelegatingJob; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; @@ -71,8 +72,6 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.relaxedResponseFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing the {@link QuartzEndpoint}. @@ -179,16 +178,15 @@ class QuartzEndpointDocumentationTests extends MockMvcEndpointDocumentationTests .type(JsonFieldType.OBJECT) .description("Job data map keyed by name, if any.") }; - @MockBean + @MockitoBean private Scheduler scheduler; @Test void quartzReport() throws Exception { mockJobs(jobOne, jobTwo, jobThree); mockTriggers(cronTrigger, simpleTrigger, calendarIntervalTrigger, dailyTimeIntervalTrigger); - this.mockMvc.perform(get("/actuator/quartz")) - .andExpect(status().isOk()) - .andDo(document("quartz/report", + assertThat(this.mvc.get().uri("/actuator/quartz")).hasStatusOk() + .apply(document("quartz/report", responseFields(fieldWithPath("jobs.groups").description("An array of job group names."), fieldWithPath("triggers.groups").description("An array of trigger group names.")))); } @@ -196,9 +194,8 @@ void quartzReport() throws Exception { @Test void quartzJobs() throws Exception { mockJobs(jobOne, jobTwo, jobThree); - this.mockMvc.perform(get("/actuator/quartz/jobs")) - .andExpect(status().isOk()) - .andDo(document("quartz/jobs", + assertThat(this.mvc.get().uri("/actuator/quartz/jobs")).hasStatusOk() + .apply(document("quartz/jobs", responseFields(fieldWithPath("groups").description("Job groups keyed by name."), fieldWithPath("groups.*.jobs").description("An array of job names.")))); } @@ -206,9 +203,8 @@ void quartzJobs() throws Exception { @Test void quartzTriggers() throws Exception { mockTriggers(cronTrigger, simpleTrigger, calendarIntervalTrigger, dailyTimeIntervalTrigger); - this.mockMvc.perform(get("/actuator/quartz/triggers")) - .andExpect(status().isOk()) - .andDo(document("quartz/triggers", + assertThat(this.mvc.get().uri("/actuator/quartz/triggers")).hasStatusOk() + .apply(document("quartz/triggers", responseFields(fieldWithPath("groups").description("Trigger groups keyed by name."), fieldWithPath("groups.*.paused").description("Whether this trigger group is paused."), fieldWithPath("groups.*.triggers").description("An array of trigger names.")))); @@ -217,9 +213,8 @@ void quartzTriggers() throws Exception { @Test void quartzJobGroup() throws Exception { mockJobs(jobOne, jobTwo, jobThree); - this.mockMvc.perform(get("/actuator/quartz/jobs/samples")) - .andExpect(status().isOk()) - .andDo(document("quartz/job-group", responseFields(fieldWithPath("group").description("Name of the group."), + assertThat(this.mvc.get().uri("/actuator/quartz/jobs/samples")).hasStatusOk() + .apply(document("quartz/job-group", responseFields(fieldWithPath("group").description("Name of the group."), fieldWithPath("jobs").description("Job details keyed by name."), fieldWithPath("jobs.*.className").description("Fully qualified name of the job implementation.")))); } @@ -250,9 +245,8 @@ void quartzTriggerGroup() throws Exception { given(customTrigger.getPreviousFireTime()).willReturn(fromUtc("2020-07-14T16:00:00Z")); given(customTrigger.getNextFireTime()).willReturn(fromUtc("2021-07-14T16:00:00Z")); mockTriggers(cron, simple, calendarInterval, tueThuTrigger, customTrigger); - this.mockMvc.perform(get("/actuator/quartz/triggers/tests")) - .andExpect(status().isOk()) - .andDo(document("quartz/trigger-group", + assertThat(this.mvc.get().uri("/actuator/quartz/triggers/tests")).hasStatusOk() + .apply(document("quartz/trigger-group", responseFields(fieldWithPath("group").description("Name of the group."), fieldWithPath("paused").description("Whether the group is paused."), fieldWithPath("triggers.cron").description("Cron triggers keyed by name, if any."), @@ -281,9 +275,8 @@ void quartzJob() throws Exception { mockTriggers(firstTrigger, secondTrigger); given(this.scheduler.getTriggersOfJob(jobOne.getKey())) .willAnswer((invocation) -> List.of(firstTrigger, secondTrigger)); - this.mockMvc.perform(get("/actuator/quartz/jobs/samples/jobOne")) - .andExpect(status().isOk()) - .andDo(document("quartz/job-details", responseFields( + assertThat(this.mvc.get().uri("/actuator/quartz/jobs/samples/jobOne")).hasStatusOk() + .apply(document("quartz/job-details", responseFields( fieldWithPath("group").description("Name of the group."), fieldWithPath("name").description("Name of the job."), fieldWithPath("description").description("Description of the job, if any."), @@ -301,9 +294,8 @@ void quartzJob() throws Exception { @Test void quartzTriggerCommon() throws Exception { setupTriggerDetails(cronTrigger.getTriggerBuilder(), TriggerState.NORMAL); - this.mockMvc.perform(get("/actuator/quartz/triggers/samples/example")) - .andExpect(status().isOk()) - .andDo(document("quartz/trigger-details-common", responseFields(commonCronDetails).and(subsectionWithPath( + assertThat(this.mvc.get().uri("/actuator/quartz/triggers/samples/example")).hasStatusOk() + .apply(document("quartz/trigger-details-common", responseFields(commonCronDetails).and(subsectionWithPath( "calendarInterval") .description( "Calendar time interval trigger details, if any. Present when `type` is `calendarInterval`.") @@ -330,9 +322,8 @@ void quartzTriggerCommon() throws Exception { @Test void quartzTriggerCron() throws Exception { setupTriggerDetails(cronTrigger.getTriggerBuilder(), TriggerState.NORMAL); - this.mockMvc.perform(get("/actuator/quartz/triggers/samples/example")) - .andExpect(status().isOk()) - .andDo(document("quartz/trigger-details-cron", + assertThat(this.mvc.get().uri("/actuator/quartz/triggers/samples/example")).hasStatusOk() + .apply(document("quartz/trigger-details-cron", relaxedResponseFields(fieldWithPath("cron").description("Cron trigger specific details.")) .andWithPrefix("cron.", cronTriggerSummary))); } @@ -340,9 +331,8 @@ void quartzTriggerCron() throws Exception { @Test void quartzTriggerSimple() throws Exception { setupTriggerDetails(simpleTrigger.getTriggerBuilder(), TriggerState.NORMAL); - this.mockMvc.perform(get("/actuator/quartz/triggers/samples/example")) - .andExpect(status().isOk()) - .andDo(document("quartz/trigger-details-simple", + assertThat(this.mvc.get().uri("/actuator/quartz/triggers/samples/example")).hasStatusOk() + .apply(document("quartz/trigger-details-simple", relaxedResponseFields(fieldWithPath("simple").description("Simple trigger specific details.")) .andWithPrefix("simple.", simpleTriggerSummary) .and(repeatCount("simple."), timesTriggered("simple.")))); @@ -351,9 +341,8 @@ void quartzTriggerSimple() throws Exception { @Test void quartzTriggerCalendarInterval() throws Exception { setupTriggerDetails(calendarIntervalTrigger.getTriggerBuilder(), TriggerState.NORMAL); - this.mockMvc.perform(get("/actuator/quartz/triggers/samples/example")) - .andExpect(status().isOk()) - .andDo(document("quartz/trigger-details-calendar-interval", + assertThat(this.mvc.get().uri("/actuator/quartz/triggers/samples/example")).hasStatusOk() + .apply(document("quartz/trigger-details-calendar-interval", relaxedResponseFields(fieldWithPath("calendarInterval") .description("Calendar interval trigger specific details.")) .andWithPrefix("calendarInterval.", calendarIntervalTriggerSummary) @@ -368,9 +357,8 @@ void quartzTriggerCalendarInterval() throws Exception { @Test void quartzTriggerDailyTimeInterval() throws Exception { setupTriggerDetails(dailyTimeIntervalTrigger.getTriggerBuilder(), TriggerState.PAUSED); - this.mockMvc.perform(get("/actuator/quartz/triggers/samples/example")) - .andExpect(status().isOk()) - .andDo(document("quartz/trigger-details-daily-time-interval", + assertThat(this.mvc.get().uri("/actuator/quartz/triggers/samples/example")).hasStatusOk() + .apply(document("quartz/trigger-details-daily-time-interval", relaxedResponseFields(fieldWithPath("dailyTimeInterval") .description("Daily time interval trigger specific details.")) .andWithPrefix("dailyTimeInterval.", dailyTimeIntervalTriggerSummary) @@ -391,9 +379,8 @@ void quartzTriggerCustom() throws Exception { given(trigger.getNextFireTime()).willReturn(fromUtc("2020-12-07T03:00:00Z")); given(this.scheduler.getTriggerState(trigger.getKey())).willReturn(TriggerState.NORMAL); mockTriggers(trigger); - this.mockMvc.perform(get("/actuator/quartz/triggers/samples/example")) - .andExpect(status().isOk()) - .andDo(document("quartz/trigger-details-custom", + assertThat(this.mvc.get().uri("/actuator/quartz/triggers/samples/example")).hasStatusOk() + .apply(document("quartz/trigger-details-custom", relaxedResponseFields(fieldWithPath("custom").description("Custom trigger specific details.")) .andWithPrefix("custom.", customTriggerSummary))); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/SbomEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/SbomEndpointDocumentationTests.java new file mode 100644 index 000000000000..5fb4e5b54124 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/SbomEndpointDocumentationTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.sbom.SbomEndpoint; +import org.springframework.boot.actuate.sbom.SbomEndpointWebExtension; +import org.springframework.boot.actuate.sbom.SbomProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.io.ResourceLoader; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; + +/** + * Tests for generating documentation describing the {@link SbomEndpoint}. + * + * @author Moritz Halbritter + */ +class SbomEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { + + @Test + void sbom() { + assertThat(this.mvc.get().uri("/actuator/sbom")).hasStatusOk() + .apply(MockMvcRestDocumentation.document("sbom", + responseFields(fieldWithPath("ids").description("An array of available SBOM ids.")))); + } + + @Test + void sboms() { + assertThat(this.mvc.get().uri("/actuator/sbom/application")).hasStatusOk() + .apply(MockMvcRestDocumentation.document("sbom/id")); + } + + @Configuration(proxyBeanMethods = false) + @Import(BaseDocumentationConfiguration.class) + static class TestConfiguration { + + @Bean + SbomProperties sbomProperties() { + SbomProperties properties = new SbomProperties(); + properties.getApplication().setLocation("classpath:sbom/cyclonedx.json"); + return properties; + } + + @Bean + SbomEndpoint endpoint(SbomProperties properties, ResourceLoader resourceLoader) { + return new SbomEndpoint(properties, resourceLoader); + } + + @Bean + SbomEndpointWebExtension sbomEndpointWebExtension(SbomEndpoint endpoint, SbomProperties properties) { + return new SbomEndpointWebExtension(endpoint, properties); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ScheduledTasksEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ScheduledTasksEndpointDocumentationTests.java index cd757aa6cd51..f195cb3a2053 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ScheduledTasksEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ScheduledTasksEndpointDocumentationTests.java @@ -34,13 +34,12 @@ import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskHolder; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; import static org.springframework.restdocs.operation.preprocess.Preprocessors.replacePattern; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing the {@link ScheduledTasksEndpoint}. @@ -50,10 +49,9 @@ class ScheduledTasksEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { @Test - void scheduledTasks() throws Exception { - this.mockMvc.perform(get("/actuator/scheduledtasks")) - .andExpect(status().isOk()) - .andDo(document("scheduled-tasks", + void scheduledTasks() { + assertThat(this.mvc.get().uri("/actuator/scheduledtasks")).hasStatusOk() + .apply(document("scheduled-tasks", preprocessResponse(replacePattern( Pattern.compile("org.*\\.ScheduledTasksEndpointDocumentationTests\\$TestConfiguration"), "com.example.Processor")), diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/SessionsEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/SessionsEndpointDocumentationTests.java index c92c522005bf..012a2acb9ca2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/SessionsEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/SessionsEndpointDocumentationTests.java @@ -26,16 +26,18 @@ import org.springframework.boot.actuate.context.ShutdownEndpoint; import org.springframework.boot.actuate.session.SessionsEndpoint; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; import org.springframework.restdocs.payload.FieldDescriptor; import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.MapSession; import org.springframework.session.Session; import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; @@ -43,9 +45,6 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing the {@link ShutdownEndpoint}. @@ -73,37 +72,35 @@ class SessionsEndpointDocumentationTests extends MockMvcEndpointDocumentationTes .description("Maximum permitted period of inactivity, in seconds, before the session will expire."), fieldWithPath("expired").description("Whether the session has expired.")); - @MockBean + @MockitoBean private FindByIndexNameSessionRepository sessionRepository; @Test - void sessionsForUsername() throws Exception { + void sessionsForUsername() { Map sessions = new HashMap<>(); sessions.put(sessionOne.getId(), sessionOne); sessions.put(sessionTwo.getId(), sessionTwo); sessions.put(sessionThree.getId(), sessionThree); given(this.sessionRepository.findByPrincipalName("alice")).willReturn(sessions); - this.mockMvc.perform(get("/actuator/sessions").param("username", "alice")) - .andExpect(status().isOk()) - .andDo(document("sessions/username", + assertThat(this.mvc.get().uri("/actuator/sessions").param("username", "alice")).hasStatusOk() + .apply(document("sessions/username", responseFields(fieldWithPath("sessions").description("Sessions for the given username.")) .andWithPrefix("sessions.[].", sessionFields), queryParameters(parameterWithName("username").description("Name of the user.")))); } @Test - void sessionWithId() throws Exception { + void sessionWithId() { given(this.sessionRepository.findById(sessionTwo.getId())).willReturn(sessionTwo); - this.mockMvc.perform(get("/actuator/sessions/{id}", sessionTwo.getId())) - .andExpect(status().isOk()) - .andDo(document("sessions/id", responseFields(sessionFields))); + assertThat(this.mvc.get().uri("/actuator/sessions/{id}", sessionTwo.getId())).hasStatusOk() + .apply(document("sessions/id", responseFields(sessionFields))); } @Test - void deleteASession() throws Exception { - this.mockMvc.perform(delete("/actuator/sessions/{id}", sessionTwo.getId())) - .andExpect(status().isNoContent()) - .andDo(document("sessions/delete")); + void deleteASession() { + assertThat(this.mvc.delete().uri("/actuator/sessions/{id}", sessionTwo.getId())) + .hasStatus(HttpStatus.NO_CONTENT) + .apply(document("sessions/delete")); then(this.sessionRepository).should().deleteById(sessionTwo.getId()); } @@ -124,7 +121,7 @@ static class TestConfiguration { @Bean SessionsEndpoint endpoint(FindByIndexNameSessionRepository sessionRepository) { - return new SessionsEndpoint(sessionRepository); + return new SessionsEndpoint(sessionRepository, sessionRepository); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ShutdownEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ShutdownEndpointDocumentationTests.java index 36e5d3fdbf69..e2d55c40682b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ShutdownEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ShutdownEndpointDocumentationTests.java @@ -25,10 +25,9 @@ import org.springframework.context.annotation.Import; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing the {@link ShutdownEndpoint}. @@ -38,10 +37,9 @@ class ShutdownEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { @Test - void shutdown() throws Exception { - this.mockMvc.perform(post("/actuator/shutdown")) - .andExpect(status().isOk()) - .andDo(MockMvcRestDocumentation.document("shutdown", responseFields( + void shutdown() { + assertThat(this.mvc.post().uri("/actuator/shutdown")).hasStatusOk() + .apply(MockMvcRestDocumentation.document("shutdown", responseFields( fieldWithPath("message").description("Message describing the result of the request.")))); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/StartupEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/StartupEndpointDocumentationTests.java index 85e5a6e7a019..28d4f5bd29a5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/StartupEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/StartupEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,11 +30,9 @@ import org.springframework.restdocs.payload.JsonFieldType; import org.springframework.restdocs.payload.PayloadDocumentation; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing {@link StartupEndpoint}. @@ -55,17 +53,15 @@ void appendSampleStartupSteps(@Autowired BufferingApplicationStartup application } @Test - void startupSnapshot() throws Exception { - this.mockMvc.perform(get("/actuator/startup")) - .andExpect(status().isOk()) - .andDo(document("startup-snapshot", PayloadDocumentation.responseFields(responseFields()))); + void startupSnapshot() { + assertThat(this.mvc.get().uri("/actuator/startup")).hasStatusOk() + .apply(document("startup-snapshot", PayloadDocumentation.responseFields(responseFields()))); } @Test - void startup() throws Exception { - this.mockMvc.perform(post("/actuator/startup")) - .andExpect(status().isOk()) - .andDo(document("startup", PayloadDocumentation.responseFields(responseFields()))); + void startup() { + assertThat(this.mvc.post().uri("/actuator/startup")).hasStatusOk() + .apply(document("startup", PayloadDocumentation.responseFields(responseFields()))); } private FieldDescriptor[] responseFields() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ThreadDumpEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ThreadDumpEndpointDocumentationTests.java index afed23b87759..d07911d0d033 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ThreadDumpEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ThreadDumpEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,12 +31,11 @@ import org.springframework.restdocs.operation.preprocess.ContentModifyingOperationPreprocessor; import org.springframework.restdocs.payload.JsonFieldType; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for generating documentation describing {@link ThreadDumpEndpoint}. @@ -46,7 +45,7 @@ class ThreadDumpEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { @Test - void jsonThreadDump() throws Exception { + void jsonThreadDump() { ReentrantLock lock = new ReentrantLock(); CountDownLatch latch = new CountDownLatch(1); new Thread(() -> { @@ -63,9 +62,8 @@ void jsonThreadDump() throws Exception { Thread.currentThread().interrupt(); } }).start(); - this.mockMvc.perform(get("/actuator/threaddump").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andDo(MockMvcRestDocumentation + assertThat(this.mvc.get().uri("/actuator/threaddump").accept(MediaType.APPLICATION_JSON)).hasStatusOk() + .apply(MockMvcRestDocumentation .document("threaddump/json", preprocessResponse(limit("threads")), responseFields( fieldWithPath("threads").description("JVM's threads."), fieldWithPath("threads.[].blockedCount") @@ -178,10 +176,9 @@ void jsonThreadDump() throws Exception { } @Test - void textThreadDump() throws Exception { - this.mockMvc.perform(get("/actuator/threaddump").accept(MediaType.TEXT_PLAIN)) - .andExpect(status().isOk()) - .andDo(MockMvcRestDocumentation.document("threaddump/text", + void textThreadDump() { + assertThat(this.mvc.get().uri("/actuator/threaddump").accept(MediaType.TEXT_PLAIN)).hasStatusOk() + .apply(MockMvcRestDocumentation.document("threaddump/text", preprocessResponse(new ContentModifyingOperationPreprocessor((bytes, mediaType) -> { String content = new String(bytes, StandardCharsets.UTF_8); int mainThreadIndex = content.indexOf("\"main\" - Thread"); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfigurationReflectionTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfigurationReflectionTests.java deleted file mode 100644 index c6100f971b7b..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfigurationReflectionTests.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.health; - -import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfigurationReflectionTests.TestHealthIndicator; -import org.springframework.boot.actuate.health.AbstractHealthIndicator; -import org.springframework.boot.actuate.health.Health.Builder; -import org.springframework.boot.actuate.health.HealthContributor; - -/** - * Tests for {@link CompositeHealthContributorConfiguration} using reflection to create - * indicator instances. - * - * @author Phillip Webb - */ -@SuppressWarnings("removal") -@Deprecated(since = "3.0.0", forRemoval = true) -class CompositeHealthContributorConfigurationReflectionTests - extends AbstractCompositeHealthContributorConfigurationTests { - - @Override - protected AbstractCompositeHealthContributorConfiguration newComposite() { - return new ReflectiveTestCompositeHealthContributorConfiguration(); - } - - static class ReflectiveTestCompositeHealthContributorConfiguration - extends CompositeHealthContributorConfiguration { - - } - - static class TestHealthIndicator extends AbstractHealthIndicator { - - TestHealthIndicator(TestBean testBean) { - } - - @Override - protected void doHealthCheck(Builder builder) throws Exception { - builder.up(); - } - - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfigurationReflectionTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfigurationReflectionTests.java deleted file mode 100644 index 183a3c7bd3a7..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfigurationReflectionTests.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.health; - -import reactor.core.publisher.Mono; - -import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfigurationReflectionTests.TestReactiveHealthIndicator; -import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.Health.Builder; -import org.springframework.boot.actuate.health.ReactiveHealthContributor; - -/** - * Tests for {@link CompositeReactiveHealthContributorConfiguration} using reflection to - * create indicator instances. - * - * @author Phillip Webb - */ -@SuppressWarnings("removal") -@Deprecated(since = "3.0.0", forRemoval = true) -class CompositeReactiveHealthContributorConfigurationReflectionTests extends - AbstractCompositeHealthContributorConfigurationTests { - - @Override - protected AbstractCompositeHealthContributorConfiguration newComposite() { - return new TestCompositeReactiveHealthContributorConfiguration(); - } - - static class TestCompositeReactiveHealthContributorConfiguration - extends CompositeReactiveHealthContributorConfiguration { - - } - - static class TestReactiveHealthIndicator extends AbstractReactiveHealthIndicator { - - TestReactiveHealthIndicator(TestBean testBean) { - } - - @Override - protected Mono doHealthCheck(Builder builder) { - return Mono.just(builder.up().build()); - } - - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthContributorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthContributorAutoConfigurationTests.java deleted file mode 100644 index 4ed923d5041f..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthContributorAutoConfigurationTests.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.influx; - -import org.influxdb.InfluxDB; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; -import org.springframework.boot.actuate.influx.InfluxDbHealthIndicator; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link InfluxDbHealthContributorAutoConfiguration}. - * - * @author Eddú Meléndez - */ -class InfluxDbHealthContributorAutoConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withBean(InfluxDB.class, () -> mock(InfluxDB.class)) - .withConfiguration(AutoConfigurations.of(InfluxDbHealthContributorAutoConfiguration.class, - HealthContributorAutoConfiguration.class)); - - @Test - void runShouldCreateIndicator() { - this.contextRunner.run((context) -> assertThat(context).hasSingleBean(InfluxDbHealthIndicator.class)); - } - - @Test - void runWhenDisabledShouldNotCreateIndicator() { - this.contextRunner.withPropertyValues("management.health.influxdb.enabled:false") - .run((context) -> assertThat(context).doesNotHaveBean(InfluxDbHealthIndicator.class)); - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/info/InfoContributorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/info/InfoContributorAutoConfigurationTests.java index 62801fdd6415..c9e5d73b5bdb 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/info/InfoContributorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/info/InfoContributorAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,11 +28,13 @@ import org.springframework.boot.actuate.info.InfoContributor; import org.springframework.boot.actuate.info.JavaInfoContributor; import org.springframework.boot.actuate.info.OsInfoContributor; +import org.springframework.boot.actuate.info.ProcessInfoContributor; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.info.BuildProperties; import org.springframework.boot.info.GitProperties; import org.springframework.boot.info.JavaInfo; import org.springframework.boot.info.OsInfo; +import org.springframework.boot.info.ProcessInfo; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -164,6 +166,16 @@ void osInfoContributor() { }); } + @Test + void processInfoContributor() { + this.contextRunner.withPropertyValues("management.info.process.enabled=true").run((context) -> { + assertThat(context).hasSingleBean(ProcessInfoContributor.class); + Map content = invokeContributor(context.getBean(ProcessInfoContributor.class)); + assertThat(content).containsKey("process"); + assertThat(content.get("process")).isInstanceOf(ProcessInfo.class); + }); + } + private Map invokeContributor(InfoContributor contributor) { Info.Builder builder = new Info.Builder(); contributor.contribute(builder); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/ControllerEndpointWebFluxIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/ControllerEndpointWebFluxIntegrationTests.java index 884d7bda461e..eb322c827bba 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/ControllerEndpointWebFluxIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/ControllerEndpointWebFluxIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,6 +45,7 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") class ControllerEndpointWebFluxIntegrationTests { private AnnotationConfigReactiveWebApplicationContext context; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/ControllerEndpointWebMvcIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/ControllerEndpointWebMvcIntegrationTests.java index 7c9c7b8f93c2..8658090dc564 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/ControllerEndpointWebMvcIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/ControllerEndpointWebMvcIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,19 +37,17 @@ import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebApplicationContext; import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.mock.web.MockServletContext; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.test.context.TestSecurityContextHolder; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import org.springframework.test.web.servlet.setup.MockMvcConfigurer; import org.springframework.web.bind.annotation.GetMapping; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Integration tests for the Actuator's MVC {@link ControllerEndpoint controller @@ -69,16 +67,16 @@ void close() { } @Test - void endpointsAreSecureByDefault() throws Exception { + void endpointsAreSecureByDefault() { this.context = new AnnotationConfigServletWebApplicationContext(); this.context.register(SecureConfiguration.class, ExampleController.class); - MockMvc mockMvc = createSecureMockMvc(); - mockMvc.perform(get("/actuator/example").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isUnauthorized()); + MockMvcTester mvc = createSecureMockMvcTester(); + assertThat(mvc.get().uri("/actuator/example").accept(MediaType.APPLICATION_JSON)) + .hasStatus(HttpStatus.UNAUTHORIZED); } @Test - void endpointsCanBeAccessed() throws Exception { + void endpointsCanBeAccessed() { TestSecurityContextHolder.getContext() .setAuthentication(new TestingAuthenticationToken("user", "N/A", "ROLE_ACTUATOR")); this.context = new AnnotationConfigServletWebApplicationContext(); @@ -86,22 +84,23 @@ void endpointsCanBeAccessed() throws Exception { TestPropertyValues .of("management.endpoints.web.base-path:/management", "management.endpoints.web.exposure.include=*") .applyTo(this.context); - MockMvc mockMvc = createSecureMockMvc(); - mockMvc.perform(get("/management/example")).andExpect(status().isOk()); + MockMvcTester mvc = createSecureMockMvcTester(); + assertThat(mvc.get().uri("/management/example")).hasStatusOk(); } - private MockMvc createSecureMockMvc() { - return doCreateMockMvc(springSecurity()); + private MockMvcTester createSecureMockMvcTester() { + return doCreateMockMvcTester(springSecurity()); } - private MockMvc doCreateMockMvc(MockMvcConfigurer... configurers) { + private MockMvcTester doCreateMockMvcTester(MockMvcConfigurer... configurers) { this.context.setServletContext(new MockServletContext()); this.context.refresh(); - DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.context); - for (MockMvcConfigurer configurer : configurers) { - builder.apply(configurer); - } - return builder.build(); + return MockMvcTester.from(this.context, (builder) -> { + for (MockMvcConfigurer configurer : configurers) { + builder.apply(configurer); + } + return builder.build(); + }); } @ImportAutoConfiguration({ JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, @@ -121,6 +120,7 @@ static class SecureConfiguration { } @RestControllerEndpoint(id = "example") + @SuppressWarnings("removal") static class ExampleController { @GetMapping("/") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JerseyEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JerseyEndpointIntegrationTests.java index f027c7f21ec4..83d2c64cf689 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JerseyEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JerseyEndpointIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -161,11 +161,13 @@ private Class[] getAutoconfigurations(Class... additional) { } @ControllerEndpoint(id = "controller") + @SuppressWarnings("removal") static class TestControllerEndpoint { } @RestControllerEndpoint(id = "restcontroller") + @SuppressWarnings("removal") static class TestRestControllerEndpoint { } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebEndpointsAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebEndpointsAutoConfigurationIntegrationTests.java index f2d6c56aca3b..c131bd263a4e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebEndpointsAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebEndpointsAutoConfigurationIntegrationTests.java @@ -19,6 +19,8 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration; import org.springframework.boot.actuate.health.HealthEndpointWebExtension; import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -40,6 +42,7 @@ import org.springframework.boot.context.annotation.UserConfigurations; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import static org.assertj.core.api.Assertions.assertThat; @@ -57,6 +60,7 @@ void healthEndpointWebExtensionIsAutoConfigured() { } @Test + @ClassPathExclusions({ "spring-security-oauth2-client-*.jar", "spring-security-oauth2-resource-server-*.jar" }) void healthEndpointReactiveWebExtensionIsAutoConfigured() { reactiveWebRunner() .run((context) -> assertThat(context).hasSingleBean(ReactiveHealthEndpointWebExtension.class)); @@ -80,7 +84,8 @@ private ReactiveWebApplicationContextRunner reactiveWebRunner() { MongoReactiveAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class, RepositoryRestMvcAutoConfiguration.class, HazelcastAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class, RedisAutoConfiguration.class, - RedisRepositoriesAutoConfiguration.class }) + RedisRepositoriesAutoConfiguration.class, BraveAutoConfiguration.class, + OpenTelemetryAutoConfiguration.class }) @SpringBootConfiguration static class WebEndpointTestApplication { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebFluxEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebFluxEndpointIntegrationTests.java index b0fe7468ae04..5adda29dd9dc 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebFluxEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebFluxEndpointIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -108,11 +108,13 @@ private WebTestClient createWebTestClient(ApplicationContext context) { } @ControllerEndpoint(id = "controller") + @SuppressWarnings("removal") static class TestControllerEndpoint { } @RestControllerEndpoint(id = "restcontroller") + @SuppressWarnings("removal") static class TestRestControllerEndpoint { } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointCorsIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointCorsIntegrationTests.java index 9a0ee8a1b411..1cfc54744a28 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointCorsIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointCorsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.boot.actuate.autoconfigure.integrationtest; +import org.assertj.core.api.ThrowingConsumer; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration; @@ -32,14 +33,12 @@ import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.http.HttpHeaders; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.ResultActions; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.http.HttpStatus; +import org.springframework.test.web.servlet.assertj.MockMvcTester; +import org.springframework.test.web.servlet.assertj.MvcTestResult; import org.springframework.web.context.WebApplicationContext; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for the MVC actuator endpoints' CORS support @@ -60,21 +59,22 @@ class WebMvcEndpointCorsIntegrationTests { @Test void corsIsDisabledByDefault() { - this.contextRunner.run(withMockMvc((mockMvc) -> mockMvc - .perform(options("/actuator/beans").header("Origin", "foo.example.com") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")) - .andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)))); + this.contextRunner.run(withMockMvc((mvc) -> assertThat(mvc.options() + .uri("/actuator/beans") + .header("Origin", "foo.example.com") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")) + .doesNotContainHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN))); } @Test void settingAllowedOriginsEnablesCors() { this.contextRunner.withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com") - .run(withMockMvc((mockMvc) -> { - mockMvc - .perform(options("/actuator/beans").header("Origin", "bar.example.com") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")) - .andExpect(status().isForbidden()); - performAcceptedCorsRequest(mockMvc); + .run(withMockMvc((mvc) -> { + assertThat(mvc.options() + .uri("/actuator/beans") + .header("Origin", "bar.example.com") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).hasStatus(HttpStatus.FORBIDDEN); + performAcceptedCorsRequest(mvc); })); } @@ -83,20 +83,22 @@ void settingAllowedOriginPatternsEnablesCors() { this.contextRunner .withPropertyValues("management.endpoints.web.cors.allowed-origin-patterns:*.example.com", "management.endpoints.web.cors.allow-credentials:true") - .run(withMockMvc((mockMvc) -> { - mockMvc - .perform(options("/actuator/beans").header("Origin", "bar.example.org") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")) - .andExpect(status().isForbidden()); - performAcceptedCorsRequest(mockMvc); + .run(withMockMvc((mvc) -> { + assertThat(mvc.options() + .uri("/actuator/beans") + .header("Origin", "bar.example.org") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).hasStatus(HttpStatus.FORBIDDEN); + performAcceptedCorsRequest(mvc); })); } @Test void maxAgeDefaultsTo30Minutes() { this.contextRunner.withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com") - .run(withMockMvc((mockMvc) -> performAcceptedCorsRequest(mockMvc) - .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "1800")))); + .run(withMockMvc((mvc) -> { + MvcTestResult result = performAcceptedCorsRequest(mvc); + assertThat(result).hasHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "1800"); + })); } @Test @@ -104,20 +106,20 @@ void maxAgeCanBeConfigured() { this.contextRunner .withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com", "management.endpoints.web.cors.max-age: 2400") - .run(withMockMvc((mockMvc) -> performAcceptedCorsRequest(mockMvc) - .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "2400")))); + .run(withMockMvc((mvc) -> { + MvcTestResult result = performAcceptedCorsRequest(mvc); + assertThat(result).hasHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "2400"); + })); } @Test void requestsWithDisallowedHeadersAreRejected() { this.contextRunner.withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com") - .run(withMockMvc((mockMvc) -> - - mockMvc - .perform(options("/actuator/beans").header("Origin", "foo.example.com") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Alpha")) - .andExpect(status().isForbidden()))); + .run(withMockMvc((mvc) -> assertThat(mvc.options() + .uri("/actuator/beans") + .header("Origin", "foo.example.com") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Alpha")).hasStatus(HttpStatus.FORBIDDEN))); } @Test @@ -125,21 +127,22 @@ void allowedHeadersCanBeConfigured() { this.contextRunner .withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com", "management.endpoints.web.cors.allowed-headers:Alpha,Bravo") - .run(withMockMvc((mockMvc) -> mockMvc - .perform(options("/actuator/beans").header("Origin", "foo.example.com") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Alpha")) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Alpha")))); + .run(withMockMvc((mvc) -> assertThat(mvc.options() + .uri("/actuator/beans") + .header("Origin", "foo.example.com") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Alpha")).hasStatusOk() + .headers() + .hasValue(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Alpha"))); } @Test void requestsWithDisallowedMethodsAreRejected() { this.contextRunner.withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com") - .run(withMockMvc((mockMvc) -> mockMvc - .perform(options("/actuator/beans").header(HttpHeaders.ORIGIN, "foo.example.com") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "PATCH")) - .andExpect(status().isForbidden()))); + .run(withMockMvc((mvc) -> assertThat(mvc.options() + .uri("/actuator/beans") + .header(HttpHeaders.ORIGIN, "foo.example.com") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "PATCH")).hasStatus(HttpStatus.FORBIDDEN))); } @Test @@ -147,11 +150,12 @@ void allowedMethodsCanBeConfigured() { this.contextRunner .withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com", "management.endpoints.web.cors.allowed-methods:GET,HEAD") - .run(withMockMvc((mockMvc) -> mockMvc - .perform(options("/actuator/beans").header(HttpHeaders.ORIGIN, "foo.example.com") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD")) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,HEAD")))); + .run(withMockMvc((mvc) -> assertThat(mvc.options() + .uri("/actuator/beans") + .header(HttpHeaders.ORIGIN, "foo.example.com") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD")).hasStatusOk() + .headers() + .hasValue(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,HEAD"))); } @Test @@ -159,8 +163,10 @@ void credentialsCanBeAllowed() { this.contextRunner .withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com", "management.endpoints.web.cors.allow-credentials:true") - .run(withMockMvc((mockMvc) -> performAcceptedCorsRequest(mockMvc) - .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true")))); + .run(withMockMvc((mvc) -> { + MvcTestResult result = performAcceptedCorsRequest(mvc); + assertThat(result).hasHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); + })); } @Test @@ -168,31 +174,28 @@ void credentialsCanBeDisabled() { this.contextRunner .withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com", "management.endpoints.web.cors.allow-credentials:false") - .run(withMockMvc((mockMvc) -> performAcceptedCorsRequest(mockMvc) - .andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)))); - } - - private ContextConsumer withMockMvc(MockMvcConsumer mockMvc) { - return (context) -> mockMvc.accept(MockMvcBuilders.webAppContextSetup(context).build()); + .run(withMockMvc((mvc) -> { + MvcTestResult result = performAcceptedCorsRequest(mvc); + assertThat(result).doesNotContainHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS); + })); } - private ResultActions performAcceptedCorsRequest(MockMvc mockMvc) throws Exception { - return performAcceptedCorsRequest(mockMvc, "/actuator/beans"); + private ContextConsumer withMockMvc(ThrowingConsumer mvc) { + return (context) -> mvc.accept(MockMvcTester.from(context)); } - private ResultActions performAcceptedCorsRequest(MockMvc mockMvc, String url) throws Exception { - return mockMvc - .perform(options(url).header(HttpHeaders.ORIGIN, "foo.example.com") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")) - .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "foo.example.com")) - .andExpect(status().isOk()); + private MvcTestResult performAcceptedCorsRequest(MockMvcTester mvc) { + return performAcceptedCorsRequest(mvc, "/actuator/beans"); } - @FunctionalInterface - interface MockMvcConsumer { - - void accept(MockMvc mockMvc) throws Exception; - + private MvcTestResult performAcceptedCorsRequest(MockMvcTester mvc, String url) { + MvcTestResult result = mvc.options() + .uri(url) + .header(HttpHeaders.ORIGIN, "foo.example.com") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET") + .exchange(); + assertThat(result).hasStatusOk().hasHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "foo.example.com"); + return result; } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java index 6fb027288cba..981255d65759 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -190,6 +190,7 @@ private boolean isExposed(WebTestClient client, HttpMethod method, String path) } @RestControllerEndpoint(id = "custommvc") + @SuppressWarnings("removal") static class CustomMvcEndpoint { @GetMapping("/") @@ -200,6 +201,7 @@ String main() { } @ServletEndpoint(id = "customservlet") + @SuppressWarnings("removal") static class CustomServletEndpoint implements Supplier { @Override diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointIntegrationTests.java index 6108345a1ccc..fd376a1bffb3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,24 +47,17 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.mock.web.MockServletContext; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.test.context.TestSecurityContextHolder; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import org.springframework.test.web.servlet.setup.MockMvcConfigurer; import org.springframework.web.util.pattern.PathPatternParser; import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.both; -import static org.hamcrest.Matchers.hasKey; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Integration tests for the Actuator's MVC endpoints. @@ -92,25 +85,26 @@ void webMvcEndpointHandlerMappingIsConfiguredWithPathPatternParser() { } @Test - void endpointsAreSecureByDefault() throws Exception { + void endpointsAreSecureByDefault() { this.context = new AnnotationConfigServletWebApplicationContext(); this.context.register(SecureConfiguration.class); - MockMvc mockMvc = createSecureMockMvc(); - mockMvc.perform(get("/actuator/beans").accept(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized()); + MockMvcTester mvc = createSecureMockMvcTester(); + assertThat(mvc.get().uri("/actuator/beans").accept(MediaType.APPLICATION_JSON)) + .hasStatus(HttpStatus.UNAUTHORIZED); } @Test - void endpointsAreSecureByDefaultWithCustomBasePath() throws Exception { + void endpointsAreSecureByDefaultWithCustomBasePath() { this.context = new AnnotationConfigServletWebApplicationContext(); this.context.register(SecureConfiguration.class); TestPropertyValues.of("management.endpoints.web.base-path:/management").applyTo(this.context); - MockMvc mockMvc = createSecureMockMvc(); - mockMvc.perform(get("/management/beans").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isUnauthorized()); + MockMvcTester mvc = createSecureMockMvcTester(); + assertThat(mvc.get().uri("/management/beans").accept(MediaType.APPLICATION_JSON)) + .hasStatus(HttpStatus.UNAUTHORIZED); } @Test - void endpointsAreSecureWithActuatorRoleWithCustomBasePath() throws Exception { + void endpointsAreSecureWithActuatorRoleWithCustomBasePath() { TestSecurityContextHolder.getContext() .setAuthentication(new TestingAuthenticationToken("user", "N/A", "ROLE_ACTUATOR")); this.context = new AnnotationConfigServletWebApplicationContext(); @@ -118,55 +112,54 @@ void endpointsAreSecureWithActuatorRoleWithCustomBasePath() throws Exception { TestPropertyValues .of("management.endpoints.web.base-path:/management", "management.endpoints.web.exposure.include=*") .applyTo(this.context); - MockMvc mockMvc = createSecureMockMvc(); - mockMvc.perform(get("/management/beans")).andExpect(status().isOk()); + MockMvcTester mvc = createSecureMockMvcTester(); + assertThat(mvc.get().uri("/management/beans")).hasStatusOk(); } @Test - void linksAreProvidedToAllEndpointTypes() throws Exception { + void linksAreProvidedToAllEndpointTypes() { this.context = new AnnotationConfigServletWebApplicationContext(); this.context.register(DefaultConfiguration.class, EndpointsConfiguration.class); TestPropertyValues.of("management.endpoints.web.exposure.include=*").applyTo(this.context); - MockMvc mockMvc = doCreateMockMvc(); - mockMvc.perform(get("/actuator").accept("*/*")) - .andExpect(status().isOk()) - .andExpect(jsonPath("_links", - both(hasKey("beans")).and(hasKey("servlet")) - .and(hasKey("restcontroller")) - .and(hasKey("controller")))); + MockMvcTester mvc = doCreateMockMvcTester(); + assertThat(mvc.get().uri("/actuator").accept("*/*")).hasStatusOk() + .bodyJson() + .extractingPath("_links") + .asMap() + .containsKeys("beans", "servlet", "restcontroller", "controller"); } @Test - void linksPageIsNotAvailableWhenDisabled() throws Exception { + void linksPageIsNotAvailableWhenDisabled() { this.context = new AnnotationConfigServletWebApplicationContext(); this.context.register(DefaultConfiguration.class, EndpointsConfiguration.class); TestPropertyValues.of("management.endpoints.web.discovery.enabled=false").applyTo(this.context); - MockMvc mockMvc = doCreateMockMvc(); - mockMvc.perform(get("/actuator").accept("*/*")).andExpect(status().isNotFound()); + MockMvcTester mvc = doCreateMockMvcTester(); + assertThat(mvc.get().uri("/actuator").accept("*/*")).hasStatus(HttpStatus.NOT_FOUND); } @Test - void endpointObjectMapperCanBeApplied() throws Exception { + void endpointObjectMapperCanBeApplied() { this.context = new AnnotationConfigServletWebApplicationContext(); this.context.register(EndpointObjectMapperConfiguration.class, DefaultConfiguration.class); TestPropertyValues.of("management.endpoints.web.exposure.include=*").applyTo(this.context); - MockMvc mockMvc = doCreateMockMvc(); - MvcResult result = mockMvc.perform(get("/actuator/beans")).andExpect(status().isOk()).andReturn(); - assertThat(result.getResponse().getContentAsString()).contains("\"scope\":\"notelgnis\""); + MockMvcTester mvc = doCreateMockMvcTester(); + assertThat(mvc.get().uri("/actuator/beans")).hasStatusOk().bodyText().contains("\"scope\":\"notelgnis\""); } - private MockMvc createSecureMockMvc() { - return doCreateMockMvc(springSecurity()); + private MockMvcTester createSecureMockMvcTester() { + return doCreateMockMvcTester(springSecurity()); } - private MockMvc doCreateMockMvc(MockMvcConfigurer... configurers) { + private MockMvcTester doCreateMockMvcTester(MockMvcConfigurer... configurers) { this.context.setServletContext(new MockServletContext()); this.context.refresh(); - DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.context); - for (MockMvcConfigurer configurer : configurers) { - builder.apply(configurer); - } - return builder.build(); + return MockMvcTester.from(this.context, (builder) -> { + for (MockMvcConfigurer configurer : configurers) { + builder.apply(configurer); + } + return builder.build(); + }); } @ImportAutoConfiguration({ JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, @@ -198,6 +191,7 @@ static class SecureConfiguration { } @ServletEndpoint(id = "servlet") + @SuppressWarnings("removal") static class TestServletEndpoint implements Supplier { @Override @@ -209,11 +203,13 @@ public EndpointServlet get() { } @ControllerEndpoint(id = "controller") + @SuppressWarnings("removal") static class TestControllerEndpoint { } @RestControllerEndpoint(id = "restcontroller") + @SuppressWarnings("removal") static class TestRestControllerEndpoint { } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/OpenTelemetryLoggingAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/OpenTelemetryLoggingAutoConfigurationTests.java new file mode 100644 index 000000000000..00ddf95e3f59 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/OpenTelemetryLoggingAutoConfigurationTests.java @@ -0,0 +1,226 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.logging.opentelemetry; + +import java.util.Collection; +import java.util.concurrent.atomic.AtomicInteger; + +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.logs.LogRecordProcessor; +import io.opentelemetry.sdk.logs.ReadWriteLogRecord; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OpenTelemetryLoggingAutoConfiguration}. + * + * @author Toshiaki Maki + */ +class OpenTelemetryLoggingAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner; + + OpenTelemetryLoggingAutoConfigurationTests() { + this.contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations.of( + org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration.class, + OpenTelemetryLoggingAutoConfiguration.class)); + } + + @Test + void shouldSupplyBeans() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(BatchLogRecordProcessor.class); + assertThat(context).hasSingleBean(SdkLoggerProvider.class); + }); + } + + @ParameterizedTest + @ValueSource(strings = { "io.opentelemetry.sdk.logs", "io.opentelemetry.api" }) + void shouldNotSupplyBeansIfDependencyIsMissing(String packageName) { + this.contextRunner.withClassLoader(new FilteredClassLoader(packageName)).run((context) -> { + assertThat(context).doesNotHaveBean(BatchLogRecordProcessor.class); + assertThat(context).doesNotHaveBean(SdkLoggerProvider.class); + }); + } + + @Test + void shouldBackOffOnCustomBeans() { + this.contextRunner.withUserConfiguration(CustomConfig.class).run((context) -> { + assertThat(context).hasBean("customBatchLogRecordProcessor").hasSingleBean(BatchLogRecordProcessor.class); + assertThat(context.getBeansOfType(LogRecordProcessor.class)).hasSize(1); + assertThat(context).hasBean("customSdkLoggerProvider").hasSingleBean(SdkLoggerProvider.class); + }); + } + + @Test + void shouldAllowMultipleLogRecordExporter() { + this.contextRunner.withUserConfiguration(MultipleLogRecordExporterConfig.class).run((context) -> { + assertThat(context).hasSingleBean(BatchLogRecordProcessor.class); + assertThat(context.getBeansOfType(LogRecordExporter.class)).hasSize(2); + assertThat(context).hasBean("customLogRecordExporter1"); + assertThat(context).hasBean("customLogRecordExporter2"); + }); + } + + @Test + void shouldAllowMultipleLogRecordProcessorInAdditionToBatchLogRecordProcessor() { + this.contextRunner.withUserConfiguration(MultipleLogRecordProcessorConfig.class).run((context) -> { + assertThat(context).hasSingleBean(BatchLogRecordProcessor.class); + assertThat(context).hasSingleBean(SdkLoggerProvider.class); + assertThat(context.getBeansOfType(LogRecordProcessor.class)).hasSize(3); + assertThat(context).hasBean("batchLogRecordProcessor"); + assertThat(context).hasBean("customLogRecordProcessor1"); + assertThat(context).hasBean("customLogRecordProcessor2"); + }); + } + + @Test + void shouldAllowMultipleSdkLoggerProviderBuilderCustomizer() { + this.contextRunner.withUserConfiguration(MultipleSdkLoggerProviderBuilderCustomizerConfig.class) + .run((context) -> { + assertThat(context).hasSingleBean(SdkLoggerProvider.class); + assertThat(context.getBeansOfType(NoopSdkLoggerProviderBuilderCustomizer.class)).hasSize(2); + assertThat(context).hasBean("customSdkLoggerProviderBuilderCustomizer1"); + assertThat(context).hasBean("customSdkLoggerProviderBuilderCustomizer2"); + assertThat(context + .getBean("customSdkLoggerProviderBuilderCustomizer1", NoopSdkLoggerProviderBuilderCustomizer.class) + .called()).isEqualTo(1); + assertThat(context + .getBean("customSdkLoggerProviderBuilderCustomizer2", NoopSdkLoggerProviderBuilderCustomizer.class) + .called()).isEqualTo(1); + }); + } + + @Configuration(proxyBeanMethods = false) + public static class CustomConfig { + + @Bean + public BatchLogRecordProcessor customBatchLogRecordProcessor() { + return BatchLogRecordProcessor.builder(new NoopLogRecordExporter()).build(); + } + + @Bean + public SdkLoggerProvider customSdkLoggerProvider() { + return SdkLoggerProvider.builder().build(); + } + + } + + @Configuration(proxyBeanMethods = false) + public static class MultipleLogRecordExporterConfig { + + @Bean + public LogRecordExporter customLogRecordExporter1() { + return new NoopLogRecordExporter(); + } + + @Bean + public LogRecordExporter customLogRecordExporter2() { + return new NoopLogRecordExporter(); + } + + } + + @Configuration(proxyBeanMethods = false) + public static class MultipleLogRecordProcessorConfig { + + @Bean + public LogRecordProcessor customLogRecordProcessor1() { + return new NoopLogRecordProcessor(); + } + + @Bean + public LogRecordProcessor customLogRecordProcessor2() { + return new NoopLogRecordProcessor(); + } + + } + + @Configuration(proxyBeanMethods = false) + public static class MultipleSdkLoggerProviderBuilderCustomizerConfig { + + @Bean + public SdkLoggerProviderBuilderCustomizer customSdkLoggerProviderBuilderCustomizer1() { + return new NoopSdkLoggerProviderBuilderCustomizer(); + } + + @Bean + public SdkLoggerProviderBuilderCustomizer customSdkLoggerProviderBuilderCustomizer2() { + return new NoopSdkLoggerProviderBuilderCustomizer(); + } + + } + + static class NoopLogRecordExporter implements LogRecordExporter { + + @Override + public CompletableResultCode export(Collection logs) { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + return CompletableResultCode.ofSuccess(); + } + + } + + static class NoopLogRecordProcessor implements LogRecordProcessor { + + @Override + public void onEmit(Context context, ReadWriteLogRecord logRecord) { + + } + + } + + static class NoopSdkLoggerProviderBuilderCustomizer implements SdkLoggerProviderBuilderCustomizer { + + final AtomicInteger called = new AtomicInteger(0); + + @Override + public void customize(SdkLoggerProviderBuilder builder) { + this.called.incrementAndGet(); + } + + int called() { + return this.called.get(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/OtlpLoggingAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/OtlpLoggingAutoConfigurationIntegrationTests.java new file mode 100644 index 000000000000..71b4b55c9dd9 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/OtlpLoggingAutoConfigurationIntegrationTests.java @@ -0,0 +1,128 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.logging.opentelemetry.otlp; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import okio.Buffer; +import okio.GzipSource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.autoconfigure.logging.opentelemetry.OpenTelemetryLoggingAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.ApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link OtlpLoggingAutoConfiguration}. + * + * @author Toshiaki Maki + */ +public class OtlpLoggingAutoConfigurationIntegrationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.application.name=otlp-logs-test", + "management.otlp.logging.headers.Authorization=Bearer my-token") + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class, + OpenTelemetryLoggingAutoConfiguration.class, OtlpLoggingAutoConfiguration.class)); + + private final MockWebServer mockWebServer = new MockWebServer(); + + @BeforeEach + void setUp() throws IOException { + this.mockWebServer.start(); + } + + @AfterEach + void tearDown() throws IOException { + this.mockWebServer.close(); + } + + @Test + void httpLogRecordExporterShouldUseProtobufAndNoCompressionByDefault() { + this.mockWebServer.enqueue(new MockResponse()); + this.contextRunner + .withPropertyValues("management.otlp.logging.endpoint=http://localhost:%d/v1/logs" + .formatted(this.mockWebServer.getPort())) + .run((context) -> { + logMessage(context); + RecordedRequest request = this.mockWebServer.takeRequest(10, TimeUnit.SECONDS); + assertThat(request).isNotNull(); + assertThat(request.getRequestLine()).contains("/v1/logs"); + assertThat(request.getHeader("Content-Type")).isEqualTo("application/x-protobuf"); + assertThat(request.getHeader("Content-Encoding")).isNull(); + assertThat(request.getBodySize()).isPositive(); + try (Buffer body = request.getBody()) { + assertLogMessage(body); + } + }); + } + + @Test + void httpLogRecordExporterCanBeConfiguredToUseGzipCompression() { + this.mockWebServer.enqueue(new MockResponse()); + this.contextRunner + .withPropertyValues("management.otlp.logging.endpoint=http://localhost:%d/v1/logs" + .formatted(this.mockWebServer.getPort()), "management.otlp.logging.compression=gzip") + .run((context) -> { + logMessage(context); + RecordedRequest request = this.mockWebServer.takeRequest(10, TimeUnit.SECONDS); + assertThat(request).isNotNull(); + assertThat(request.getRequestLine()).contains("/v1/logs"); + assertThat(request.getHeader("Content-Type")).isEqualTo("application/x-protobuf"); + assertThat(request.getHeader("Content-Encoding")).isEqualTo("gzip"); + assertThat(request.getBodySize()).isPositive(); + try (Buffer uncompressed = new Buffer(); Buffer body = request.getBody()) { + uncompressed.writeAll(new GzipSource(body)); + assertLogMessage(uncompressed); + } + }); + } + + private static void logMessage(ApplicationContext context) { + SdkLoggerProvider loggerProvider = context.getBean(SdkLoggerProvider.class); + loggerProvider.get("test") + .logRecordBuilder() + .setSeverity(Severity.INFO) + .setSeverityText("INFO") + .setBody("Hello") + .setTimestamp(Instant.now()) + .emit(); + } + + private static void assertLogMessage(Buffer body) { + String string = body.readString(StandardCharsets.UTF_8); + assertThat(string).contains("otlp-logs-test"); + assertThat(string).contains("test"); + assertThat(string).contains("INFO"); + assertThat(string).contains("Hello"); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/OtlpLoggingAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/OtlpLoggingAutoConfigurationTests.java new file mode 100644 index 000000000000..afe42035c0c4 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/opentelemetry/otlp/OtlpLoggingAutoConfigurationTests.java @@ -0,0 +1,132 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.logging.opentelemetry.otlp; + +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; +import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; +import okhttp3.HttpUrl; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import org.springframework.boot.actuate.autoconfigure.logging.opentelemetry.otlp.OtlpLoggingConfigurations.ConnectionDetails.PropertiesOtlpLoggingConnectionDetails; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OtlpLoggingAutoConfiguration}. + * + * @author Toshiaki Maki + */ +class OtlpLoggingAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(OtlpLoggingAutoConfiguration.class)); + + @Test + void shouldNotSupplyBeansIfPropertyIsNotSet() { + this.contextRunner.run((context) -> { + assertThat(context).doesNotHaveBean(OtlpLoggingConnectionDetails.class); + assertThat(context).doesNotHaveBean(OtlpHttpLogRecordExporter.class); + }); + } + + @Test + void shouldSupplyBeans() { + this.contextRunner.withPropertyValues("management.otlp.logging.endpoint=http://localhost:4318/v1/logs") + .run((context) -> { + assertThat(context).hasSingleBean(OtlpLoggingConnectionDetails.class); + OtlpLoggingConnectionDetails connectionDetails = context.getBean(OtlpLoggingConnectionDetails.class); + assertThat(connectionDetails.getEndpoint()).isEqualTo("http://localhost:4318/v1/logs"); + assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class) + .hasSingleBean(LogRecordExporter.class); + }); + } + + @ParameterizedTest + @ValueSource(strings = { "io.opentelemetry.sdk.logs", "io.opentelemetry.api", + "io.opentelemetry.exporter.otlp.http.logs" }) + void shouldNotSupplyBeansIfDependencyIsMissing(String packageName) { + this.contextRunner.withClassLoader(new FilteredClassLoader(packageName)).run((context) -> { + assertThat(context).doesNotHaveBean(OtlpLoggingConnectionDetails.class); + assertThat(context).doesNotHaveBean(OtlpHttpLogRecordExporter.class); + }); + } + + @Test + void shouldBackOffWhenCustomHttpExporterIsDefined() { + this.contextRunner.withUserConfiguration(CustomHttpExporterConfiguration.class) + .run((context) -> assertThat(context).hasBean("customOtlpHttpLogRecordExporter") + .hasSingleBean(LogRecordExporter.class)); + } + + @Test + void shouldBackOffWhenCustomGrpcExporterIsDefined() { + this.contextRunner.withUserConfiguration(CustomGrpcExporterConfiguration.class) + .run((context) -> assertThat(context).hasBean("customOtlpGrpcLogRecordExporter") + .hasSingleBean(LogRecordExporter.class)); + } + + @Test + void shouldBackOffWhenCustomOtlpLogsConnectionDetailsIsDefined() { + this.contextRunner.withUserConfiguration(CustomOtlpLogsConnectionDetails.class).run((context) -> { + assertThat(context).hasSingleBean(OtlpLoggingConnectionDetails.class) + .doesNotHaveBean(PropertiesOtlpLoggingConnectionDetails.class); + OtlpHttpLogRecordExporter otlpHttpLogRecordExporter = context.getBean(OtlpHttpLogRecordExporter.class); + assertThat(otlpHttpLogRecordExporter).extracting("delegate.httpSender.url") + .isEqualTo(HttpUrl.get("https://otel.example.com/v1/logs")); + }); + + } + + @Configuration(proxyBeanMethods = false) + public static class CustomHttpExporterConfiguration { + + @Bean + public OtlpHttpLogRecordExporter customOtlpHttpLogRecordExporter() { + return OtlpHttpLogRecordExporter.builder().build(); + } + + } + + @Configuration(proxyBeanMethods = false) + public static class CustomGrpcExporterConfiguration { + + @Bean + public OtlpGrpcLogRecordExporter customOtlpGrpcLogRecordExporter() { + return OtlpGrpcLogRecordExporter.builder().build(); + } + + } + + @Configuration(proxyBeanMethods = false) + public static class CustomOtlpLogsConnectionDetails { + + @Bean + public OtlpLoggingConnectionDetails customOtlpLogsConnectionDetails() { + return () -> "https://otel.example.com/v1/logs"; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryCustomizerTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryCustomizerTests.java index a24345324bbf..7f0783acd373 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryCustomizerTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryCustomizerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import io.micrometer.atlas.AtlasMeterRegistry; import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.prometheus.PrometheusMeterRegistry; +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.metrics.export.atlas.AtlasMetricsExportAutoConfiguration; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfigurationTests.java new file mode 100644 index 000000000000..5371146e0f42 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfigurationTests.java @@ -0,0 +1,144 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.metrics; + +import io.micrometer.core.aop.CountedAspect; +import io.micrometer.core.aop.MeterTagAnnotationHandler; +import io.micrometer.core.aop.TimedAspect; +import io.micrometer.core.instrument.MeterRegistry; +import org.aspectj.weaver.Advice; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link MetricsAspectsAutoConfiguration}. + * + * @author Jonatan Ivanov + */ +class MetricsAspectsAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().with(MetricsRun.simple()) + .withPropertyValues("management.observations.annotations.enabled=true") + .withConfiguration(AutoConfigurations.of(MetricsAspectsAutoConfiguration.class)); + + @Test + void shouldNotConfigureAspectsByDefault() { + new ApplicationContextRunner().with(MetricsRun.simple()) + .withConfiguration(AutoConfigurations.of(MetricsAspectsAutoConfiguration.class)) + .run((context) -> { + assertThat(context).doesNotHaveBean(CountedAspect.class); + assertThat(context).doesNotHaveBean(TimedAspect.class); + }); + } + + @Test + void shouldConfigureAspectsWithLegacyProperty() { + new ApplicationContextRunner().with(MetricsRun.simple()) + .withConfiguration(AutoConfigurations.of(MetricsAspectsAutoConfiguration.class)) + .withPropertyValues("micrometer.observations.annotations.enabled=true") + .run((context) -> { + assertThat(context).hasSingleBean(CountedAspect.class); + assertThat(context).hasSingleBean(TimedAspect.class); + }); + } + + @Test + void shouldConfigureAspects() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(CountedAspect.class); + assertThat(context).hasSingleBean(TimedAspect.class); + }); + } + + @Test + void shouldConfigureMeterTagAnnotationHandler() { + this.contextRunner.withUserConfiguration(MeterTagAnnotationHandlerConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(CountedAspect.class); + assertThat(ReflectionTestUtils.getField(context.getBean(TimedAspect.class), "meterTagAnnotationHandler")) + .isSameAs(context.getBean(MeterTagAnnotationHandler.class)); + }); + } + + @Test + void shouldNotConfigureAspectsIfMicrometerIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader(MeterRegistry.class)).run((context) -> { + assertThat(context).doesNotHaveBean(CountedAspect.class); + assertThat(context).doesNotHaveBean(TimedAspect.class); + }); + } + + @Test + void shouldNotConfigureAspectsIfAspectjIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader(Advice.class)).run((context) -> { + assertThat(context).doesNotHaveBean(CountedAspect.class); + assertThat(context).doesNotHaveBean(TimedAspect.class); + }); + } + + @Test + void shouldNotConfigureAspectsIfMeterRegistryBeanIsMissing() { + new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(MetricsAspectsAutoConfiguration.class)) + .run((context) -> { + assertThat(context).doesNotHaveBean(MeterRegistry.class); + assertThat(context).doesNotHaveBean(CountedAspect.class); + assertThat(context).doesNotHaveBean(TimedAspect.class); + }); + } + + @Test + void shouldBackOffIfAspectBeansExist() { + this.contextRunner.withUserConfiguration(CustomAspectsConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(CountedAspect.class).hasBean("customCountedAspect"); + assertThat(context).hasSingleBean(TimedAspect.class).hasBean("customTimedAspect"); + }); + } + + @Configuration(proxyBeanMethods = false) + static class CustomAspectsConfiguration { + + @Bean + CountedAspect customCountedAspect(MeterRegistry registry) { + return new CountedAspect(registry); + } + + @Bean + TimedAspect customTimedAspect(MeterRegistry registry) { + return new TimedAspect(registry); + } + + } + + @Configuration(proxyBeanMethods = false) + static class MeterTagAnnotationHandlerConfiguration { + + @Bean + MeterTagAnnotationHandler meterTagAnnotationHandler() { + return new MeterTagAnnotationHandler(null, null); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfigurationTests.java index c04c31435a72..b7f4876bb3fe 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfigurationTests.java @@ -25,6 +25,7 @@ import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration.MeterRegistryCloser; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -40,6 +41,7 @@ * Tests for {@link MetricsAutoConfiguration}. * * @author Andy Wilkinson + * @author Moritz Halbritter */ class MetricsAutoConfigurationTests { @@ -72,6 +74,21 @@ void configuresMeterRegistries() { }); } + @Test + void shouldSupplyMeterRegistryCloser() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(MeterRegistryCloser.class)); + } + + @Test + void meterRegistryCloserShouldCloseRegistryOnShutdown() { + this.contextRunner.withUserConfiguration(MeterRegistryConfiguration.class).run((context) -> { + MeterRegistry meterRegistry = context.getBean(MeterRegistry.class); + assertThat(meterRegistry.isClosed()).isFalse(); + context.close(); + assertThat(meterRegistry.isClosed()).isTrue(); + }); + } + @Configuration(proxyBeanMethods = false) static class CustomClockConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/dynatrace/DynatracePropertiesTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/dynatrace/DynatracePropertiesTests.java index 69f3651bc30b..5a784ead6dd1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/dynatrace/DynatracePropertiesTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/dynatrace/DynatracePropertiesTests.java @@ -38,6 +38,7 @@ void defaultValuesAreConsistent() { assertThat(properties.getV1().getTechnologyType()).isEqualTo(config.technologyType()); assertThat(properties.getV2().isUseDynatraceSummaryInstruments()) .isEqualTo(config.useDynatraceSummaryInstruments()); + assertThat(properties.getV2().isExportMeterMetadata()).isEqualTo(config.exportMeterMetadata()); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java index 09752dd0c7ff..f303e4a2cfdc 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java @@ -21,6 +21,7 @@ import io.micrometer.registry.otlp.OtlpMeterRegistry; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsExportAutoConfiguration.PropertiesOtlpMetricsConnectionDetails; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -83,6 +84,23 @@ void allowsRegistryToBeCustomized() { .hasBean("customRegistry")); } + @Test + void definesPropertiesBasedConnectionDetailsByDefault() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(PropertiesOtlpMetricsConnectionDetails.class)); + } + + @Test + void testConnectionFactoryWithOverridesWhenUsingCustomConnectionDetails() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class, ConnectionDetailsConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(OtlpMetricsConnectionDetails.class) + .doesNotHaveBean(PropertiesOtlpMetricsConnectionDetails.class); + OtlpConfig config = context.getBean(OtlpConfig.class); + assertThat(config.url()).isEqualTo("http://localhost:12345/v1/metrics"); + }); + } + @Configuration(proxyBeanMethods = false) static class BaseConfiguration { @@ -115,4 +133,14 @@ OtlpMeterRegistry customRegistry(OtlpConfig config, Clock clock) { } + @Configuration(proxyBeanMethods = false) + static class ConnectionDetailsConfiguration { + + @Bean + OtlpMetricsConnectionDetails otlpConnectionDetails() { + return () -> "http://localhost:12345/v1/metrics"; + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapterTests.java index d2fc02a7f412..5413e8c3a2f6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,55 +16,158 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp; +import java.util.Collections; import java.util.Map; +import java.util.concurrent.TimeUnit; import io.micrometer.registry.otlp.AggregationTemporality; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsExportAutoConfiguration.PropertiesOtlpMetricsConnectionDetails; +import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryProperties; +import org.springframework.mock.env.MockEnvironment; + import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; /** * Tests for {@link OtlpPropertiesConfigAdapter}. * * @author Eddú Meléndez + * @author Moritz Halbritter */ class OtlpPropertiesConfigAdapterTests { + private OtlpProperties properties; + + private OpenTelemetryProperties openTelemetryProperties; + + private MockEnvironment environment; + + private OtlpMetricsConnectionDetails connectionDetails; + + @BeforeEach + void setUp() { + this.properties = new OtlpProperties(); + this.openTelemetryProperties = new OpenTelemetryProperties(); + this.environment = new MockEnvironment(); + this.connectionDetails = new PropertiesOtlpMetricsConnectionDetails(this.properties); + } + @Test void whenPropertiesUrlIsSetAdapterUrlReturnsIt() { - OtlpProperties properties = new OtlpProperties(); - properties.setUrl("http://another-url:4318/v1/metrics"); - assertThat(new OtlpPropertiesConfigAdapter(properties).url()).isEqualTo("http://another-url:4318/v1/metrics"); + this.properties.setUrl("http://another-url:4318/v1/metrics"); + assertThat(createAdapter().url()).isEqualTo("http://another-url:4318/v1/metrics"); } @Test void whenPropertiesAggregationTemporalityIsNotSetAdapterAggregationTemporalityReturnsCumulative() { - OtlpProperties properties = new OtlpProperties(); - assertThat(new OtlpPropertiesConfigAdapter(properties).aggregationTemporality()) - .isSameAs(AggregationTemporality.CUMULATIVE); + assertThat(createAdapter().aggregationTemporality()).isSameAs(AggregationTemporality.CUMULATIVE); } @Test void whenPropertiesAggregationTemporalityIsSetAdapterAggregationTemporalityReturnsIt() { - OtlpProperties properties = new OtlpProperties(); - properties.setAggregationTemporality(AggregationTemporality.DELTA); - assertThat(new OtlpPropertiesConfigAdapter(properties).aggregationTemporality()) - .isSameAs(AggregationTemporality.DELTA); + this.properties.setAggregationTemporality(AggregationTemporality.DELTA); + assertThat(createAdapter().aggregationTemporality()).isSameAs(AggregationTemporality.DELTA); } @Test + @SuppressWarnings("removal") void whenPropertiesResourceAttributesIsSetAdapterResourceAttributesReturnsIt() { - OtlpProperties properties = new OtlpProperties(); - properties.setResourceAttributes(Map.of("service.name", "boot-service")); - assertThat(new OtlpPropertiesConfigAdapter(properties).resourceAttributes()).containsEntry("service.name", - "boot-service"); + this.properties.setResourceAttributes(Map.of("service.name", "boot-service")); + assertThat(createAdapter().resourceAttributes()).containsEntry("service.name", "boot-service"); } @Test void whenPropertiesHeadersIsSetAdapterHeadersReturnsIt() { - OtlpProperties properties = new OtlpProperties(); - properties.setHeaders(Map.of("header", "value")); - assertThat(new OtlpPropertiesConfigAdapter(properties).headers()).containsEntry("header", "value"); + this.properties.setHeaders(Map.of("header", "value")); + assertThat(createAdapter().headers()).containsEntry("header", "value"); + } + + @Test + void whenPropertiesBaseTimeUnitIsNotSetAdapterBaseTimeUnitReturnsMillis() { + assertThat(createAdapter().baseTimeUnit()).isSameAs(TimeUnit.MILLISECONDS); + } + + @Test + void whenPropertiesBaseTimeUnitIsSetAdapterBaseTimeUnitReturnsIt() { + this.properties.setBaseTimeUnit(TimeUnit.SECONDS); + assertThat(createAdapter().baseTimeUnit()).isSameAs(TimeUnit.SECONDS); + } + + @Test + @SuppressWarnings("removal") + void openTelemetryPropertiesShouldOverrideOtlpPropertiesIfNotEmpty() { + this.properties.setResourceAttributes(Map.of("a", "alpha")); + this.openTelemetryProperties.setResourceAttributes(Map.of("b", "beta")); + assertThat(createAdapter().resourceAttributes()).contains(entry("b", "beta")); + assertThat(createAdapter().resourceAttributes()).doesNotContain(entry("a", "alpha")); + } + + @Test + @SuppressWarnings("removal") + void openTelemetryPropertiesShouldNotOverrideOtlpPropertiesIfEmpty() { + this.properties.setResourceAttributes(Map.of("a", "alpha")); + this.openTelemetryProperties.setResourceAttributes(Collections.emptyMap()); + assertThat(createAdapter().resourceAttributes()).contains(entry("a", "alpha")); + } + + @Test + @SuppressWarnings("removal") + void serviceNameOverridesApplicationName() { + this.environment.setProperty("spring.application.name", "alpha"); + this.properties.setResourceAttributes(Map.of("service.name", "beta")); + assertThat(createAdapter().resourceAttributes()).containsEntry("service.name", "beta"); + } + + @Test + void serviceNameOverridesApplicationNameWhenUsingOtelProperties() { + this.environment.setProperty("spring.application.name", "alpha"); + this.openTelemetryProperties.setResourceAttributes(Map.of("service.name", "beta")); + assertThat(createAdapter().resourceAttributes()).containsEntry("service.name", "beta"); + } + + @Test + void shouldUseApplicationNameIfServiceNameIsNotSet() { + this.environment.setProperty("spring.application.name", "alpha"); + assertThat(createAdapter().resourceAttributes()).containsEntry("service.name", "alpha"); + } + + @Test + void shouldUseDefaultApplicationNameIfApplicationNameIsNotSet() { + assertThat(createAdapter().resourceAttributes()).containsEntry("service.name", "unknown_service"); + } + + @Test + @SuppressWarnings("removal") + void serviceGroupOverridesApplicationGroup() { + this.environment.setProperty("spring.application.group", "alpha"); + this.properties.setResourceAttributes(Map.of("service.group", "beta")); + assertThat(createAdapter().resourceAttributes()).containsEntry("service.group", "beta"); + } + + @Test + void serviceGroupOverridesApplicationGroupWhenUsingOtelProperties() { + this.environment.setProperty("spring.application.group", "alpha"); + this.openTelemetryProperties.setResourceAttributes(Map.of("service.group", "beta")); + assertThat(createAdapter().resourceAttributes()).containsEntry("service.group", "beta"); + } + + @Test + void shouldUseApplicationGroupIfServiceGroupIsNotSet() { + this.environment.setProperty("spring.application.group", "alpha"); + assertThat(createAdapter().resourceAttributes()).containsEntry("service.group", "alpha"); + } + + @Test + void shouldUseDefaultApplicationGroupIfApplicationGroupIsNotSet() { + assertThat(createAdapter().resourceAttributes()).doesNotContainKey("service.group"); + } + + private OtlpPropertiesConfigAdapter createAdapter() { + return new OtlpPropertiesConfigAdapter(this.properties, this.openTelemetryProperties, this.connectionDetails, + this.environment); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesTests.java index 69f945b66ce5..3046e2279dca 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesTests.java @@ -37,6 +37,7 @@ void defaultValuesAreConsistent() { assertStepRegistryDefaultValues(properties, config); assertThat(properties.getUrl()).isEqualTo(config.url()); assertThat(properties.getAggregationTemporality()).isSameAs(config.aggregationTemporality()); + assertThat(properties.getBaseTimeUnit()).isSameAs(config.baseTimeUnit()); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/DualPrometheusMetricsExportAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/DualPrometheusMetricsExportAutoConfigurationTests.java new file mode 100644 index 000000000000..f0c08e522891 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/DualPrometheusMetricsExportAutoConfigurationTests.java @@ -0,0 +1,411 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.prometheusmetrics.PrometheusConfig; +import io.prometheus.client.CollectorRegistry; +import io.prometheus.client.exemplars.ExemplarSampler; +import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier; +import io.prometheus.client.exporter.BasicAuthHttpConnectionFactory; +import io.prometheus.client.exporter.DefaultHttpConnectionFactory; +import io.prometheus.client.exporter.HttpConnectionFactory; +import io.prometheus.client.exporter.PushGateway; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import org.assertj.core.api.ThrowingConsumer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.DualPrometheusMetricsExportAutoConfigurationTests.CustomSecondEndpointConfiguration.SecondPrometheusScrapeEndpoint; +import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; +import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint; +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager; +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusScrapeEndpoint; +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusSimpleclientScrapeEndpoint; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ContextConsumer; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link PrometheusSimpleclientMetricsExportAutoConfiguration} and + * {@link PrometheusMetricsExportAutoConfiguration} with both Prometheus clients on the + * classpath. + * + * @author Andy Wilkinson + * @author Stephane Nicoll + * @author Jonatan Ivanov + */ +@SuppressWarnings({ "removal", "deprecation" }) +@ExtendWith(OutputCaptureExtension.class) +class DualPrometheusMetricsExportAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(PrometheusSimpleclientMetricsExportAutoConfiguration.class, + PrometheusMetricsExportAutoConfiguration.class)); + + @Test + void backsOffWithoutAClock() { + this.contextRunner.run((context) -> assertThat(context) + .doesNotHaveBean(io.micrometer.prometheusmetrics.PrometheusMeterRegistry.class) + .doesNotHaveBean(io.micrometer.prometheus.PrometheusMeterRegistry.class)); + } + + @Test + void autoConfiguresItsConfigPrometheusRegistryAndMeterRegistry() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(io.micrometer.prometheus.PrometheusMeterRegistry.class) + .hasSingleBean(CollectorRegistry.class) + .hasSingleBean(io.micrometer.prometheus.PrometheusConfig.class) + .hasSingleBean(io.micrometer.prometheusmetrics.PrometheusMeterRegistry.class) + .hasSingleBean(PrometheusRegistry.class) + .hasSingleBean(io.micrometer.prometheusmetrics.PrometheusConfig.class)); + } + + @Test + void autoConfigurationCanBeDisabledWithDefaultsEnabledProperty() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class) + .withPropertyValues("management.defaults.metrics.export.enabled=false") + .run((context) -> assertThat(context) + .doesNotHaveBean(io.micrometer.prometheus.PrometheusMeterRegistry.class) + .doesNotHaveBean(CollectorRegistry.class) + .doesNotHaveBean(io.micrometer.prometheus.PrometheusConfig.class) + .doesNotHaveBean(io.micrometer.prometheusmetrics.PrometheusMeterRegistry.class) + .doesNotHaveBean(PrometheusRegistry.class) + .doesNotHaveBean(io.micrometer.prometheusmetrics.PrometheusConfig.class)); + } + + @Test + void autoConfigurationCanBeDisabledWithSpecificEnabledProperty() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class) + .withPropertyValues("management.prometheus.metrics.export.enabled=false") + .run((context) -> assertThat(context) + .doesNotHaveBean(io.micrometer.prometheus.PrometheusMeterRegistry.class) + .doesNotHaveBean(CollectorRegistry.class) + .doesNotHaveBean(io.micrometer.prometheus.PrometheusConfig.class) + .doesNotHaveBean(io.micrometer.prometheusmetrics.PrometheusMeterRegistry.class) + .doesNotHaveBean(PrometheusRegistry.class) + .doesNotHaveBean(io.micrometer.prometheusmetrics.PrometheusConfig.class)); + } + + @Test + void allowsCustomConfigToBeUsed() { + this.contextRunner.withUserConfiguration(CustomConfigConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(io.micrometer.prometheus.PrometheusMeterRegistry.class) + .hasSingleBean(CollectorRegistry.class) + .hasSingleBean(io.micrometer.prometheus.PrometheusConfig.class) + .hasBean("customConfig") + .hasSingleBean(PrometheusRegistry.class) + .hasSingleBean(io.micrometer.prometheusmetrics.PrometheusConfig.class) + .hasBean("otherCustomConfig")); + } + + @Test + void allowsCustomRegistryToBeUsed() { + this.contextRunner.withUserConfiguration(CustomRegistryConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(io.micrometer.prometheus.PrometheusMeterRegistry.class) + .hasBean("customRegistry") + .hasSingleBean(CollectorRegistry.class) + .hasSingleBean(io.micrometer.prometheus.PrometheusConfig.class) + .hasSingleBean(PrometheusRegistry.class) + .hasSingleBean(io.micrometer.prometheusmetrics.PrometheusConfig.class) + .hasBean("otherCustomRegistry")); + } + + @Test + void allowsCustomCollectorRegistryToBeUsed() { + this.contextRunner.withUserConfiguration(CustomCollectorRegistryConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(io.micrometer.prometheus.PrometheusMeterRegistry.class) + .hasBean("customCollectorRegistry") + .hasSingleBean(CollectorRegistry.class) + .hasSingleBean(io.micrometer.prometheus.PrometheusConfig.class) + .hasBean("customPrometheusRegistry") + .hasSingleBean(PrometheusRegistry.class) + .hasSingleBean(io.micrometer.prometheusmetrics.PrometheusConfig.class)); + } + + @Test + void autoConfiguresExemplarSamplerIfSpanContextSupplierIsPresent() { + this.contextRunner.withUserConfiguration(ExemplarsConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(SpanContextSupplier.class) + .hasSingleBean(ExemplarSampler.class) + .hasSingleBean(io.micrometer.prometheus.PrometheusMeterRegistry.class)); + } + + @Test + void allowsCustomExemplarSamplerToBeUsed() { + this.contextRunner.withUserConfiguration(ExemplarsConfiguration.class) + .withBean("customExemplarSampler", ExemplarSampler.class, () -> mock(ExemplarSampler.class)) + .run((context) -> assertThat(context).hasSingleBean(ExemplarSampler.class) + .getBean(ExemplarSampler.class) + .isSameAs(context.getBean("customExemplarSampler"))); + } + + @Test + void exemplarSamplerIsNotAutoConfiguredIfSpanContextSupplierIsMissing() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(SpanContextSupplier.class) + .doesNotHaveBean(ExemplarSampler.class) + .hasSingleBean(io.micrometer.prometheus.PrometheusMeterRegistry.class)); + } + + @Test + void addsScrapeEndpointToManagementContext() { + this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) + .withUserConfiguration(BaseConfiguration.class) + .withPropertyValues("management.endpoints.web.exposure.include=prometheus") + .run((context) -> assertThat(context).hasSingleBean(PrometheusScrapeEndpoint.class) + .doesNotHaveBean(PrometheusSimpleclientScrapeEndpoint.class)); + } + + @Test + void scrapeEndpointNotAddedToManagementContextWhenNotExposed() { + this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) + .withUserConfiguration(BaseConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(PrometheusSimpleclientScrapeEndpoint.class) + .doesNotHaveBean(PrometheusScrapeEndpoint.class)); + } + + @Test + void scrapeEndpointCanBeDisabled() { + this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) + .withPropertyValues("management.endpoints.web.exposure.include=prometheus", + "management.endpoint.prometheus.enabled=false") + .withUserConfiguration(BaseConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(PrometheusSimpleclientScrapeEndpoint.class) + .doesNotHaveBean(PrometheusScrapeEndpoint.class)); + } + + @Test + void allowsCustomScrapeEndpointToBeUsed() { + this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) + .withUserConfiguration(CustomEndpointConfiguration.class) + .run((context) -> assertThat(context).hasBean("customEndpoint") + .hasSingleBean(PrometheusSimpleclientScrapeEndpoint.class)); + } + + @Test + void allowsCustomSecondScrapeEndpointToBeUsed() { + this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) + .withUserConfiguration(CustomSecondEndpointConfiguration.class) + .run((context) -> assertThat(context).hasBean("customSecondEndpoint") + .hasSingleBean(PrometheusSimpleclientScrapeEndpoint.class) + .hasSingleBean(SecondPrometheusScrapeEndpoint.class) + .hasSingleBean(PrometheusScrapeEndpoint.class)); + } + + @Test + void pushGatewayIsNotConfiguredWhenEnabledFlagIsNotSet() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(PrometheusPushGatewayManager.class)); + } + + @Test + void withPushGatewayEnabled(CapturedOutput output) { + this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) + .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true") + .withUserConfiguration(BaseConfiguration.class) + .run((context) -> { + assertThat(output).doesNotContain("Invalid PushGateway base url"); + hasGatewayURL(context, "http://localhost:9091/metrics/"); + }); + } + + @Test + void withPushGatewayNoBasicAuth() { + this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) + .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true") + .withUserConfiguration(BaseConfiguration.class) + .run(hasHttpConnectionFactory((httpConnectionFactory) -> assertThat(httpConnectionFactory) + .isInstanceOf(DefaultHttpConnectionFactory.class))); + } + + @Test + void withCustomPushGatewayURL() { + this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) + .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true", + "management.prometheus.metrics.export.pushgateway.base-url=https://example.com:8080") + .withUserConfiguration(BaseConfiguration.class) + .run((context) -> hasGatewayURL(context, "https://example.com:8080/metrics/")); + } + + @Test + void withPushGatewayBasicAuth() { + this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) + .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true", + "management.prometheus.metrics.export.pushgateway.username=admin", + "management.prometheus.metrics.export.pushgateway.password=secret") + .withUserConfiguration(BaseConfiguration.class) + .run(hasHttpConnectionFactory((httpConnectionFactory) -> assertThat(httpConnectionFactory) + .isInstanceOf(BasicAuthHttpConnectionFactory.class))); + } + + private void hasGatewayURL(AssertableApplicationContext context, String url) { + assertThat(getPushGateway(context)).hasFieldOrPropertyWithValue("gatewayBaseURL", url); + } + + private ContextConsumer hasHttpConnectionFactory( + ThrowingConsumer httpConnectionFactory) { + return (context) -> { + PushGateway pushGateway = getPushGateway(context); + httpConnectionFactory + .accept((HttpConnectionFactory) ReflectionTestUtils.getField(pushGateway, "connectionFactory")); + }; + } + + private PushGateway getPushGateway(AssertableApplicationContext context) { + assertThat(context).hasSingleBean(PrometheusPushGatewayManager.class); + PrometheusPushGatewayManager gatewayManager = context.getBean(PrometheusPushGatewayManager.class); + return (PushGateway) ReflectionTestUtils.getField(gatewayManager, "pushGateway"); + } + + @Configuration(proxyBeanMethods = false) + static class BaseConfiguration { + + @Bean + Clock clock() { + return Clock.SYSTEM; + } + + } + + @Configuration(proxyBeanMethods = false) + @Import(BaseConfiguration.class) + static class CustomConfigConfiguration { + + @Bean + io.micrometer.prometheus.PrometheusConfig customConfig() { + return (key) -> null; + } + + @Bean + io.micrometer.prometheusmetrics.PrometheusConfig otherCustomConfig() { + return (key) -> null; + } + + } + + @Configuration(proxyBeanMethods = false) + @Import(BaseConfiguration.class) + static class CustomRegistryConfiguration { + + @Bean + io.micrometer.prometheus.PrometheusMeterRegistry customRegistry( + io.micrometer.prometheus.PrometheusConfig config, CollectorRegistry collectorRegistry, Clock clock) { + return new io.micrometer.prometheus.PrometheusMeterRegistry(config, collectorRegistry, clock); + } + + @Bean + io.micrometer.prometheusmetrics.PrometheusMeterRegistry otherCustomRegistry( + io.micrometer.prometheusmetrics.PrometheusConfig config, PrometheusRegistry prometheusRegistry, + Clock clock) { + return new io.micrometer.prometheusmetrics.PrometheusMeterRegistry(config, prometheusRegistry, clock); + } + + } + + @Configuration(proxyBeanMethods = false) + @Import(BaseConfiguration.class) + static class CustomCollectorRegistryConfiguration { + + @Bean + CollectorRegistry customCollectorRegistry() { + return new CollectorRegistry(); + } + + @Bean + PrometheusRegistry customPrometheusRegistry() { + return new PrometheusRegistry(); + } + + } + + @Configuration(proxyBeanMethods = false) + @Import(BaseConfiguration.class) + static class CustomEndpointConfiguration { + + @Bean + PrometheusSimpleclientScrapeEndpoint customEndpoint(CollectorRegistry collectorRegistry) { + return new PrometheusSimpleclientScrapeEndpoint(collectorRegistry); + } + + } + + @Configuration(proxyBeanMethods = false) + @Import(BaseConfiguration.class) + static class CustomSecondEndpointConfiguration { + + @Bean + PrometheusScrapeEndpoint prometheusScrapeEndpoint(PrometheusRegistry prometheusRegistry, + PrometheusConfig prometheusConfig) { + return new PrometheusScrapeEndpoint(prometheusRegistry, prometheusConfig.prometheusProperties()); + } + + @Bean + SecondPrometheusScrapeEndpoint customSecondEndpoint(CollectorRegistry collectorRegistry) { + return new SecondPrometheusScrapeEndpoint(collectorRegistry); + } + + @WebEndpoint(id = "prometheussc") + static class SecondPrometheusScrapeEndpoint extends PrometheusSimpleclientScrapeEndpoint { + + SecondPrometheusScrapeEndpoint(CollectorRegistry collectorRegistry) { + super(collectorRegistry); + } + + } + + } + + @Configuration(proxyBeanMethods = false) + @Import(BaseConfiguration.class) + static class ExemplarsConfiguration { + + @Bean + SpanContextSupplier spanContextSupplier() { + return new SpanContextSupplier() { + + @Override + public String getTraceId() { + return null; + } + + @Override + public String getSpanId() { + return null; + } + + @Override + public boolean isSampled() { + return false; + } + + }; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java index 585455b22575..1c26b2ec7135 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,35 +17,23 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus; import io.micrometer.core.instrument.Clock; -import io.micrometer.prometheus.PrometheusConfig; -import io.micrometer.prometheus.PrometheusMeterRegistry; -import io.prometheus.client.CollectorRegistry; -import io.prometheus.client.exemplars.ExemplarSampler; -import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier; -import io.prometheus.client.exporter.BasicAuthHttpConnectionFactory; -import io.prometheus.client.exporter.DefaultHttpConnectionFactory; -import io.prometheus.client.exporter.HttpConnectionFactory; -import io.prometheus.client.exporter.PushGateway; -import org.assertj.core.api.ThrowingConsumer; +import io.micrometer.prometheusmetrics.PrometheusConfig; +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.tracer.common.SpanContext; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager; import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusScrapeEndpoint; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.assertj.AssertableApplicationContext; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.test.context.runner.ContextConsumer; -import org.springframework.boot.test.system.CapturedOutput; -import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; /** * Tests for {@link PrometheusMetricsExportAutoConfiguration}. @@ -54,92 +42,81 @@ * @author Stephane Nicoll * @author Jonatan Ivanov */ -@ExtendWith(OutputCaptureExtension.class) class PrometheusMetricsExportAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withClassLoader(new FilteredClassLoader("io.micrometer.prometheus.", "io.prometheus.client")) .withConfiguration(AutoConfigurations.of(PrometheusMetricsExportAutoConfiguration.class)); @Test void backsOffWithoutAClock() { - this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(PrometheusMeterRegistry.class)); + this.contextRunner.run((context) -> assertThat(context) + .doesNotHaveBean(io.micrometer.prometheusmetrics.PrometheusMeterRegistry.class)); } @Test void autoConfiguresItsConfigCollectorRegistryAndMeterRegistry() { this.contextRunner.withUserConfiguration(BaseConfiguration.class) - .run((context) -> assertThat(context).hasSingleBean(PrometheusMeterRegistry.class) - .hasSingleBean(CollectorRegistry.class) - .hasSingleBean(PrometheusConfig.class)); + .run((context) -> assertThat(context) + .hasSingleBean(io.micrometer.prometheusmetrics.PrometheusMeterRegistry.class) + .hasSingleBean(PrometheusRegistry.class) + .hasSingleBean(io.micrometer.prometheusmetrics.PrometheusConfig.class)); } @Test void autoConfigurationCanBeDisabledWithDefaultsEnabledProperty() { this.contextRunner.withUserConfiguration(BaseConfiguration.class) .withPropertyValues("management.defaults.metrics.export.enabled=false") - .run((context) -> assertThat(context).doesNotHaveBean(PrometheusMeterRegistry.class) - .doesNotHaveBean(CollectorRegistry.class) - .doesNotHaveBean(PrometheusConfig.class)); + .run((context) -> assertThat(context) + .doesNotHaveBean(io.micrometer.prometheusmetrics.PrometheusMeterRegistry.class) + .doesNotHaveBean(PrometheusRegistry.class) + .doesNotHaveBean(io.micrometer.prometheusmetrics.PrometheusConfig.class)); } @Test void autoConfigurationCanBeDisabledWithSpecificEnabledProperty() { this.contextRunner.withUserConfiguration(BaseConfiguration.class) .withPropertyValues("management.prometheus.metrics.export.enabled=false") - .run((context) -> assertThat(context).doesNotHaveBean(PrometheusMeterRegistry.class) - .doesNotHaveBean(CollectorRegistry.class) - .doesNotHaveBean(PrometheusConfig.class)); + .run((context) -> assertThat(context) + .doesNotHaveBean(io.micrometer.prometheusmetrics.PrometheusMeterRegistry.class) + .doesNotHaveBean(PrometheusRegistry.class) + .doesNotHaveBean(io.micrometer.prometheusmetrics.PrometheusConfig.class)); } @Test void allowsCustomConfigToBeUsed() { this.contextRunner.withUserConfiguration(CustomConfigConfiguration.class) - .run((context) -> assertThat(context).hasSingleBean(PrometheusMeterRegistry.class) - .hasSingleBean(CollectorRegistry.class) - .hasSingleBean(PrometheusConfig.class) + .run((context) -> assertThat(context) + .hasSingleBean(io.micrometer.prometheusmetrics.PrometheusMeterRegistry.class) + .hasSingleBean(PrometheusRegistry.class) + .hasSingleBean(io.micrometer.prometheusmetrics.PrometheusConfig.class) .hasBean("customConfig")); } @Test void allowsCustomRegistryToBeUsed() { this.contextRunner.withUserConfiguration(CustomRegistryConfiguration.class) - .run((context) -> assertThat(context).hasSingleBean(PrometheusMeterRegistry.class) + .run((context) -> assertThat(context) + .hasSingleBean(io.micrometer.prometheusmetrics.PrometheusMeterRegistry.class) .hasBean("customRegistry") - .hasSingleBean(CollectorRegistry.class) - .hasSingleBean(PrometheusConfig.class)); + .hasSingleBean(PrometheusRegistry.class) + .hasSingleBean(io.micrometer.prometheusmetrics.PrometheusConfig.class)); } @Test void allowsCustomCollectorRegistryToBeUsed() { - this.contextRunner.withUserConfiguration(CustomCollectorRegistryConfiguration.class) - .run((context) -> assertThat(context).hasSingleBean(PrometheusMeterRegistry.class) - .hasBean("customCollectorRegistry") - .hasSingleBean(CollectorRegistry.class) - .hasSingleBean(PrometheusConfig.class)); + this.contextRunner.withUserConfiguration(CustomPrometheusRegistryConfiguration.class) + .run((context) -> assertThat(context) + .hasSingleBean(io.micrometer.prometheusmetrics.PrometheusMeterRegistry.class) + .hasBean("customPrometheusRegistry") + .hasSingleBean(PrometheusRegistry.class) + .hasSingleBean(io.micrometer.prometheusmetrics.PrometheusConfig.class)); } @Test - void autoConfiguresExemplarSamplerIfSpanContextSupplierIsPresent() { + void autoConfiguresPrometheusMeterRegistryIfSpanContextIsPresent() { this.contextRunner.withUserConfiguration(ExemplarsConfiguration.class) - .run((context) -> assertThat(context).hasSingleBean(SpanContextSupplier.class) - .hasSingleBean(ExemplarSampler.class) - .hasSingleBean(PrometheusMeterRegistry.class)); - } - - @Test - void allowsCustomExemplarSamplerToBeUsed() { - this.contextRunner.withUserConfiguration(ExemplarsConfiguration.class) - .withBean("customExemplarSampler", ExemplarSampler.class, () -> mock(ExemplarSampler.class)) - .run((context) -> assertThat(context).hasSingleBean(ExemplarSampler.class) - .getBean(ExemplarSampler.class) - .isSameAs(context.getBean("customExemplarSampler"))); - } - - @Test - void exemplarSamplerIsNotAutoConfiguredIfSpanContextSupplierIsMissing() { - this.contextRunner.withUserConfiguration(BaseConfiguration.class) - .run((context) -> assertThat(context).doesNotHaveBean(SpanContextSupplier.class) - .doesNotHaveBean(ExemplarSampler.class) + .run((context) -> assertThat(context).hasSingleBean(SpanContext.class) .hasSingleBean(PrometheusMeterRegistry.class)); } @@ -161,8 +138,8 @@ void scrapeEndpointNotAddedToManagementContextWhenNotExposed() { @Test void scrapeEndpointCanBeDisabled() { this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) - .withPropertyValues("management.endpoints.web.exposure.include=prometheus") - .withPropertyValues("management.endpoint.prometheus.enabled=false") + .withPropertyValues("management.endpoints.web.exposure.include=prometheus", + "management.endpoint.prometheus.enabled=false") .withUserConfiguration(BaseConfiguration.class) .run((context) -> assertThat(context).doesNotHaveBean(PrometheusScrapeEndpoint.class)); } @@ -181,65 +158,6 @@ void pushGatewayIsNotConfiguredWhenEnabledFlagIsNotSet() { .run((context) -> assertThat(context).doesNotHaveBean(PrometheusPushGatewayManager.class)); } - @Test - void withPushGatewayEnabled(CapturedOutput output) { - this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) - .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true") - .withUserConfiguration(BaseConfiguration.class) - .run((context) -> { - assertThat(output).doesNotContain("Invalid PushGateway base url"); - hasGatewayURL(context, "http://localhost:9091/metrics/"); - }); - } - - @Test - void withPushGatewayNoBasicAuth() { - this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) - .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true") - .withUserConfiguration(BaseConfiguration.class) - .run(hasHttpConnectionFactory((httpConnectionFactory) -> assertThat(httpConnectionFactory) - .isInstanceOf(DefaultHttpConnectionFactory.class))); - } - - @Test - void withCustomPushGatewayURL() { - this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) - .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true", - "management.prometheus.metrics.export.pushgateway.base-url=https://example.com:8080") - .withUserConfiguration(BaseConfiguration.class) - .run((context) -> hasGatewayURL(context, "https://example.com:8080/metrics/")); - } - - @Test - void withPushGatewayBasicAuth() { - this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) - .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true", - "management.prometheus.metrics.export.pushgateway.username=admin", - "management.prometheus.metrics.export.pushgateway.password=secret") - .withUserConfiguration(BaseConfiguration.class) - .run(hasHttpConnectionFactory((httpConnectionFactory) -> assertThat(httpConnectionFactory) - .isInstanceOf(BasicAuthHttpConnectionFactory.class))); - } - - private void hasGatewayURL(AssertableApplicationContext context, String url) { - assertThat(getPushGateway(context)).hasFieldOrPropertyWithValue("gatewayBaseURL", url); - } - - private ContextConsumer hasHttpConnectionFactory( - ThrowingConsumer httpConnectionFactory) { - return (context) -> { - PushGateway pushGateway = getPushGateway(context); - httpConnectionFactory - .accept((HttpConnectionFactory) ReflectionTestUtils.getField(pushGateway, "connectionFactory")); - }; - } - - private PushGateway getPushGateway(AssertableApplicationContext context) { - assertThat(context).hasSingleBean(PrometheusPushGatewayManager.class); - PrometheusPushGatewayManager gatewayManager = context.getBean(PrometheusPushGatewayManager.class); - return (PushGateway) ReflectionTestUtils.getField(gatewayManager, "pushGateway"); - } - @Configuration(proxyBeanMethods = false) static class BaseConfiguration { @@ -255,7 +173,7 @@ Clock clock() { static class CustomConfigConfiguration { @Bean - PrometheusConfig customConfig() { + io.micrometer.prometheusmetrics.PrometheusConfig customConfig() { return (key) -> null; } @@ -266,20 +184,21 @@ PrometheusConfig customConfig() { static class CustomRegistryConfiguration { @Bean - PrometheusMeterRegistry customRegistry(PrometheusConfig config, CollectorRegistry collectorRegistry, + io.micrometer.prometheusmetrics.PrometheusMeterRegistry customRegistry( + io.micrometer.prometheusmetrics.PrometheusConfig config, PrometheusRegistry prometheusRegistry, Clock clock) { - return new PrometheusMeterRegistry(config, collectorRegistry, clock); + return new io.micrometer.prometheusmetrics.PrometheusMeterRegistry(config, prometheusRegistry, clock); } } @Configuration(proxyBeanMethods = false) @Import(BaseConfiguration.class) - static class CustomCollectorRegistryConfiguration { + static class CustomPrometheusRegistryConfiguration { @Bean - CollectorRegistry customCollectorRegistry() { - return new CollectorRegistry(); + PrometheusRegistry customPrometheusRegistry() { + return new PrometheusRegistry(); } } @@ -289,8 +208,9 @@ CollectorRegistry customCollectorRegistry() { static class CustomEndpointConfiguration { @Bean - PrometheusScrapeEndpoint customEndpoint(CollectorRegistry collectorRegistry) { - return new PrometheusScrapeEndpoint(collectorRegistry); + PrometheusScrapeEndpoint customEndpoint(PrometheusRegistry prometheusRegistry, + PrometheusConfig prometheusConfig) { + return new PrometheusScrapeEndpoint(prometheusRegistry, prometheusConfig.prometheusProperties()); } } @@ -300,24 +220,27 @@ PrometheusScrapeEndpoint customEndpoint(CollectorRegistry collectorRegistry) { static class ExemplarsConfiguration { @Bean - SpanContextSupplier spanContextSupplier() { - return new SpanContextSupplier() { + SpanContext spanContext() { + return new SpanContext() { @Override - public String getTraceId() { + public String getCurrentTraceId() { return null; } @Override - public String getSpanId() { + public String getCurrentSpanId() { return null; } @Override - public boolean isSampled() { + public boolean isCurrentSpanSampled() { return false; } + @Override + public void markCurrentSpanAsExemplar() { + } }; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusPropertiesConfigAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusPropertiesConfigAdapterTests.java index 68929bf7b605..09f34b55a826 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusPropertiesConfigAdapterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusPropertiesConfigAdapterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import java.time.Duration; -import io.micrometer.prometheus.HistogramFlavor; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.AbstractPropertiesConfigAdapterTests; @@ -44,14 +43,6 @@ void whenPropertiesDescriptionsIsSetAdapterDescriptionsReturnsIt() { assertThat(new PrometheusPropertiesConfigAdapter(properties).descriptions()).isFalse(); } - @Test - void whenPropertiesHistogramFlavorIsSetAdapterHistogramFlavorReturnsIt() { - PrometheusProperties properties = new PrometheusProperties(); - properties.setHistogramFlavor(HistogramFlavor.VictoriaMetrics); - assertThat(new PrometheusPropertiesConfigAdapter(properties).histogramFlavor()) - .isEqualTo(HistogramFlavor.VictoriaMetrics); - } - @Test void whenPropertiesStepIsSetAdapterStepReturnsIt() { PrometheusProperties properties = new PrometheusProperties(); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusPropertiesTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusPropertiesTests.java index 3cfc3c5ac9c5..cfdd0a4188c8 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusPropertiesTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusPropertiesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus; -import io.micrometer.prometheus.PrometheusConfig; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -31,9 +30,19 @@ class PrometheusPropertiesTests { @Test void defaultValuesAreConsistent() { PrometheusProperties properties = new PrometheusProperties(); - PrometheusConfig config = PrometheusConfig.DEFAULT; + io.micrometer.prometheusmetrics.PrometheusConfig config = io.micrometer.prometheusmetrics.PrometheusConfig.DEFAULT; assertThat(properties.isDescriptions()).isEqualTo(config.descriptions()); - assertThat(properties.getHistogramFlavor()).isEqualTo(config.histogramFlavor()); + assertThat(properties.getStep()).isEqualTo(config.step()); + } + + @SuppressWarnings("deprecation") + @Test + void defaultValuesAreConsistentWithSimpleclient() { + PrometheusProperties properties = new PrometheusProperties(); + io.micrometer.prometheus.PrometheusConfig config = io.micrometer.prometheus.PrometheusConfig.DEFAULT; + assertThat(properties.isDescriptions()).isEqualTo(config.descriptions()); + assertThat(PrometheusSimpleclientPropertiesConfigAdapter.mapToMicrometerHistogramFlavor(properties)) + .isEqualTo(config.histogramFlavor()); assertThat(properties.getStep()).isEqualTo(config.step()); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusSimpleclientMetricsExportAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusSimpleclientMetricsExportAutoConfigurationTests.java new file mode 100644 index 000000000000..b1778b76f6f5 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusSimpleclientMetricsExportAutoConfigurationTests.java @@ -0,0 +1,330 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus; + +import io.micrometer.core.instrument.Clock; +import io.prometheus.client.CollectorRegistry; +import io.prometheus.client.exemplars.ExemplarSampler; +import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier; +import io.prometheus.client.exporter.BasicAuthHttpConnectionFactory; +import io.prometheus.client.exporter.DefaultHttpConnectionFactory; +import io.prometheus.client.exporter.HttpConnectionFactory; +import io.prometheus.client.exporter.PushGateway; +import org.assertj.core.api.ThrowingConsumer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager; +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusSimpleclientScrapeEndpoint; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ContextConsumer; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link PrometheusSimpleclientMetricsExportAutoConfiguration}. + * + * @author Andy Wilkinson + * @author Stephane Nicoll + * @author Jonatan Ivanov + */ +@SuppressWarnings({ "removal", "deprecation" }) +@ExtendWith(OutputCaptureExtension.class) +class PrometheusSimpleclientMetricsExportAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withClassLoader(new FilteredClassLoader("io.micrometer.prometheusmetrics.", "io.prometheus.metrics")) + .withConfiguration(AutoConfigurations.of(PrometheusSimpleclientMetricsExportAutoConfiguration.class)); + + @Test + void backsOffWithoutAClock() { + this.contextRunner.run((context) -> assertThat(context) + .doesNotHaveBean(io.micrometer.prometheus.PrometheusMeterRegistry.class)); + } + + @Test + void autoConfiguresItsConfigCollectorRegistryAndMeterRegistry() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(io.micrometer.prometheus.PrometheusMeterRegistry.class) + .hasSingleBean(CollectorRegistry.class) + .hasSingleBean(io.micrometer.prometheus.PrometheusConfig.class)); + } + + @Test + void autoConfigurationCanBeDisabledWithDefaultsEnabledProperty() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class) + .withPropertyValues("management.defaults.metrics.export.enabled=false") + .run((context) -> assertThat(context) + .doesNotHaveBean(io.micrometer.prometheus.PrometheusMeterRegistry.class) + .doesNotHaveBean(CollectorRegistry.class) + .doesNotHaveBean(io.micrometer.prometheus.PrometheusConfig.class)); + } + + @Test + void autoConfigurationCanBeDisabledWithSpecificEnabledProperty() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class) + .withPropertyValues("management.prometheus.metrics.export.enabled=false") + .run((context) -> assertThat(context) + .doesNotHaveBean(io.micrometer.prometheus.PrometheusMeterRegistry.class) + .doesNotHaveBean(CollectorRegistry.class) + .doesNotHaveBean(io.micrometer.prometheus.PrometheusConfig.class)); + } + + @Test + void allowsCustomConfigToBeUsed() { + this.contextRunner.withUserConfiguration(CustomConfigConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(io.micrometer.prometheus.PrometheusMeterRegistry.class) + .hasSingleBean(CollectorRegistry.class) + .hasSingleBean(io.micrometer.prometheus.PrometheusConfig.class) + .hasBean("customConfig")); + } + + @Test + void allowsCustomRegistryToBeUsed() { + this.contextRunner.withUserConfiguration(CustomRegistryConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(io.micrometer.prometheus.PrometheusMeterRegistry.class) + .hasBean("customRegistry") + .hasSingleBean(CollectorRegistry.class) + .hasSingleBean(io.micrometer.prometheus.PrometheusConfig.class)); + } + + @Test + void allowsCustomCollectorRegistryToBeUsed() { + this.contextRunner.withUserConfiguration(CustomCollectorRegistryConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(io.micrometer.prometheus.PrometheusMeterRegistry.class) + .hasBean("customCollectorRegistry") + .hasSingleBean(CollectorRegistry.class) + .hasSingleBean(io.micrometer.prometheus.PrometheusConfig.class)); + } + + @Test + void autoConfiguresExemplarSamplerIfSpanContextSupplierIsPresent() { + this.contextRunner.withUserConfiguration(ExemplarsConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(SpanContextSupplier.class) + .hasSingleBean(ExemplarSampler.class) + .hasSingleBean(io.micrometer.prometheus.PrometheusMeterRegistry.class)); + } + + @Test + void allowsCustomExemplarSamplerToBeUsed() { + this.contextRunner.withUserConfiguration(ExemplarsConfiguration.class) + .withBean("customExemplarSampler", ExemplarSampler.class, () -> mock(ExemplarSampler.class)) + .run((context) -> assertThat(context).hasSingleBean(ExemplarSampler.class) + .getBean(ExemplarSampler.class) + .isSameAs(context.getBean("customExemplarSampler"))); + } + + @Test + void exemplarSamplerIsNotAutoConfiguredIfSpanContextSupplierIsMissing() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(SpanContextSupplier.class) + .doesNotHaveBean(ExemplarSampler.class) + .hasSingleBean(io.micrometer.prometheus.PrometheusMeterRegistry.class)); + } + + @Test + void addsScrapeEndpointToManagementContext() { + this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) + .withUserConfiguration(BaseConfiguration.class) + .withPropertyValues("management.endpoints.web.exposure.include=prometheus") + .run((context) -> assertThat(context).hasSingleBean(PrometheusSimpleclientScrapeEndpoint.class)); + } + + @Test + void scrapeEndpointNotAddedToManagementContextWhenNotExposed() { + this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) + .withUserConfiguration(BaseConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(PrometheusSimpleclientScrapeEndpoint.class)); + } + + @Test + void scrapeEndpointCanBeDisabled() { + this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) + .withPropertyValues("management.endpoints.web.exposure.include=prometheus", + "management.endpoint.prometheus.enabled=false") + .withUserConfiguration(BaseConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(PrometheusSimpleclientScrapeEndpoint.class)); + } + + @Test + void allowsCustomScrapeEndpointToBeUsed() { + this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) + .withUserConfiguration(CustomEndpointConfiguration.class) + .run((context) -> assertThat(context).hasBean("customEndpoint") + .hasSingleBean(PrometheusSimpleclientScrapeEndpoint.class)); + } + + @Test + void pushGatewayIsNotConfiguredWhenEnabledFlagIsNotSet() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(PrometheusPushGatewayManager.class)); + } + + @Test + void withPushGatewayEnabled(CapturedOutput output) { + this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) + .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true") + .withUserConfiguration(BaseConfiguration.class) + .run((context) -> { + assertThat(output).doesNotContain("Invalid PushGateway base url"); + hasGatewayURL(context, "http://localhost:9091/metrics/"); + }); + } + + @Test + void withPushGatewayNoBasicAuth() { + this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) + .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true") + .withUserConfiguration(BaseConfiguration.class) + .run(hasHttpConnectionFactory((httpConnectionFactory) -> assertThat(httpConnectionFactory) + .isInstanceOf(DefaultHttpConnectionFactory.class))); + } + + @Test + void withCustomPushGatewayURL() { + this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) + .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true", + "management.prometheus.metrics.export.pushgateway.base-url=https://example.com:8080") + .withUserConfiguration(BaseConfiguration.class) + .run((context) -> hasGatewayURL(context, "https://example.com:8080/metrics/")); + } + + @Test + void withPushGatewayBasicAuth() { + this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) + .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true", + "management.prometheus.metrics.export.pushgateway.username=admin", + "management.prometheus.metrics.export.pushgateway.password=secret") + .withUserConfiguration(BaseConfiguration.class) + .run(hasHttpConnectionFactory((httpConnectionFactory) -> assertThat(httpConnectionFactory) + .isInstanceOf(BasicAuthHttpConnectionFactory.class))); + } + + private void hasGatewayURL(AssertableApplicationContext context, String url) { + assertThat(getPushGateway(context)).hasFieldOrPropertyWithValue("gatewayBaseURL", url); + } + + private ContextConsumer hasHttpConnectionFactory( + ThrowingConsumer httpConnectionFactory) { + return (context) -> { + PushGateway pushGateway = getPushGateway(context); + httpConnectionFactory + .accept((HttpConnectionFactory) ReflectionTestUtils.getField(pushGateway, "connectionFactory")); + }; + } + + private PushGateway getPushGateway(AssertableApplicationContext context) { + assertThat(context).hasSingleBean(PrometheusPushGatewayManager.class); + PrometheusPushGatewayManager gatewayManager = context.getBean(PrometheusPushGatewayManager.class); + return (PushGateway) ReflectionTestUtils.getField(gatewayManager, "pushGateway"); + } + + @Configuration(proxyBeanMethods = false) + static class BaseConfiguration { + + @Bean + Clock clock() { + return Clock.SYSTEM; + } + + } + + @Configuration(proxyBeanMethods = false) + @Import(BaseConfiguration.class) + static class CustomConfigConfiguration { + + @Bean + io.micrometer.prometheus.PrometheusConfig customConfig() { + return (key) -> null; + } + + } + + @Configuration(proxyBeanMethods = false) + @Import(BaseConfiguration.class) + static class CustomRegistryConfiguration { + + @Bean + io.micrometer.prometheus.PrometheusMeterRegistry customRegistry( + io.micrometer.prometheus.PrometheusConfig config, CollectorRegistry collectorRegistry, Clock clock) { + return new io.micrometer.prometheus.PrometheusMeterRegistry(config, collectorRegistry, clock); + } + + } + + @Configuration(proxyBeanMethods = false) + @Import(BaseConfiguration.class) + static class CustomCollectorRegistryConfiguration { + + @Bean + CollectorRegistry customCollectorRegistry() { + return new CollectorRegistry(); + } + + } + + @Configuration(proxyBeanMethods = false) + @Import(BaseConfiguration.class) + static class CustomEndpointConfiguration { + + @Bean + PrometheusSimpleclientScrapeEndpoint customEndpoint(CollectorRegistry collectorRegistry) { + return new PrometheusSimpleclientScrapeEndpoint(collectorRegistry); + } + + } + + @Configuration(proxyBeanMethods = false) + @Import(BaseConfiguration.class) + static class ExemplarsConfiguration { + + @Bean + SpanContextSupplier spanContextSupplier() { + return new SpanContextSupplier() { + + @Override + public String getTraceId() { + return null; + } + + @Override + public String getSpanId() { + return null; + } + + @Override + public boolean isSampled() { + return false; + } + + }; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusSimpleclientPropertiesConfigAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusSimpleclientPropertiesConfigAdapterTests.java new file mode 100644 index 000000000000..cfd4701d6d71 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusSimpleclientPropertiesConfigAdapterTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus; + +import java.time.Duration; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusProperties.HistogramFlavor; +import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.AbstractPropertiesConfigAdapterTests; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link PrometheusSimpleclientPropertiesConfigAdapter}. + * + * @author Mirko Sobeck + */ +@SuppressWarnings({ "deprecation", "removal" }) +class PrometheusSimpleclientPropertiesConfigAdapterTests extends + AbstractPropertiesConfigAdapterTests { + + PrometheusSimpleclientPropertiesConfigAdapterTests() { + super(PrometheusSimpleclientPropertiesConfigAdapter.class); + } + + @Test + void whenPropertiesDescriptionsIsSetAdapterDescriptionsReturnsIt() { + PrometheusProperties properties = new PrometheusProperties(); + properties.setDescriptions(false); + assertThat(new PrometheusSimpleclientPropertiesConfigAdapter(properties).descriptions()).isFalse(); + } + + @Test + void whenPropertiesHistogramFlavorIsSetAdapterHistogramFlavorReturnsIt() { + PrometheusProperties properties = new PrometheusProperties(); + properties.setHistogramFlavor(HistogramFlavor.VictoriaMetrics); + assertThat(new PrometheusSimpleclientPropertiesConfigAdapter(properties).histogramFlavor()) + .isEqualTo(io.micrometer.prometheus.HistogramFlavor.VictoriaMetrics); + } + + @Test + void whenPropertiesStepIsSetAdapterStepReturnsIt() { + PrometheusProperties properties = new PrometheusProperties(); + properties.setStep(Duration.ofSeconds(30)); + assertThat(new PrometheusSimpleclientPropertiesConfigAdapter(properties).step()) + .isEqualTo(Duration.ofSeconds(30)); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/signalfx/SignalFxPropertiesConfigAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/signalfx/SignalFxPropertiesConfigAdapterTests.java index 4fa086069d04..d664a342148a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/signalfx/SignalFxPropertiesConfigAdapterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/signalfx/SignalFxPropertiesConfigAdapterTests.java @@ -68,7 +68,7 @@ void whenPropertiesSourceIsSetAdapterSourceReturnsIt() { } @Test - void whenPropertiesPublishHistogramTypeIsCumulativePublishCumulativeHistogramReturnsIt() { + void whenPropertiesPublishHistogramTypeIsCumulativeAdapterPublishCumulativeHistogramReturnsIt() { SignalFxProperties properties = createProperties(); properties.setPublishedHistogramType(HistogramType.CUMULATIVE); assertThat(createConfigAdapter(properties).publishCumulativeHistogram()).isTrue(); @@ -76,7 +76,7 @@ void whenPropertiesPublishHistogramTypeIsCumulativePublishCumulativeHistogramRet } @Test - void whenPropertiesPublishHistogramTypeIsDeltaPublishDeltaHistogramReturnsIt() { + void whenPropertiesPublishHistogramTypeIsDeltaAdapterPublishDeltaHistogramReturnsIt() { SignalFxProperties properties = createProperties(); properties.setPublishedHistogramType(HistogramType.DELTA); assertThat(createConfigAdapter(properties).publishDeltaHistogram()).isTrue(); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapterTests.java index 54234f878714..2acd34bbe5bc 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapterTests.java @@ -18,11 +18,15 @@ import java.net.URI; +import com.wavefront.sdk.common.clients.service.token.TokenService.Type; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.PushRegistryPropertiesConfigAdapterTests; import org.springframework.boot.actuate.autoconfigure.wavefront.WavefrontProperties; import org.springframework.boot.actuate.autoconfigure.wavefront.WavefrontProperties.Metrics.Export; +import org.springframework.boot.actuate.autoconfigure.wavefront.WavefrontProperties.TokenType; import static org.assertj.core.api.Assertions.assertThat; @@ -62,7 +66,7 @@ void whenPropertiesGlobalPrefixIsSetAdapterGlobalPrefixReturnsIt() { protected void whenPropertiesBatchSizeIsSetAdapterBatchSizeReturnsIt() { WavefrontProperties properties = new WavefrontProperties(); properties.getSender().setBatchSize(10042); - assertThat(createConfigAdapter(properties.getMetrics().getExport()).batchSize()).isEqualTo(10042); + assertThat(new WavefrontPropertiesConfigAdapter(properties).batchSize()).isEqualTo(10042); } @Test @@ -107,4 +111,20 @@ void whenPropertiesReportDayDistributionIsSetAdapterReportDayDistributionReturns assertThat(createConfigAdapter(properties).reportDayDistribution()).isTrue(); } + @ParameterizedTest + @CsvSource(textBlock = """ + null, WAVEFRONT_API_TOKEN + NO_TOKEN, NO_TOKEN + WAVEFRONT_API_TOKEN, WAVEFRONT_API_TOKEN + CSP_API_TOKEN, CSP_API_TOKEN + CSP_CLIENT_CREDENTIALS, CSP_CLIENT_CREDENTIALS + """) + void whenTokenTypeIsSetAdapterReturnsIt(String property, String wavefront) { + TokenType propertyTokenType = property.equals("null") ? null : TokenType.valueOf(property); + Type wavefrontTokenType = Type.valueOf(wavefront); + WavefrontProperties properties = new WavefrontProperties(); + properties.setApiTokenType(propertyTokenType); + assertThat(new WavefrontPropertiesConfigAdapter(properties).apiTokenType()).isEqualTo(wavefrontTokenType); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/JerseyServerMetricsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/JerseyServerMetricsAutoConfigurationTests.java index a7b1a6326ea9..e117e02359d0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/JerseyServerMetricsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/JerseyServerMetricsAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,16 +19,12 @@ import java.net.URI; import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Timer; -import io.micrometer.core.instrument.binder.jersey.server.DefaultJerseyTagsProvider; -import io.micrometer.core.instrument.binder.jersey.server.JerseyTagsProvider; -import io.micrometer.core.instrument.binder.jersey.server.MetricsApplicationEventListener; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; +import org.glassfish.jersey.micrometer.server.ObservationApplicationEventListener; import org.glassfish.jersey.server.ResourceConfig; -import org.glassfish.jersey.server.monitoring.RequestEvent; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; @@ -55,6 +51,7 @@ * * @author Michael Weirauch * @author Michael Simons + * @author Moritz Halbritter */ class JerseyServerMetricsAutoConfigurationTests { @@ -77,16 +74,10 @@ void shouldOnlyBeActiveInWebApplicationContext() { @Test void shouldProvideAllNecessaryBeans() { - this.webContextRunner.run((context) -> assertThat(context).hasSingleBean(DefaultJerseyTagsProvider.class) + this.webContextRunner.run((context) -> assertThat(context).hasBean("jerseyMetricsUriTagFilter") .hasSingleBean(ResourceConfigCustomizer.class)); } - @Test - void shouldHonorExistingTagProvider() { - this.webContextRunner.withUserConfiguration(CustomJerseyTagsProviderConfiguration.class) - .run((context) -> assertThat(context).hasSingleBean(CustomJerseyTagsProvider.class)); - } - @Test void httpRequestsAreTimed() { this.webContextRunner.run((context) -> { @@ -99,10 +90,9 @@ void httpRequestsAreTimed() { @Test void noHttpRequestsTimedWhenJerseyInstrumentationMissingFromClasspath() { - this.webContextRunner.withClassLoader(new FilteredClassLoader(MetricsApplicationEventListener.class)) + this.webContextRunner.withClassLoader(new FilteredClassLoader(ObservationApplicationEventListener.class)) .run((context) -> { doRequest(context); - MeterRegistry registry = context.getBean(MeterRegistry.class); assertThat(registry.find("http.server.requests").timer()).isNull(); }); @@ -125,7 +115,7 @@ ResourceConfig resourceConfig() { } @Path("/users") - public class TestResource { + public static class TestResource { @GET @Path("/{id}") @@ -137,28 +127,4 @@ public String getUser(@PathParam("id") String id) { } - @Configuration(proxyBeanMethods = false) - static class CustomJerseyTagsProviderConfiguration { - - @Bean - JerseyTagsProvider customJerseyTagsProvider() { - return new CustomJerseyTagsProvider(); - } - - } - - static class CustomJerseyTagsProvider implements JerseyTagsProvider { - - @Override - public Iterable httpRequestTags(RequestEvent event) { - return null; - } - - @Override - public Iterable httpLongRequestTags(RequestEvent event) { - return null; - } - - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/mongo/MongoMetricsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/mongo/MongoMetricsAutoConfigurationTests.java index 7c3d870c3baa..5c5757730d15 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/mongo/MongoMetricsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/mongo/MongoMetricsAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import io.micrometer.core.instrument.binder.mongodb.MongoConnectionPoolTagsProvider; import io.micrometer.core.instrument.binder.mongodb.MongoMetricsCommandListener; import io.micrometer.core.instrument.binder.mongodb.MongoMetricsConnectionPoolListener; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun; @@ -62,7 +63,7 @@ void whenThereIsAMeterRegistryThenMetricsCommandListenerIsAdded() { assertThat(context).hasSingleBean(MongoMetricsCommandListener.class); assertThat(getActualMongoClientSettingsUsedToConstructClient(context)) .extracting(MongoClientSettings::getCommandListeners) - .asList() + .asInstanceOf(InstanceOfAssertFactories.LIST) .containsExactly(context.getBean(MongoMetricsCommandListener.class)); assertThat(getMongoCommandTagsProviderUsedToConstructListener(context)) .isInstanceOf(DefaultMongoCommandTagsProvider.class); @@ -168,7 +169,7 @@ private ContextConsumer assertThatMetricsCommandLi assertThat(context).doesNotHaveBean(MongoMetricsCommandListener.class); assertThat(getActualMongoClientSettingsUsedToConstructClient(context)) .extracting(MongoClientSettings::getCommandListeners) - .asList() + .asInstanceOf(InstanceOfAssertFactories.LIST) .isEmpty(); }; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/jetty/JettyMetricsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/jetty/JettyMetricsAutoConfigurationTests.java index d53632c54410..0e8437a192be 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/jetty/JettyMetricsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/jetty/JettyMetricsAutoConfigurationTests.java @@ -31,7 +31,6 @@ import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; -import org.springframework.boot.testsupport.web.servlet.Servlet5ClassPathOverrides; import org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory; import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext; @@ -50,7 +49,6 @@ * @author Andy Wilkinson * @author Chris Bono */ -@Servlet5ClassPathOverrides class JettyMetricsAutoConfigurationTests { @Test diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java index 5d9c1d3466a9..f2742c73f1ce 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java @@ -34,9 +34,11 @@ import io.micrometer.observation.ObservationHandler.FirstMatchingCompositeObservationHandler; import io.micrometer.observation.ObservationPredicate; import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.aop.ObservedAspect; import io.micrometer.tracing.Tracer; import io.micrometer.tracing.handler.TracingAwareMeterObservationHandler; import io.micrometer.tracing.handler.TracingObservationHandler; +import org.aspectj.weaver.Advice; import org.junit.jupiter.api.Test; import org.mockito.Answers; @@ -48,6 +50,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.annotation.Order; +import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -58,6 +61,7 @@ * * @author Moritz Halbritter * @author Jonatan Ivanov + * @author Vedran Pavic */ class ObservationAutoConfigurationTests { @@ -77,6 +81,7 @@ void beansShouldNotBeSuppliedWhenMicrometerObservationIsNotOnClassPath() { assertThat(context).hasSingleBean(MeterRegistry.class); assertThat(context).doesNotHaveBean(ObservationRegistry.class); assertThat(context).doesNotHaveBean(ObservationHandler.class); + assertThat(context).doesNotHaveBean(ObservedAspect.class); assertThat(context).doesNotHaveBean(ObservationHandlerGrouping.class); }); } @@ -88,6 +93,7 @@ void supplyObservationRegistryWhenMicrometerCoreAndTracingAreNotOnClassPath() { ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); Observation.start("test-observation", observationRegistry).stop(); assertThat(context).doesNotHaveBean(ObservationHandler.class); + assertThat(context).hasSingleBean(ObservedAspect.class); assertThat(context).doesNotHaveBean(ObservationHandlerGrouping.class); }); } @@ -99,6 +105,7 @@ void supplyMeterHandlerAndGroupingWhenMicrometerCoreIsOnClassPathButTracingIsNot Observation.start("test-observation", observationRegistry).stop(); assertThat(context).hasSingleBean(ObservationHandler.class); assertThat(context).hasSingleBean(DefaultMeterObservationHandler.class); + assertThat(context).hasSingleBean(ObservedAspect.class); assertThat(context).hasSingleBean(ObservationHandlerGrouping.class); assertThat(context).hasBean("metricsObservationHandlerGrouping"); }); @@ -110,6 +117,7 @@ void supplyOnlyTracingObservationHandlerGroupingWhenMicrometerCoreIsNotOnClassPa ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); Observation.start("test-observation", observationRegistry).stop(); assertThat(context).doesNotHaveBean(ObservationHandler.class); + assertThat(context).hasSingleBean(ObservedAspect.class); assertThat(context).hasSingleBean(ObservationHandlerGrouping.class); assertThat(context).hasBean("tracingObservationHandlerGrouping"); }); @@ -123,6 +131,7 @@ void supplyMeterHandlerAndGroupingWhenMicrometerCoreAndTracingAreOnClassPath() { // TracingAwareMeterObservationHandler that we don't test here Observation.start("test-observation", observationRegistry); assertThat(context).hasSingleBean(ObservationHandler.class); + assertThat(context).hasSingleBean(ObservedAspect.class); assertThat(context).hasSingleBean(TracingAwareMeterObservationHandler.class); assertThat(context).hasSingleBean(ObservationHandlerGrouping.class); assertThat(context).hasBean("metricsAndTracingObservationHandlerGrouping"); @@ -138,6 +147,7 @@ void supplyMeterHandlerAndGroupingWhenMicrometerCoreAndTracingAreOnClassPathButT Observation.start("test-observation", observationRegistry).stop(); assertThat(context).hasSingleBean(ObservationHandler.class); assertThat(context).hasSingleBean(DefaultMeterObservationHandler.class); + assertThat(context).hasSingleBean(ObservedAspect.class); assertThat(context).hasSingleBean(ObservationHandlerGrouping.class); assertThat(context).hasBean("metricsAndTracingObservationHandlerGrouping"); }); @@ -155,6 +165,7 @@ void autoConfiguresDefaultMeterObservationHandler() { assertThat(meterRegistry.get("test-observation").timer().count()).isOne(); assertThat(context).hasSingleBean(DefaultMeterObservationHandler.class); assertThat(context).hasSingleBean(ObservationHandler.class); + assertThat(context).hasSingleBean(ObservedAspect.class); }); } @@ -164,6 +175,20 @@ void allowsDefaultMeterObservationHandlerToBeDisabled() { .run((context) -> assertThat(context).doesNotHaveBean(ObservationHandler.class)); } + @Test + void allowsObservedAspectToBeDisabled() { + this.contextRunner.withClassLoader(new FilteredClassLoader(Advice.class)) + .run((context) -> assertThat(context).doesNotHaveBean(ObservedAspect.class)); + } + + @Test + void allowsObservedAspectToBeCustomized() { + this.contextRunner.withUserConfiguration(CustomObservedAspectConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(ObservedAspect.class) + .getBean(ObservedAspect.class) + .isSameAs(context.getBean("customObservedAspect"))); + } + @Test void autoConfiguresObservationPredicates() { this.contextRunner.withUserConfiguration(ObservationPredicates.class).run((context) -> { @@ -189,6 +214,22 @@ void autoConfiguresObservationFilters() { }); } + @Test + void shouldSupplyPropertiesObservationFilterBean() { + this.contextRunner + .run((context) -> assertThat(context).hasSingleBean(PropertiesObservationFilterPredicate.class)); + } + + @Test + void shouldApplyCommonKeyValuesToObservations() { + this.contextRunner.withPropertyValues("management.observations.key-values.a=alpha").run((context) -> { + ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); + Observation.start("keyvalues", observationRegistry).stop(); + MeterRegistry meterRegistry = context.getBean(MeterRegistry.class); + assertThat(meterRegistry.get("keyvalues").tag("a", "alpha").timer().count()).isOne(); + }); + } + @Test void autoConfiguresGlobalObservationConventions() { this.contextRunner.withUserConfiguration(CustomGlobalObservationConvention.class).run((context) -> { @@ -207,14 +248,13 @@ void autoConfiguresObservationHandlers() { Observation.start("test-observation", observationRegistry).stop(); assertThat(context).doesNotHaveBean(DefaultMeterObservationHandler.class); assertThat(handlers).hasSize(2); - // Regular handlers are registered first - assertThat(handlers.get(0)).isInstanceOf(CustomObservationHandler.class); // Multiple MeterObservationHandler are wrapped in - // FirstMatchingCompositeObservationHandler, which calls only the first - // one - assertThat(handlers.get(1)).isInstanceOf(CustomMeterObservationHandler.class); - assertThat(((CustomMeterObservationHandler) handlers.get(1)).getName()) + // FirstMatchingCompositeObservationHandler, which calls only the first one + assertThat(handlers.get(0)).isInstanceOf(CustomMeterObservationHandler.class); + assertThat(((CustomMeterObservationHandler) handlers.get(0)).getName()) .isEqualTo("customMeterObservationHandler1"); + // Regular handlers are registered last + assertThat(handlers.get(1)).isInstanceOf(CustomObservationHandler.class); assertThat(context).doesNotHaveBean(DefaultMeterObservationHandler.class); assertThat(context).doesNotHaveBean(TracingAwareMeterObservationHandler.class); }); @@ -257,25 +297,84 @@ void autoConfiguresObservationHandlerWhenTracingIsActive() { List> handlers = context.getBean(CalledHandlers.class).getCalledHandlers(); Observation.start("test-observation", observationRegistry).stop(); assertThat(handlers).hasSize(3); - // Regular handlers are registered first - assertThat(handlers.get(0)).isInstanceOf(CustomObservationHandler.class); // Multiple TracingObservationHandler are wrapped in - // FirstMatchingCompositeObservationHandler, which calls only the first - // one - assertThat(handlers.get(1)).isInstanceOf(CustomTracingObservationHandler.class); - assertThat(((CustomTracingObservationHandler) handlers.get(1)).getName()) + // FirstMatchingCompositeObservationHandler, which calls only the first one + assertThat(handlers.get(0)).isInstanceOf(CustomTracingObservationHandler.class); + assertThat(((CustomTracingObservationHandler) handlers.get(0)).getName()) .isEqualTo("customTracingHandler1"); // Multiple MeterObservationHandler are wrapped in - // FirstMatchingCompositeObservationHandler, which calls only the first - // one - assertThat(handlers.get(2)).isInstanceOf(CustomMeterObservationHandler.class); - assertThat(((CustomMeterObservationHandler) handlers.get(2)).getName()) + // FirstMatchingCompositeObservationHandler, which calls only the first one + assertThat(handlers.get(1)).isInstanceOf(CustomMeterObservationHandler.class); + assertThat(((CustomMeterObservationHandler) handlers.get(1)).getName()) .isEqualTo("customMeterObservationHandler1"); + // Regular handlers are registered last + assertThat(handlers.get(2)).isInstanceOf(CustomObservationHandler.class); assertThat(context).doesNotHaveBean(TracingAwareMeterObservationHandler.class); assertThat(context).doesNotHaveBean(DefaultMeterObservationHandler.class); }); } + @Test + void shouldNotDisableSpringSecurityObservationsByDefault() { + this.contextRunner.run((context) -> { + ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); + Observation.start("spring.security.filterchains", observationRegistry).stop(); + MeterRegistry meterRegistry = context.getBean(MeterRegistry.class); + assertThat(meterRegistry.get("spring.security.filterchains").timer().count()).isOne(); + }); + } + + @Test + void shouldDisableSpringSecurityObservationsIfPropertyIsSet() { + this.contextRunner.withPropertyValues("management.observations.enable.spring.security=false").run((context) -> { + ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); + Observation.start("spring.security.filterchains", observationRegistry).stop(); + MeterRegistry meterRegistry = context.getBean(MeterRegistry.class); + assertThatExceptionOfType(MeterNotFoundException.class) + .isThrownBy(() -> meterRegistry.get("spring.security.filterchains").timer()); + }); + } + + @Test + void shouldEnableLongTaskTimersByDefault() { + this.contextRunner.run((context) -> { + DefaultMeterObservationHandler handler = context.getBean(DefaultMeterObservationHandler.class); + assertThat(handler).hasFieldOrPropertyWithValue("shouldCreateLongTaskTimer", true); + }); + } + + @Test + void shouldDisableLongTaskTimerIfPropertyIsSet() { + this.contextRunner.withPropertyValues("management.observations.long-task-timer.enabled=false") + .run((context) -> { + DefaultMeterObservationHandler handler = context.getBean(DefaultMeterObservationHandler.class); + assertThat(handler).hasFieldOrPropertyWithValue("shouldCreateLongTaskTimer", false); + }); + } + + @Test + @SuppressWarnings("unchecked") + void shouldEnableLongTaskTimersForTracingByDefault() { + this.tracingContextRunner.run((context) -> { + TracingAwareMeterObservationHandler tracingHandler = context + .getBean(TracingAwareMeterObservationHandler.class); + Object delegate = ReflectionTestUtils.getField(tracingHandler, "delegate"); + assertThat(delegate).hasFieldOrPropertyWithValue("shouldCreateLongTaskTimer", true); + }); + } + + @Test + @SuppressWarnings("unchecked") + void shouldDisableLongTaskTimerForTracingIfPropertyIsSet() { + this.tracingContextRunner.withPropertyValues("management.observations.long-task-timer.enabled=false") + .run((context) -> { + TracingAwareMeterObservationHandler tracingHandler = context + .getBean(TracingAwareMeterObservationHandler.class); + Object delegate = ReflectionTestUtils.getField(tracingHandler, "delegate"); + assertThat(delegate).hasFieldOrPropertyWithValue("shouldCreateLongTaskTimer", false); + }); + } + @Configuration(proxyBeanMethods = false) static class ObservationPredicates { @@ -303,6 +402,16 @@ ObservationFilter observationFilterTwo() { } + @Configuration(proxyBeanMethods = false) + static class CustomObservedAspectConfiguration { + + @Bean + ObservedAspect customObservedAspect(ObservationRegistry observationRegistry) { + return new ObservedAspect(observationRegistry); + } + + } + @Configuration(proxyBeanMethods = false) static class CustomGlobalObservationConvention { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationHandlerGroupingTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationHandlerGroupingTests.java new file mode 100644 index 000000000000..b42d0a155c81 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationHandlerGroupingTests.java @@ -0,0 +1,126 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.observation; + +import java.lang.reflect.Method; +import java.util.List; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.Observation.Context; +import io.micrometer.observation.ObservationHandler; +import io.micrometer.observation.ObservationHandler.FirstMatchingCompositeObservationHandler; +import io.micrometer.observation.ObservationRegistry.ObservationConfig; +import org.junit.jupiter.api.Test; + +import org.springframework.util.ReflectionUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ObservationHandlerGrouping}. + * + * @author Moritz Halbritter + */ +class ObservationHandlerGroupingTests { + + @Test + void shouldGroupCategoriesIntoFirstMatchingHandlerAndRespectCategoryOrder() { + ObservationHandlerGrouping grouping = new ObservationHandlerGrouping( + List.of(ObservationHandlerA.class, ObservationHandlerB.class)); + ObservationConfig config = new ObservationConfig(); + ObservationHandlerA handlerA1 = new ObservationHandlerA("a1"); + ObservationHandlerA handlerA2 = new ObservationHandlerA("a2"); + ObservationHandlerB handlerB1 = new ObservationHandlerB("b1"); + ObservationHandlerB handlerB2 = new ObservationHandlerB("b2"); + grouping.apply(List.of(handlerB1, handlerB2, handlerA1, handlerA2), config); + List> handlers = getObservationHandlers(config); + assertThat(handlers).hasSize(2); + // Category A is first + assertThat(handlers.get(0)).isInstanceOf(FirstMatchingCompositeObservationHandler.class); + FirstMatchingCompositeObservationHandler firstMatching0 = (FirstMatchingCompositeObservationHandler) handlers + .get(0); + assertThat(firstMatching0.getHandlers()).containsExactly(handlerA1, handlerA2); + // Category B is second + assertThat(handlers.get(1)).isInstanceOf(FirstMatchingCompositeObservationHandler.class); + FirstMatchingCompositeObservationHandler firstMatching1 = (FirstMatchingCompositeObservationHandler) handlers + .get(1); + assertThat(firstMatching1.getHandlers()).containsExactly(handlerB1, handlerB2); + } + + @Test + void uncategorizedHandlersShouldBeOrderedAfterCategories() { + ObservationHandlerGrouping grouping = new ObservationHandlerGrouping(ObservationHandlerA.class); + ObservationConfig config = new ObservationConfig(); + ObservationHandlerA handlerA1 = new ObservationHandlerA("a1"); + ObservationHandlerA handlerA2 = new ObservationHandlerA("a2"); + ObservationHandlerB handlerB1 = new ObservationHandlerB("b1"); + grouping.apply(List.of(handlerB1, handlerA1, handlerA2), config); + List> handlers = getObservationHandlers(config); + assertThat(handlers).hasSize(2); + // Category A is first + assertThat(handlers.get(0)).isInstanceOf(FirstMatchingCompositeObservationHandler.class); + FirstMatchingCompositeObservationHandler firstMatching0 = (FirstMatchingCompositeObservationHandler) handlers + .get(0); + // Uncategorized handlers follow + assertThat(firstMatching0.getHandlers()).containsExactly(handlerA1, handlerA2); + assertThat(handlers.get(1)).isEqualTo(handlerB1); + } + + @SuppressWarnings("unchecked") + private static List> getObservationHandlers(ObservationConfig config) { + Method method = ReflectionUtils.findMethod(ObservationConfig.class, "getObservationHandlers"); + ReflectionUtils.makeAccessible(method); + return (List>) ReflectionUtils.invokeMethod(method, config); + } + + private static class NamedObservationHandler implements ObservationHandler { + + private final String name; + + NamedObservationHandler(String name) { + this.name = name; + } + + @Override + public boolean supportsContext(Context context) { + return true; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{name='" + this.name + "'}"; + } + + } + + private static class ObservationHandlerA extends NamedObservationHandler { + + ObservationHandlerA(String name) { + super(name); + } + + } + + private static class ObservationHandlerB extends NamedObservationHandler { + + ObservationHandlerB(String name) { + super(name); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterPredicateTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterPredicateTests.java new file mode 100644 index 000000000000..4afd4f601e67 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterPredicateTests.java @@ -0,0 +1,100 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.observation; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import io.micrometer.observation.Observation.Context; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link PropertiesObservationFilterPredicate}. + * + * @author Moritz Halbritter + */ +class PropertiesObservationFilterPredicateTests { + + @Test + void shouldDoNothingIfKeyValuesAreEmpty() { + PropertiesObservationFilterPredicate filter = createFilter(); + Context mapped = mapContext(filter, "a", "alpha"); + assertThat(mapped.getLowCardinalityKeyValues()).containsExactly(KeyValue.of("a", "alpha")); + } + + @Test + void shouldAddKeyValues() { + PropertiesObservationFilterPredicate filter = createFilter("b", "beta"); + Context mapped = mapContext(filter, "a", "alpha"); + assertThat(mapped.getLowCardinalityKeyValues()).containsExactly(KeyValue.of("a", "alpha"), + KeyValue.of("b", "beta")); + } + + @Test + void shouldFilter() { + PropertiesObservationFilterPredicate predicate = createPredicate("spring.security"); + Context context = new Context(); + assertThat(predicate.test("spring.security.filterchains", context)).isFalse(); + assertThat(predicate.test("spring.security", context)).isFalse(); + assertThat(predicate.test("spring.data", context)).isTrue(); + assertThat(predicate.test("spring", context)).isTrue(); + } + + @Test + void filterShouldFallbackToAll() { + PropertiesObservationFilterPredicate predicate = createPredicate("all"); + Context context = new Context(); + assertThat(predicate.test("spring.security.filterchains", context)).isFalse(); + assertThat(predicate.test("spring.security", context)).isFalse(); + assertThat(predicate.test("spring.data", context)).isFalse(); + assertThat(predicate.test("spring", context)).isFalse(); + } + + @Test + void shouldNotFilterIfDisabledNamesIsEmpty() { + PropertiesObservationFilterPredicate predicate = createPredicate(); + Context context = new Context(); + assertThat(predicate.test("spring.security.filterchains", context)).isTrue(); + assertThat(predicate.test("spring.security", context)).isTrue(); + assertThat(predicate.test("spring.data", context)).isTrue(); + assertThat(predicate.test("spring", context)).isTrue(); + } + + private static Context mapContext(PropertiesObservationFilterPredicate filter, String... initialKeyValues) { + Context context = new Context(); + context.addLowCardinalityKeyValues(KeyValues.of(initialKeyValues)); + return filter.map(context); + } + + private static PropertiesObservationFilterPredicate createFilter(String... keyValues) { + ObservationProperties properties = new ObservationProperties(); + for (int i = 0; i < keyValues.length; i += 2) { + properties.getKeyValues().put(keyValues[i], keyValues[i + 1]); + } + return new PropertiesObservationFilterPredicate(properties); + } + + private static PropertiesObservationFilterPredicate createPredicate(String... disabledNames) { + ObservationProperties properties = new ObservationProperties(); + for (String name : disabledNames) { + properties.getEnable().put(name, false); + } + return new PropertiesObservationFilterPredicate(properties); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientHttpObservationConventionAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientHttpObservationConventionAdapterTests.java deleted file mode 100644 index 814e71a4c720..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientHttpObservationConventionAdapterTests.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.observation.web.client; - -import java.net.URI; - -import io.micrometer.common.KeyValue; -import io.micrometer.observation.Observation; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.actuate.metrics.web.client.DefaultRestTemplateExchangeTagsProvider; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.client.ClientHttpRequest; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.http.client.observation.ClientRequestObservationContext; -import org.springframework.mock.http.client.MockClientHttpRequest; -import org.springframework.mock.http.client.MockClientHttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ClientHttpObservationConventionAdapter}. - * - * @author Brian Clozel - */ -@SuppressWarnings({ "deprecation", "removal" }) -class ClientHttpObservationConventionAdapterTests { - - private static final String TEST_METRIC_NAME = "test.metric.name"; - - private final ClientHttpObservationConventionAdapter convention = new ClientHttpObservationConventionAdapter( - TEST_METRIC_NAME, new DefaultRestTemplateExchangeTagsProvider()); - - private final ClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, URI.create("/resource/test")); - - private final ClientHttpResponse response = new MockClientHttpResponse("foo".getBytes(), HttpStatus.OK); - - private ClientRequestObservationContext context; - - @BeforeEach - void setup() { - this.context = new ClientRequestObservationContext(this.request); - this.context.setResponse(this.response); - this.context.setUriTemplate("/resource/{name}"); - } - - @Test - void shouldUseConfiguredName() { - assertThat(this.convention.getName()).isEqualTo(TEST_METRIC_NAME); - } - - @Test - void shouldOnlySupportClientHttpObservationContext() { - assertThat(this.convention.supportsContext(this.context)).isTrue(); - assertThat(this.convention.supportsContext(new OtherContext())).isFalse(); - } - - @Test - void shouldPushTagsAsLowCardinalityKeyValues() { - assertThat(this.convention.getLowCardinalityKeyValues(this.context)).contains(KeyValue.of("status", "200"), - KeyValue.of("outcome", "SUCCESS"), KeyValue.of("uri", "/resource/{name}"), - KeyValue.of("method", "GET")); - } - - @Test - void shouldNotPushAnyHighCardinalityKeyValue() { - assertThat(this.convention.getHighCardinalityKeyValues(this.context)).isEmpty(); - } - - static class OtherContext extends Observation.Context { - - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapterTests.java deleted file mode 100644 index d4560ed10406..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapterTests.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2012-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.observation.web.client; - -import java.net.URI; - -import io.micrometer.common.KeyValue; -import io.micrometer.observation.Observation; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.actuate.metrics.web.reactive.client.DefaultWebClientExchangeTagsProvider; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.web.reactive.function.client.ClientRequest; -import org.springframework.web.reactive.function.client.ClientRequestObservationContext; -import org.springframework.web.reactive.function.client.ClientResponse; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ClientObservationConventionAdapter}. - * - * @author Brian Clozel - */ -@SuppressWarnings({ "deprecation", "removal" }) -class ClientObservationConventionAdapterTests { - - private static final String TEST_METRIC_NAME = "test.metric.name"; - - private final ClientObservationConventionAdapter convention = new ClientObservationConventionAdapter( - TEST_METRIC_NAME, new DefaultWebClientExchangeTagsProvider()); - - private final ClientRequest.Builder requestBuilder = ClientRequest - .create(HttpMethod.GET, URI.create("/resource/test")) - .attribute(WebClient.class.getName() + ".uriTemplate", "/resource/{name}"); - - private final ClientResponse response = ClientResponse.create(HttpStatus.OK).body("foo").build(); - - private ClientRequestObservationContext context; - - @BeforeEach - void setup() { - this.context = new ClientRequestObservationContext(); - this.context.setCarrier(this.requestBuilder); - this.context.setResponse(this.response); - this.context.setUriTemplate("/resource/{name}"); - } - - @Test - void shouldUseConfiguredName() { - assertThat(this.convention.getName()).isEqualTo(TEST_METRIC_NAME); - } - - @Test - void shouldOnlySupportClientObservationContext() { - assertThat(this.convention.supportsContext(this.context)).isTrue(); - assertThat(this.convention.supportsContext(new OtherContext())).isFalse(); - } - - @Test - void shouldPushTagsAsLowCardinalityKeyValues() { - this.context.setRequest(this.requestBuilder.build()); - assertThat(this.convention.getLowCardinalityKeyValues(this.context)).contains(KeyValue.of("status", "200"), - KeyValue.of("outcome", "SUCCESS"), KeyValue.of("uri", "/resource/{name}"), - KeyValue.of("method", "GET")); - } - - @Test - void doesNotFailWithEmptyRequest() { - this.context.setUriTemplate(null); - assertThat(this.convention.getLowCardinalityKeyValues(this.context)).contains(KeyValue.of("status", "200"), - KeyValue.of("outcome", "SUCCESS"), KeyValue.of("uri", "/resource/{name}"), - KeyValue.of("method", "GET")); - } - - @Test - void shouldNotPushAnyHighCardinalityKeyValue() { - assertThat(this.convention.getHighCardinalityKeyValues(this.context)).isEmpty(); - } - - static class OtherContext extends Observation.Context { - - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestClientObservationConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestClientObservationConfigurationTests.java new file mode 100644 index 000000000000..1400c4f6c027 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestClientObservationConfigurationTests.java @@ -0,0 +1,175 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.observation.web.client; + +import io.micrometer.common.KeyValues; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.tck.TestObservationRegistry; +import io.micrometer.observation.tck.TestObservationRegistryAssert; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun; +import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; +import org.springframework.boot.actuate.metrics.web.client.ObservationRestClientCustomizer; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.boot.test.web.client.MockServerRestClientCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.observation.ClientRequestObservationContext; +import org.springframework.http.client.observation.DefaultClientRequestObservationConvention; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClient.Builder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus; + +/** + * Tests for {@link RestClientObservationConfiguration}. + * + * @author Brian Clozel + * @author Moritz Halbritter + */ +@ExtendWith(OutputCaptureExtension.class) +class RestClientObservationConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withBean(ObservationRegistry.class, TestObservationRegistry::create) + .withConfiguration(AutoConfigurations.of(ObservationAutoConfiguration.class, RestClientAutoConfiguration.class, + HttpClientObservationsAutoConfiguration.class)); + + @Test + void contributesCustomizerBean() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ObservationRestClientCustomizer.class)); + } + + @Test + void restClientCreatedWithBuilderIsInstrumented() { + this.contextRunner.run((context) -> { + RestClient restClient = buildRestClient(context); + restClient.get().uri("/projects/{project}", "spring-boot").retrieve().toBodilessEntity(); + TestObservationRegistry registry = context.getBean(TestObservationRegistry.class); + TestObservationRegistryAssert.assertThat(registry) + .hasObservationWithNameEqualToIgnoringCase("http.client.requests"); + }); + } + + @Test + void restClientCreatedWithBuilderUsesCustomConventionName() { + final String observationName = "test.metric.name"; + this.contextRunner.withPropertyValues("management.observations.http.client.requests.name=" + observationName) + .run((context) -> { + RestClient restClient = buildRestClient(context); + restClient.get().uri("/projects/{project}", "spring-boot").retrieve().toBodilessEntity(); + TestObservationRegistry registry = context.getBean(TestObservationRegistry.class); + TestObservationRegistryAssert.assertThat(registry) + .hasObservationWithNameEqualToIgnoringCase(observationName); + }); + } + + @Test + void restClientCreatedWithBuilderUsesCustomConvention() { + this.contextRunner.withUserConfiguration(CustomConvention.class).run((context) -> { + RestClient restClient = buildRestClient(context); + restClient.get().uri("/projects/{project}", "spring-boot").retrieve().toBodilessEntity(); + TestObservationRegistry registry = context.getBean(TestObservationRegistry.class); + TestObservationRegistryAssert.assertThat(registry) + .hasObservationWithNameEqualTo("http.client.requests") + .that() + .hasLowCardinalityKeyValue("project", "spring-boot"); + }); + } + + @Test + void afterMaxUrisReachedFurtherUrisAreDenied(CapturedOutput output) { + this.contextRunner.with(MetricsRun.simple()) + .withPropertyValues("management.metrics.web.client.max-uri-tags=2") + .run((context) -> { + RestClientWithMockServer restClientWithMockServer = buildRestClientAndMockServer(context); + MockRestServiceServer server = restClientWithMockServer.mockServer(); + RestClient restClient = restClientWithMockServer.restClient(); + for (int i = 0; i < 3; i++) { + server.expect(requestTo("/test/" + i)).andRespond(withStatus(HttpStatus.OK)); + } + for (int i = 0; i < 3; i++) { + restClient.get().uri("/test/" + i, String.class).retrieve().toBodilessEntity(); + } + TestObservationRegistry registry = context.getBean(TestObservationRegistry.class); + TestObservationRegistryAssert.assertThat(registry) + .hasNumberOfObservationsWithNameEqualTo("http.client.requests", 3); + MeterRegistry meterRegistry = context.getBean(MeterRegistry.class); + assertThat(meterRegistry.find("http.client.requests").timers()).hasSize(2); + assertThat(output).contains("Reached the maximum number of URI tags for 'http.client.requests'.") + .contains("Are you using 'uriVariables'?"); + }); + } + + @Test + void backsOffWhenRestClientBuilderIsMissing() { + new ApplicationContextRunner().with(MetricsRun.simple()) + .withConfiguration(AutoConfigurations.of(ObservationAutoConfiguration.class, + HttpClientObservationsAutoConfiguration.class)) + .run((context) -> assertThat(context).doesNotHaveBean(ObservationRestClientCustomizer.class)); + } + + private RestClient buildRestClient(AssertableApplicationContext context) { + RestClientWithMockServer restClientWithMockServer = buildRestClientAndMockServer(context); + restClientWithMockServer.mockServer() + .expect(requestTo("/projects/spring-boot")) + .andRespond(withStatus(HttpStatus.OK)); + return restClientWithMockServer.restClient(); + } + + private RestClientWithMockServer buildRestClientAndMockServer(AssertableApplicationContext context) { + Builder builder = context.getBean(Builder.class); + MockServerRestClientCustomizer customizer = new MockServerRestClientCustomizer(); + customizer.customize(builder); + return new RestClientWithMockServer(builder.build(), customizer.getServer()); + } + + private record RestClientWithMockServer(RestClient restClient, MockRestServiceServer mockServer) { + } + + @Configuration(proxyBeanMethods = false) + static class CustomConventionConfiguration { + + @Bean + CustomConvention customConvention() { + return new CustomConvention(); + } + + } + + static class CustomConvention extends DefaultClientRequestObservationConvention { + + @Override + public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) { + return super.getLowCardinalityKeyValues(context).and("project", "spring-boot"); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestClientObservationConfigurationWithoutMetricsTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestClientObservationConfigurationWithoutMetricsTests.java new file mode 100644 index 000000000000..3aa82c08c26b --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestClientObservationConfigurationWithoutMetricsTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.observation.web.client; + +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.tck.TestObservationRegistry; +import io.micrometer.observation.tck.TestObservationRegistryAssert; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.boot.test.web.client.MockServerRestClientCustomizer; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; +import org.springframework.http.HttpStatus; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClient.Builder; + +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus; + +/** + * Tests for {@link RestClientObservationConfiguration} without Micrometer Metrics. + * + * @author Brian Clozel + * @author Andy Wilkinson + * @author Moritz Halbritter + */ +@ExtendWith(OutputCaptureExtension.class) +@ClassPathExclusions("micrometer-core-*.jar") +class RestClientObservationConfigurationWithoutMetricsTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withBean(ObservationRegistry.class, TestObservationRegistry::create) + .withConfiguration(AutoConfigurations.of(ObservationAutoConfiguration.class, RestClientAutoConfiguration.class, + HttpClientObservationsAutoConfiguration.class)); + + @Test + void restClientCreatedWithBuilderIsInstrumented() { + this.contextRunner.run((context) -> { + RestClient restClient = buildRestClient(context); + restClient.get().uri("/projects/{project}", "spring-boot").retrieve().toBodilessEntity(); + TestObservationRegistry registry = context.getBean(TestObservationRegistry.class); + TestObservationRegistryAssert.assertThat(registry) + .hasObservationWithNameEqualToIgnoringCase("http.client.requests"); + }); + } + + private RestClient buildRestClient(AssertableApplicationContext context) { + Builder builder = context.getBean(Builder.class); + MockServerRestClientCustomizer customizer = new MockServerRestClientCustomizer(); + customizer.customize(builder); + customizer.getServer().expect(requestTo("/projects/spring-boot")).andRespond(withStatus(HttpStatus.OK)); + return builder.build(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestTemplateObservationConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestTemplateObservationConfigurationTests.java index 6116649a14dc..92b4367cad0c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestTemplateObservationConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestTemplateObservationConfigurationTests.java @@ -18,8 +18,6 @@ import io.micrometer.common.KeyValues; import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Tag; -import io.micrometer.core.instrument.Tags; import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.tck.TestObservationRegistry; import io.micrometer.observation.tck.TestObservationRegistryAssert; @@ -28,9 +26,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun; import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; -import org.springframework.boot.actuate.metrics.web.client.DefaultRestTemplateExchangeTagsProvider; import org.springframework.boot.actuate.metrics.web.client.ObservationRestTemplateCustomizer; -import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTagsProvider; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; @@ -40,9 +36,7 @@ import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; -import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.observation.ClientRequestObservationContext; import org.springframework.http.client.observation.DefaultClientRequestObservationConvention; import org.springframework.test.web.client.MockRestServiceServer; @@ -58,7 +52,6 @@ * @author Brian Clozel */ @ExtendWith(OutputCaptureExtension.class) -@SuppressWarnings("removal") class RestTemplateObservationConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() @@ -68,8 +61,7 @@ class RestTemplateObservationConfigurationTests { @Test void contributesCustomizerBean() { - this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ObservationRestTemplateCustomizer.class) - .doesNotHaveBean(DefaultRestTemplateExchangeTagsProvider.class)); + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ObservationRestTemplateCustomizer.class)); } @Test @@ -96,32 +88,6 @@ void restTemplateCreatedWithBuilderUsesCustomConventionName() { }); } - @Test - void restTemplateCreatedWithBuilderUsesCustomMetricName() { - final String metricName = "test.metric.name"; - this.contextRunner.withPropertyValues("management.metrics.web.client.request.metric-name=" + metricName) - .run((context) -> { - RestTemplate restTemplate = buildRestTemplate(context); - restTemplate.getForEntity("/projects/{project}", Void.class, "spring-boot"); - TestObservationRegistry registry = context.getBean(TestObservationRegistry.class); - TestObservationRegistryAssert.assertThat(registry) - .hasObservationWithNameEqualToIgnoringCase(metricName); - }); - } - - @Test - void restTemplateCreatedWithBuilderUsesCustomTagsProvider() { - this.contextRunner.withUserConfiguration(CustomTagsConfiguration.class).run((context) -> { - RestTemplate restTemplate = buildRestTemplate(context); - restTemplate.getForEntity("/projects/{project}", Void.class, "spring-boot"); - TestObservationRegistry registry = context.getBean(TestObservationRegistry.class); - TestObservationRegistryAssert.assertThat(registry) - .hasObservationWithNameEqualTo("http.client.requests") - .that() - .hasLowCardinalityKeyValue("project", "spring-boot"); - }); - } - @Test void restTemplateCreatedWithBuilderUsesCustomConvention() { this.contextRunner.withUserConfiguration(CustomConvention.class).run((context) -> { @@ -163,8 +129,7 @@ void backsOffWhenRestTemplateBuilderIsMissing() { new ApplicationContextRunner().with(MetricsRun.simple()) .withConfiguration(AutoConfigurations.of(ObservationAutoConfiguration.class, HttpClientObservationsAutoConfiguration.class)) - .run((context) -> assertThat(context).doesNotHaveBean(DefaultRestTemplateExchangeTagsProvider.class) - .doesNotHaveBean(ObservationRestTemplateCustomizer.class)); + .run((context) -> assertThat(context).doesNotHaveBean(ObservationRestTemplateCustomizer.class)); } private RestTemplate buildRestTemplate(AssertableApplicationContext context) { @@ -174,26 +139,6 @@ private RestTemplate buildRestTemplate(AssertableApplicationContext context) { return restTemplate; } - @Configuration(proxyBeanMethods = false) - static class CustomTagsConfiguration { - - @Bean - CustomTagsProvider customTagsProvider() { - return new CustomTagsProvider(); - } - - } - - @Deprecated(since = "3.0.0", forRemoval = true) - static class CustomTagsProvider implements RestTemplateExchangeTagsProvider { - - @Override - public Iterable getTags(String urlTemplate, HttpRequest request, ClientHttpResponse response) { - return Tags.of("project", "spring-boot"); - } - - } - @Configuration(proxyBeanMethods = false) static class CustomConventionConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/WebClientObservationConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/WebClientObservationConfigurationTests.java index 9a94712edc78..8a917298b0e0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/WebClientObservationConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/WebClientObservationConfigurationTests.java @@ -29,9 +29,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun; import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; -import org.springframework.boot.actuate.metrics.web.reactive.client.DefaultWebClientExchangeTagsProvider; import org.springframework.boot.actuate.metrics.web.reactive.client.ObservationWebClientCustomizer; -import org.springframework.boot.actuate.metrics.web.reactive.client.WebClientExchangeTagsProvider; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; @@ -59,7 +57,6 @@ * @author Stephane Nicoll */ @ExtendWith(OutputCaptureExtension.class) -@SuppressWarnings("removal") class WebClientObservationConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().with(MetricsRun.simple()) @@ -69,8 +66,7 @@ class WebClientObservationConfigurationTests { @Test void contributesCustomizerBean() { - this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ObservationWebClientCustomizer.class) - .doesNotHaveBean(DefaultWebClientExchangeTagsProvider.class)); + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ObservationWebClientCustomizer.class)); } @Test @@ -82,14 +78,6 @@ void webClientCreatedWithBuilderIsInstrumented() { }); } - @Test - void shouldNotOverrideCustomTagsProvider() { - this.contextRunner.withUserConfiguration(CustomTagsProviderConfig.class) - .run((context) -> assertThat(context).getBeans(WebClientExchangeTagsProvider.class) - .hasSize(1) - .containsKey("customTagsProvider")); - } - @Test void shouldUseCustomConventionIfAvailable() { this.contextRunner.withUserConfiguration(CustomConvention.class).run((context) -> { @@ -170,16 +158,6 @@ private WebClient mockWebClient(WebClient.Builder builder) { return builder.clientConnector(connector).build(); } - @Configuration(proxyBeanMethods = false) - static class CustomTagsProviderConfig { - - @Bean - WebClientExchangeTagsProvider customTagsProvider() { - return mock(WebClientExchangeTagsProvider.class); - } - - } - @Configuration(proxyBeanMethods = false) static class CustomConventionConfig { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapterTests.java deleted file mode 100644 index 9d32817dc80c..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapterTests.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.observation.web.reactive; - -import java.util.Map; - -import io.micrometer.common.KeyValue; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.actuate.metrics.web.reactive.server.DefaultWebFluxTagsProvider; -import org.springframework.http.server.reactive.observation.ServerRequestObservationContext; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.http.server.reactive.MockServerHttpResponse; -import org.springframework.web.reactive.HandlerMapping; -import org.springframework.web.util.pattern.PathPatternParser; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ServerRequestObservationConventionAdapter}. - * - * @author Brian Clozel - */ -@SuppressWarnings("removal") -@Deprecated(since = "3.0.0", forRemoval = true) -class ServerRequestObservationConventionAdapterTests { - - private static final String TEST_METRIC_NAME = "test.metric.name"; - - private final ServerRequestObservationConventionAdapter convention = new ServerRequestObservationConventionAdapter( - TEST_METRIC_NAME, new DefaultWebFluxTagsProvider()); - - @Test - void shouldUseConfiguredName() { - assertThat(this.convention.getName()).isEqualTo(TEST_METRIC_NAME); - } - - @Test - void shouldPushTagsAsLowCardinalityKeyValues() { - MockServerHttpRequest request = MockServerHttpRequest.get("/resource/test").build(); - MockServerHttpResponse response = new MockServerHttpResponse(); - ServerRequestObservationContext context = new ServerRequestObservationContext(request, response, - Map.of(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, - PathPatternParser.defaultInstance.parse("/resource/{name}"))); - assertThat(this.convention.getLowCardinalityKeyValues(context)).contains(KeyValue.of("status", "200"), - KeyValue.of("outcome", "SUCCESS"), KeyValue.of("uri", "/resource/{name}"), - KeyValue.of("method", "GET")); - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java index 88609e23f686..384be8d4ae30 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,40 +16,28 @@ package org.springframework.boot.actuate.autoconfigure.observation.web.reactive; -import java.util.List; +import java.time.Duration; +import java.time.temporal.ChronoUnit; import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Tag; -import io.micrometer.core.instrument.Tags; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import reactor.core.publisher.Mono; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun; import org.springframework.boot.actuate.autoconfigure.metrics.web.TestController; import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; -import org.springframework.boot.actuate.metrics.web.reactive.server.DefaultWebFluxTagsProvider; -import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsContributor; -import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsProvider; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; import org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.filter.reactive.ServerHttpObservationFilter; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebFilter; -import org.springframework.web.server.WebFilterChain; +import org.springframework.http.server.reactive.observation.ServerRequestObservationConvention; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; /** * Tests for {@link WebFluxObservationAutoConfiguration} @@ -57,9 +45,9 @@ * @author Brian Clozel * @author Dmytro Nosan * @author Madhura Bhave + * @author Moritz Halbritter */ @ExtendWith(OutputCaptureExtension.class) -@SuppressWarnings("removal") class WebFluxObservationAutoConfigurationTests { private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() @@ -67,53 +55,6 @@ class WebFluxObservationAutoConfigurationTests { .withConfiguration( AutoConfigurations.of(ObservationAutoConfiguration.class, WebFluxObservationAutoConfiguration.class)); - @Test - void shouldProvideWebFluxObservationFilter() { - this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ServerHttpObservationFilter.class)); - } - - @Test - void shouldProvideWebFluxObservationFilterOrdered() { - this.contextRunner.withBean(FirstWebFilter.class).withBean(ThirdWebFilter.class).run((context) -> { - List webFilters = context.getBeanProvider(WebFilter.class).orderedStream().toList(); - assertThat(webFilters.get(0)).isInstanceOf(FirstWebFilter.class); - assertThat(webFilters.get(1)).isInstanceOf(ServerHttpObservationFilter.class); - assertThat(webFilters.get(2)).isInstanceOf(ThirdWebFilter.class); - }); - } - - @Test - void shouldUseConventionAdapterWhenCustomTagsProvider() { - this.contextRunner.withUserConfiguration(CustomTagsProviderConfiguration.class).run((context) -> { - assertThat(context).hasSingleBean(ServerHttpObservationFilter.class); - assertThat(context).hasSingleBean(WebFluxTagsProvider.class); - assertThat(context).getBean(ServerHttpObservationFilter.class) - .extracting("observationConvention") - .isInstanceOf(ServerRequestObservationConventionAdapter.class); - }); - } - - @Test - void shouldUseConventionAdapterWhenCustomTagsContributor() { - this.contextRunner.withUserConfiguration(CustomTagsContributorConfiguration.class).run((context) -> { - assertThat(context).hasSingleBean(ServerHttpObservationFilter.class); - assertThat(context).hasSingleBean(WebFluxTagsContributor.class); - assertThat(context).getBean(ServerHttpObservationFilter.class) - .extracting("observationConvention") - .isInstanceOf(ServerRequestObservationConventionAdapter.class); - }); - } - - @Test - void shouldUseCustomConventionWhenAvailable() { - this.contextRunner.withUserConfiguration(CustomConventionConfiguration.class).run((context) -> { - assertThat(context).hasSingleBean(ServerHttpObservationFilter.class); - assertThat(context).getBean(ServerHttpObservationFilter.class) - .extracting("observationConvention") - .isInstanceOf(CustomConvention.class); - }); - } - @Test void afterMaxUrisReachedFurtherUrisAreDenied(CapturedOutput output) { this.contextRunner.withUserConfiguration(TestController.class) @@ -127,21 +68,6 @@ void afterMaxUrisReachedFurtherUrisAreDenied(CapturedOutput output) { }); } - @Test - @Deprecated(since = "3.0.0", forRemoval = true) - void afterMaxUrisReachedFurtherUrisAreDeniedWhenUsingCustomMetricName(CapturedOutput output) { - this.contextRunner.withUserConfiguration(TestController.class) - .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class, ObservationAutoConfiguration.class, - WebFluxAutoConfiguration.class)) - .withPropertyValues("management.metrics.web.server.max-uri-tags=2", - "management.metrics.web.server.request.metric-name=my.http.server.requests") - .run((context) -> { - MeterRegistry registry = getInitializedMeterRegistry(context); - assertThat(registry.get("my.http.server.requests").meters()).hasSizeLessThanOrEqualTo(2); - assertThat(output).contains("Reached the maximum number of URI tags for 'my.http.server.requests'"); - }); - } - @Test void afterMaxUrisReachedFurtherUrisAreDeniedWhenUsingCustomObservationName(CapturedOutput output) { this.contextRunner.withUserConfiguration(TestController.class) @@ -150,7 +76,7 @@ void afterMaxUrisReachedFurtherUrisAreDeniedWhenUsingCustomObservationName(Captu .withPropertyValues("management.metrics.web.server.max-uri-tags=2", "management.observations.http.server.requests.name=my.http.server.requests") .run((context) -> { - MeterRegistry registry = getInitializedMeterRegistry(context); + MeterRegistry registry = getInitializedMeterRegistry(context, "my.http.server.requests"); assertThat(registry.get("my.http.server.requests").meters()).hasSizeLessThanOrEqualTo(2); assertThat(output).contains("Reached the maximum number of URI tags for 'my.http.server.requests'"); }); @@ -169,84 +95,39 @@ void shouldNotDenyNorLogIfMaxUrisIsNotReached(CapturedOutput output) { }); } - private MeterRegistry getInitializedMeterRegistry(AssertableReactiveWebApplicationContext context) - throws Exception { - return getInitializedMeterRegistry(context, "/test0", "/test1", "/test2"); - } - - private MeterRegistry getInitializedMeterRegistry(AssertableReactiveWebApplicationContext context, String... urls) - throws Exception { - assertThat(context).hasSingleBean(ServerHttpObservationFilter.class); - WebTestClient client = WebTestClient.bindToApplicationContext(context).build(); - for (String url : urls) { - client.get().uri(url).exchange().expectStatus().isOk(); - } - return context.getBean(MeterRegistry.class); - } - - @Deprecated(since = "3.0.0", forRemoval = true) - @Configuration(proxyBeanMethods = false) - static class CustomTagsProviderConfiguration { - - @Bean - WebFluxTagsProvider tagsProvider() { - return new DefaultWebFluxTagsProvider(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class CustomTagsContributorConfiguration { - - @Bean - WebFluxTagsContributor tagsContributor() { - return new CustomTagsContributor(); - } - - } - - @Deprecated(since = "3.0.0", forRemoval = true) - static class CustomTagsContributor implements WebFluxTagsContributor { - - @Override - public Iterable httpRequestTags(ServerWebExchange exchange, Throwable ex) { - return Tags.of("custom", "testvalue"); - } - - } - - @Configuration(proxyBeanMethods = false) - static class CustomConventionConfiguration { - - @Bean - CustomConvention customConvention() { - return new CustomConvention(); - } - + @Test + void shouldSupplyDefaultServerRequestObservationConvention() { + this.contextRunner.withPropertyValues("management.observations.http.server.requests.name=some-other-name") + .run((context) -> { + assertThat(context).hasSingleBean(DefaultServerRequestObservationConvention.class); + DefaultServerRequestObservationConvention bean = context + .getBean(DefaultServerRequestObservationConvention.class); + assertThat(bean.getName()).isEqualTo("some-other-name"); + }); } - static class CustomConvention extends DefaultServerRequestObservationConvention { - + @Test + void shouldBackOffOnCustomServerRequestObservationConvention() { + this.contextRunner + .withBean("customServerRequestObservationConvention", ServerRequestObservationConvention.class, + () -> mock(ServerRequestObservationConvention.class)) + .run((context) -> { + assertThat(context).hasBean("customServerRequestObservationConvention"); + assertThat(context).hasSingleBean(ServerRequestObservationConvention.class); + }); } - @Order(Ordered.HIGHEST_PRECEDENCE) - static class FirstWebFilter implements WebFilter { - - @Override - public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - return chain.filter(exchange); - } - + private MeterRegistry getInitializedMeterRegistry(AssertableReactiveWebApplicationContext context) { + return getInitializedMeterRegistry(context, "http.server.requests"); } - @Order(Ordered.HIGHEST_PRECEDENCE + 2) - static class ThirdWebFilter implements WebFilter { - - @Override - public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - return chain.filter(exchange); - } - + private MeterRegistry getInitializedMeterRegistry(AssertableReactiveWebApplicationContext context, + String metricName) { + MeterRegistry meterRegistry = context.getBean(MeterRegistry.class); + meterRegistry.timer(metricName, "uri", "/test0").record(Duration.of(500, ChronoUnit.SECONDS)); + meterRegistry.timer(metricName, "uri", "/test1").record(Duration.of(500, ChronoUnit.SECONDS)); + meterRegistry.timer(metricName, "uri", "/test2").record(Duration.of(500, ChronoUnit.SECONDS)); + return meterRegistry; } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/ServerRequestObservationConventionAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/ServerRequestObservationConventionAdapterTests.java deleted file mode 100644 index 9705829af336..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/ServerRequestObservationConventionAdapterTests.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.observation.web.servlet; - -import java.util.Collections; -import java.util.List; - -import io.micrometer.common.KeyValue; -import io.micrometer.core.instrument.Tag; -import io.micrometer.core.instrument.Tags; -import io.micrometer.observation.Observation; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider; -import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsContributor; -import org.springframework.http.server.observation.ServerRequestObservationContext; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.web.servlet.HandlerMapping; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ServerRequestObservationConventionAdapter} - * - * @author Brian Clozel - */ -@SuppressWarnings("removal") -@Deprecated(since = "3.0.0", forRemoval = true) -class ServerRequestObservationConventionAdapterTests { - - private static final String TEST_METRIC_NAME = "test.metric.name"; - - private final ServerRequestObservationConventionAdapter convention = new ServerRequestObservationConventionAdapter( - TEST_METRIC_NAME, new DefaultWebMvcTagsProvider(), Collections.emptyList()); - - private final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/resource/test"); - - private final MockHttpServletResponse response = new MockHttpServletResponse(); - - private final ServerRequestObservationContext context = new ServerRequestObservationContext(this.request, - this.response); - - @Test - void customNameIsUsed() { - assertThat(this.convention.getName()).isEqualTo(TEST_METRIC_NAME); - } - - @Test - void onlySupportServerRequestObservationContext() { - assertThat(this.convention.supportsContext(this.context)).isTrue(); - assertThat(this.convention.supportsContext(new OtherContext())).isFalse(); - } - - @Test - void pushTagsAsLowCardinalityKeyValues() { - this.request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "/resource/{name}"); - this.context.setPathPattern("/resource/{name}"); - assertThat(this.convention.getLowCardinalityKeyValues(this.context)).contains(KeyValue.of("status", "200"), - KeyValue.of("outcome", "SUCCESS"), KeyValue.of("uri", "/resource/{name}"), - KeyValue.of("method", "GET")); - } - - @Test - void doesNotPushAnyHighCardinalityKeyValue() { - assertThat(this.convention.getHighCardinalityKeyValues(this.context)).isEmpty(); - } - - @Test - void pushTagsFromContributors() { - ServerRequestObservationConventionAdapter convention = new ServerRequestObservationConventionAdapter( - TEST_METRIC_NAME, null, List.of(new CustomWebMvcContributor())); - assertThat(convention.getLowCardinalityKeyValues(this.context)).contains(KeyValue.of("custom", "value")); - } - - static class OtherContext extends Observation.Context { - - } - - static class CustomWebMvcContributor implements WebMvcTagsContributor { - - @Override - public Iterable getTags(HttpServletRequest request, HttpServletResponse response, Object handler, - Throwable exception) { - return Tags.of("custom", "value"); - } - - @Override - public Iterable getLongRequestTags(HttpServletRequest request, Object handler) { - return Collections.emptyList(); - } - - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfigurationTests.java index a8fe9bf9c9f1..847b5882d771 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,12 @@ package org.springframework.boot.actuate.autoconfigure.observation.web.servlet; -import java.util.Collections; import java.util.EnumSet; import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Tag; import io.micrometer.observation.tck.TestObservationRegistry; import jakarta.servlet.DispatcherType; import jakarta.servlet.Filter; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -33,9 +29,6 @@ import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun; import org.springframework.boot.actuate.autoconfigure.metrics.web.TestController; import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; -import org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider; -import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsContributor; -import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; @@ -47,14 +40,11 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.http.server.observation.DefaultServerRequestObservationConvention; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import org.springframework.web.filter.ServerHttpObservationFilter; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link WebMvcObservationAutoConfiguration}. @@ -66,7 +56,6 @@ * @author Chanhyeong LEE */ @ExtendWith(OutputCaptureExtension.class) -@SuppressWarnings("removal") class WebMvcObservationAutoConfigurationTests { private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() @@ -84,21 +73,12 @@ void backsOffWhenMeterRegistryIsMissing() { @Test void definesFilterWhenRegistryIsPresent() { this.contextRunner.run((context) -> { - assertThat(context).doesNotHaveBean(DefaultWebMvcTagsProvider.class); assertThat(context).hasSingleBean(FilterRegistrationBean.class); assertThat(context.getBean(FilterRegistrationBean.class).getFilter()) .isInstanceOf(ServerHttpObservationFilter.class); }); } - @Test - void adapterConventionWhenTagsProviderPresent() { - this.contextRunner.withUserConfiguration(TagsProviderConfiguration.class) - .run((context) -> assertThat(context.getBean(FilterRegistrationBean.class).getFilter()) - .extracting("observationConvention") - .isInstanceOf(ServerRequestObservationConventionAdapter.class)); - } - @Test void customConventionWhenPresent() { this.contextRunner.withUserConfiguration(CustomConventionConfiguration.class) @@ -159,21 +139,6 @@ void afterMaxUrisReachedFurtherUrisAreDenied(CapturedOutput output) { }); } - @Test - @Deprecated(since = "3.0.0", forRemoval = true) - void afterMaxUrisReachedFurtherUrisAreDeniedWhenUsingCustomMetricName(CapturedOutput output) { - this.contextRunner.withUserConfiguration(TestController.class) - .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class, ObservationAutoConfiguration.class, - WebMvcAutoConfiguration.class)) - .withPropertyValues("management.metrics.web.server.max-uri-tags=2", - "management.metrics.web.server.request.metric-name=my.http.server.requests") - .run((context) -> { - MeterRegistry registry = getInitializedMeterRegistry(context); - assertThat(registry.get("my.http.server.requests").meters()).hasSizeLessThanOrEqualTo(2); - assertThat(output).contains("Reached the maximum number of URI tags for 'my.http.server.requests'"); - }); - } - @Test void afterMaxUrisReachedFurtherUrisAreDeniedWhenUsingCustomObservationName(CapturedOutput output) { this.contextRunner.withUserConfiguration(TestController.class) @@ -201,71 +166,21 @@ void shouldNotDenyNorLogIfMaxUrisIsNotReached(CapturedOutput output) { }); } - @Test - void whenTagContributorsAreDefinedThenTagsProviderUsesThem() { - this.contextRunner.withUserConfiguration(TagsContributorsConfiguration.class) - .run((context) -> assertThat(context.getBean(FilterRegistrationBean.class).getFilter()) - .extracting("observationConvention") - .isInstanceOf(ServerRequestObservationConventionAdapter.class)); - } - private MeterRegistry getInitializedMeterRegistry(AssertableWebApplicationContext context) throws Exception { return getInitializedMeterRegistry(context, "/test0", "/test1", "/test2"); } - private MeterRegistry getInitializedMeterRegistry(AssertableWebApplicationContext context, String... urls) - throws Exception { + private MeterRegistry getInitializedMeterRegistry(AssertableWebApplicationContext context, String... urls) { assertThat(context).hasSingleBean(FilterRegistrationBean.class); Filter filter = context.getBean(FilterRegistrationBean.class).getFilter(); assertThat(filter).isInstanceOf(ServerHttpObservationFilter.class); - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).addFilters(filter).build(); + MockMvcTester mvc = MockMvcTester.from(context, (builder) -> builder.addFilters(filter).build()); for (String url : urls) { - mockMvc.perform(MockMvcRequestBuilders.get(url)).andExpect(status().isOk()); + assertThat(mvc.get().uri(url)).hasStatusOk(); } return context.getBean(MeterRegistry.class); } - @Configuration(proxyBeanMethods = false) - static class TagsProviderConfiguration { - - @Bean - TestWebMvcTagsProvider tagsProvider() { - return new TestWebMvcTagsProvider(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class TagsContributorsConfiguration { - - @Bean - WebMvcTagsContributor tagContributorOne() { - return mock(WebMvcTagsContributor.class); - } - - @Bean - WebMvcTagsContributor tagContributorTwo() { - return mock(WebMvcTagsContributor.class); - } - - } - - @Deprecated(since = "3.0.0", forRemoval = true) - private static final class TestWebMvcTagsProvider implements WebMvcTagsProvider { - - @Override - public Iterable getTags(HttpServletRequest request, HttpServletResponse response, Object handler, - Throwable exception) { - return Collections.emptyList(); - } - - @Override - public Iterable getLongRequestTags(HttpServletRequest request, Object handler) { - return Collections.emptyList(); - } - - } - @Configuration(proxyBeanMethods = false) static class TestServerHttpObservationFilterRegistrationConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfigurationTests.java new file mode 100644 index 000000000000..6848c8003d16 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfigurationTests.java @@ -0,0 +1,174 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.opentelemetry; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.semconv.ResourceAttributes; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.context.annotation.ImportCandidates; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link OpenTelemetryAutoConfiguration}. + * + * @author Moritz Halbritter + */ +class OpenTelemetryAutoConfigurationTests { + + private final ApplicationContextRunner runner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)); + + @Test + void isRegisteredInAutoConfigurationImports() { + assertThat(ImportCandidates.load(AutoConfiguration.class, null).getCandidates()) + .contains(OpenTelemetryAutoConfiguration.class.getName()); + } + + @Test + void shouldProvideBeans() { + this.runner.run((context) -> { + assertThat(context).hasSingleBean(OpenTelemetrySdk.class); + assertThat(context).hasSingleBean(Resource.class); + }); + } + + @Test + void shouldBackOffIfOpenTelemetryIsNotOnClasspath() { + this.runner.withClassLoader(new FilteredClassLoader("io.opentelemetry")).run((context) -> { + assertThat(context).doesNotHaveBean(OpenTelemetrySdk.class); + assertThat(context).doesNotHaveBean(Resource.class); + }); + } + + @Test + void backsOffOnUserSuppliedBeans() { + this.runner.withUserConfiguration(UserConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(OpenTelemetry.class); + assertThat(context).hasBean("customOpenTelemetry"); + assertThat(context).hasSingleBean(Resource.class); + assertThat(context).hasBean("customResource"); + }); + } + + @Test + void shouldApplySpringApplicationNameToResource() { + this.runner.withPropertyValues("spring.application.name=my-application").run((context) -> { + Resource resource = context.getBean(Resource.class); + assertThat(resource.getAttributes().asMap()) + .contains(entry(ResourceAttributes.SERVICE_NAME, "my-application")); + }); + } + + @Test + void shouldApplySpringApplicationGroupToResource() { + this.runner.withPropertyValues("spring.application.group=my-group").run((context) -> { + Resource resource = context.getBean(Resource.class); + assertThat(resource.getAttributes().asMap()) + .contains(entry(AttributeKey.stringKey("service.group"), "my-group")); + }); + } + + @Test + void shouldNotApplySpringApplicationGroupIfNotSet() { + this.runner.run((context) -> { + Resource resource = context.getBean(Resource.class); + assertThat(resource.getAttributes().asMap()).doesNotContainKey(AttributeKey.stringKey("service.group")); + }); + } + + @Test + void shouldFallbackToDefaultApplicationNameIfSpringApplicationNameIsNotSet() { + this.runner.run((context) -> { + Resource resource = context.getBean(Resource.class); + assertThat(resource.getAttributes().asMap()) + .contains(entry(ResourceAttributes.SERVICE_NAME, "unknown_service")); + }); + } + + @Test + void shouldApplyResourceAttributesFromProperties() { + this.runner.withPropertyValues("management.opentelemetry.resource-attributes.region=us-west").run((context) -> { + Resource resource = context.getBean(Resource.class); + assertThat(resource.getAttributes().asMap()).contains(entry(AttributeKey.stringKey("region"), "us-west")); + }); + } + + @Test + void shouldRegisterSdkTracerProviderIfAvailable() { + this.runner.withBean(SdkTracerProvider.class, () -> SdkTracerProvider.builder().build()).run((context) -> { + OpenTelemetry openTelemetry = context.getBean(OpenTelemetry.class); + assertThat(openTelemetry.getTracerProvider()).isNotNull(); + }); + } + + @Test + void shouldRegisterContextPropagatorsIfAvailable() { + this.runner.withBean(ContextPropagators.class, ContextPropagators::noop).run((context) -> { + OpenTelemetry openTelemetry = context.getBean(OpenTelemetry.class); + assertThat(openTelemetry.getPropagators()).isNotNull(); + }); + } + + @Test + void shouldRegisterSdkLoggerProviderIfAvailable() { + this.runner.withBean(SdkLoggerProvider.class, () -> SdkLoggerProvider.builder().build()).run((context) -> { + OpenTelemetry openTelemetry = context.getBean(OpenTelemetry.class); + assertThat(openTelemetry.getLogsBridge()).isNotNull(); + }); + } + + @Test + void shouldRegisterSdkMeterProviderIfAvailable() { + this.runner.withBean(SdkMeterProvider.class, () -> SdkMeterProvider.builder().build()).run((context) -> { + OpenTelemetry openTelemetry = context.getBean(OpenTelemetry.class); + assertThat(openTelemetry.getMeterProvider()).isNotNull(); + }); + } + + @Configuration(proxyBeanMethods = false) + private static final class UserConfiguration { + + @Bean + OpenTelemetry customOpenTelemetry() { + return mock(OpenTelemetry.class); + } + + @Bean + Resource customResource() { + return Resource.getDefault(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryPropertiesTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryPropertiesTests.java new file mode 100644 index 000000000000..d7da608e9804 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryPropertiesTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.opentelemetry; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +/** + * Tests for {@link OpenTelemetryProperties}. + * + * @author Moritz Halbritter + */ +class OpenTelemetryPropertiesTests { + + private final ApplicationContextRunner runner = new ApplicationContextRunner().withPropertyValues( + "management.opentelemetry.resource-attributes.a=alpha", + "management.opentelemetry.resource-attributes.b=beta"); + + @Test + @ClassPathExclusions("opentelemetry-sdk-*") + void shouldNotDependOnOpenTelemetrySdk() { + this.runner.withUserConfiguration(TestConfiguration.class).run((context) -> { + OpenTelemetryProperties properties = context.getBean(OpenTelemetryProperties.class); + assertThat(properties.getResourceAttributes()).containsOnly(entry("a", "alpha"), entry("b", "beta")); + }); + } + + @Configuration(proxyBeanMethods = false) + @EnableConfigurationProperties(OpenTelemetryProperties.class) + private static final class TestConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfigurationTests.java new file mode 100644 index 000000000000..7f827a6343dc --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfigurationTests.java @@ -0,0 +1,117 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.r2dbc; + +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +import io.micrometer.observation.Observation.Context; +import io.micrometer.observation.ObservationHandler; +import io.micrometer.observation.ObservationRegistry; +import io.r2dbc.spi.ConnectionFactory; +import org.awaitility.Awaitility; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.r2dbc.ProxyConnectionFactoryCustomizer; +import org.springframework.boot.autoconfigure.r2dbc.R2dbcProxyAutoConfiguration; +import org.springframework.boot.context.annotation.ImportCandidates; +import org.springframework.boot.r2dbc.ConnectionFactoryBuilder; +import org.springframework.boot.r2dbc.ConnectionFactoryDecorator; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link R2dbcObservationAutoConfiguration}. + * + * @author Moritz Halbritter + * @author Tadaya Tsuyukubo + */ +class R2dbcObservationAutoConfigurationTests { + + private final ApplicationContextRunner runnerWithoutObservationRegistry = new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of(R2dbcProxyAutoConfiguration.class, R2dbcObservationAutoConfiguration.class)); + + private final ApplicationContextRunner runner = this.runnerWithoutObservationRegistry + .withBean(ObservationRegistry.class, ObservationRegistry::create); + + @Test + void shouldBeRegisteredInAutoConfigurationImports() { + assertThat(ImportCandidates.load(AutoConfiguration.class, null).getCandidates()) + .contains(R2dbcObservationAutoConfiguration.class.getName()); + } + + @Test + void shouldNotSupplyBeansIfObservationRegistryIsNotPresent() { + this.runnerWithoutObservationRegistry + .run((context) -> assertThat(context).doesNotHaveBean(ProxyConnectionFactoryCustomizer.class)); + } + + @Test + void decoratorShouldReportObservations() { + this.runner.run((context) -> { + CapturingObservationHandler handler = registerCapturingObservationHandler(context); + ConnectionFactoryDecorator decorator = context.getBean(ConnectionFactoryDecorator.class); + assertThat(decorator).isNotNull(); + ConnectionFactory connectionFactory = ConnectionFactoryBuilder + .withUrl("r2dbc:h2:mem:///" + UUID.randomUUID()) + .build(); + ConnectionFactory decorated = decorator.decorate(connectionFactory); + Mono.from(decorated.create()) + .flatMap((c) -> Mono.from(c.createStatement("SELECT 1;").execute()) + .flatMap((ignore) -> Mono.from(c.close()))) + .block(); + assertThat(handler.awaitContext().getName()).as("context.getName()").isEqualTo("r2dbc.query"); + }); + } + + private static CapturingObservationHandler registerCapturingObservationHandler( + AssertableApplicationContext context) { + ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); + assertThat(observationRegistry).isNotNull(); + CapturingObservationHandler handler = new CapturingObservationHandler(); + observationRegistry.observationConfig().observationHandler(handler); + return handler; + } + + private static final class CapturingObservationHandler implements ObservationHandler { + + private final AtomicReference context = new AtomicReference<>(); + + @Override + public boolean supportsContext(Context context) { + return true; + } + + @Override + public void onStart(Context context) { + this.context.set(context); + } + + Context awaitContext() { + return Awaitility.await().untilAtomic(this.context, Matchers.notNullValue()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/sbom/SbomEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/sbom/SbomEndpointAutoConfigurationTests.java new file mode 100644 index 000000000000..4352ec61c8b2 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/sbom/SbomEndpointAutoConfigurationTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.sbom; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.sbom.SbomEndpoint; +import org.springframework.boot.actuate.sbom.SbomEndpointWebExtension; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SbomEndpointAutoConfiguration}. + * + * @author Moritz Halbritter + */ +class SbomEndpointAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SbomEndpointAutoConfiguration.class)); + + @Test + void runShouldHaveEndpointBean() { + this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=sbom") + .run((context) -> assertThat(context).hasSingleBean(SbomEndpoint.class)); + } + + @Test + void runWhenNotExposedShouldNotHaveEndpointBean() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(SbomEndpoint.class)); + } + + @Test + void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() { + this.contextRunner.withPropertyValues("management.endpoint.sbom.enabled:false") + .withPropertyValues("management.endpoints.web.exposure.include=*") + .run((context) -> assertThat(context).doesNotHaveBean(SbomEndpoint.class)); + } + + @Test + void runWhenOnlyExposedOverJmxShouldHaveEndpointBeanWithoutWebExtension() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=info", "spring.jmx.enabled=true", + "management.endpoints.jmx.exposure.include=sbom") + .run((context) -> assertThat(context).hasSingleBean(SbomEndpoint.class) + .doesNotHaveBean(SbomEndpointWebExtension.class)); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/scheduling/ScheduledTasksObservabilityAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/scheduling/ScheduledTasksObservabilityAutoConfigurationTests.java new file mode 100644 index 000000000000..60ef5d14c5cc --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/scheduling/ScheduledTasksObservabilityAutoConfigurationTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.scheduling; + +import java.util.List; + +import io.micrometer.observation.ObservationRegistry; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.scheduling.ScheduledTasksObservabilityAutoConfiguration.ObservabilitySchedulingConfigurer; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.context.annotation.ImportCandidates; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ScheduledTasksObservabilityAutoConfiguration}. + * + * @author Moritz Halbritter + */ +class ScheduledTasksObservabilityAutoConfigurationTests { + + private final ApplicationContextRunner runner = new ApplicationContextRunner().withConfiguration(AutoConfigurations + .of(ObservationAutoConfiguration.class, ScheduledTasksObservabilityAutoConfiguration.class)); + + @Test + void shouldProvideObservabilitySchedulingConfigurer() { + this.runner.run((context) -> assertThat(context).hasSingleBean(ObservabilitySchedulingConfigurer.class)); + } + + @Test + void observabilitySchedulingConfigurerShouldConfigureObservationRegistry() { + ObservationRegistry observationRegistry = ObservationRegistry.create(); + ObservabilitySchedulingConfigurer configurer = new ObservabilitySchedulingConfigurer(observationRegistry); + ScheduledTaskRegistrar registrar = new ScheduledTaskRegistrar(); + configurer.configureTasks(registrar); + assertThat(registrar.getObservationRegistry()).isEqualTo(observationRegistry); + } + + @Test + void isRegisteredInAutoConfigurationsFile() { + List configurations = ImportCandidates.load(AutoConfiguration.class, null).getCandidates(); + assertThat(configurations).contains(ScheduledTasksObservabilityAutoConfiguration.class.getName()); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequestTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequestTests.java index f29594a43394..9ba6c9d1fce5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequestTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequestTests.java @@ -355,6 +355,7 @@ static class FooEndpoint { } @ServletEndpoint(id = "baz") + @SuppressWarnings("removal") static class BazServletEndpoint { } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java index fbfe9f23efe9..66b39ded5a73 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,6 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration; import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; -import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; @@ -48,6 +47,8 @@ import org.springframework.mock.http.server.reactive.MockServerHttpResponse; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.web.server.ServerWebExchange; @@ -70,17 +71,26 @@ class ReactiveManagementWebSecurityAutoConfigurationTests { HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class, WebFluxAutoConfiguration.class, EnvironmentEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, - ReactiveSecurityAutoConfiguration.class, ReactiveUserDetailsServiceAutoConfiguration.class, - ReactiveManagementWebSecurityAutoConfiguration.class)); + ReactiveSecurityAutoConfiguration.class, ReactiveManagementWebSecurityAutoConfiguration.class)); @Test void permitAllForHealth() { - this.contextRunner.run((context) -> assertThat(getAuthenticateHeader(context, "/actuator/health")).isNull()); + this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class) + .run((context) -> assertThat(getAuthenticateHeader(context, "/actuator/health")).isNull()); } @Test void securesEverythingElse() { + this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class).run((context) -> { + assertThat(getAuthenticateHeader(context, "/actuator").get(0)).contains("Basic realm="); + assertThat(getAuthenticateHeader(context, "/foo").toString()).contains("Basic realm="); + }); + } + + @Test + void noExistingAuthenticationManagerOrUserDetailsService() { this.contextRunner.run((context) -> { + assertThat(getAuthenticateHeader(context, "/actuator/health")).isNull(); assertThat(getAuthenticateHeader(context, "/actuator").get(0)).contains("Basic realm="); assertThat(getAuthenticateHeader(context, "/foo").toString()).contains("Basic realm="); }); @@ -88,10 +98,12 @@ void securesEverythingElse() { @Test void usesMatchersBasedOffConfiguredActuatorBasePath() { - this.contextRunner.withPropertyValues("management.endpoints.web.base-path=/").run((context) -> { - assertThat(getAuthenticateHeader(context, "/health")).isNull(); - assertThat(getAuthenticateHeader(context, "/foo").get(0)).contains("Basic realm="); - }); + this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class) + .withPropertyValues("management.endpoints.web.base-path=/") + .run((context) -> { + assertThat(getAuthenticateHeader(context, "/health")).isNull(); + assertThat(getAuthenticateHeader(context, "/foo").get(0)).contains("Basic realm="); + }); } @Test @@ -155,6 +167,17 @@ protected ServerWebExchange createExchange(ServerHttpRequest request, ServerHttp } + @Configuration(proxyBeanMethods = false) + static class UserDetailsServiceConfiguration { + + @Bean + MapReactiveUserDetailsService userDetailsService() { + return new MapReactiveUserDetailsService( + User.withUsername("alice").password("secret").roles("admin").build()); + } + + } + @Configuration(proxyBeanMethods = false) static class CustomSecurityConfiguration { @@ -168,6 +191,11 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { return http.build(); } + @Bean + ReactiveAuthenticationManager authenticationManager() { + return mock(ReactiveAuthenticationManager.class); + } + } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/AbstractEndpointRequestIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/AbstractEndpointRequestIntegrationTests.java index 35236a3c5696..b0860810ee94 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/AbstractEndpointRequestIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/AbstractEndpointRequestIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,7 +37,6 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; -import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; @@ -45,6 +44,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.reactive.server.WebTestClient; @@ -100,8 +101,8 @@ protected final WebApplicationContextRunner getContextRunner() { return createContextRunner().withPropertyValues("management.endpoints.web.exposure.include=*") .withUserConfiguration(BaseConfiguration.class, SecurityConfiguration.class) .withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, SecurityAutoConfiguration.class, - UserDetailsServiceAutoConfiguration.class, EndpointAutoConfiguration.class, - WebEndpointAutoConfiguration.class, ManagementContextAutoConfiguration.class)); + EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, + ManagementContextAutoConfiguration.class)); } @@ -177,6 +178,7 @@ Object getAll() { } @ServletEndpoint(id = "se1") + @SuppressWarnings("removal") static class TestServletEndpoint implements Supplier { @Override @@ -189,6 +191,12 @@ public EndpointServlet get() { @Configuration(proxyBeanMethods = false) static class SecurityConfiguration { + @Bean + InMemoryUserDetailsManager userDetailsManager() { + return new InMemoryUserDetailsManager( + User.withUsername("user").password("{noop}password").roles("admin").build()); + } + @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests((requests) -> { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java index 440b484b830a..d3d5cb448b7a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java @@ -354,6 +354,7 @@ static class FooEndpoint { } @ServletEndpoint(id = "baz") + @SuppressWarnings("removal") static class BazServletEndpoint { } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfigurationTests.java index 576fe6bbaee5..491e4e3d6186 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,20 @@ package org.springframework.boot.actuate.autoconfigure.session; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.session.ReactiveSessionsEndpoint; import org.springframework.boot.actuate.session.SessionsEndpoint; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.session.FindByIndexNameSessionRepository; +import org.springframework.session.ReactiveFindByIndexNameSessionRepository; +import org.springframework.session.ReactiveSessionRepository; +import org.springframework.session.SessionRepository; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -32,36 +38,117 @@ * Tests for {@link SessionsEndpointAutoConfiguration}. * * @author Vedran Pavic + * @author Moritz Halbritter */ class SessionsEndpointAutoConfigurationTests { - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(SessionsEndpointAutoConfiguration.class)) - .withUserConfiguration(SessionConfiguration.class); + @Nested + class ServletSessionEndpointConfigurationTests { - @Test - void runShouldHaveEndpointBean() { - this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=sessions") - .run((context) -> assertThat(context).hasSingleBean(SessionsEndpoint.class)); - } + private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SessionsEndpointAutoConfiguration.class)) + .withUserConfiguration(IndexedSessionRepositoryConfiguration.class); - @Test - void runWhenNotExposedShouldNotHaveEndpointBean() { - this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(SessionsEndpoint.class)); - } + @Test + void runShouldHaveEndpointBean() { + this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=sessions") + .run((context) -> assertThat(context).hasSingleBean(SessionsEndpoint.class)); + } + + @Test + void runWhenNoIndexedSessionRepositoryShouldHaveEndpointBean() { + new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SessionsEndpointAutoConfiguration.class)) + .withUserConfiguration(SessionRepositoryConfiguration.class) + .withPropertyValues("management.endpoints.web.exposure.include=sessions") + .run((context) -> assertThat(context).hasSingleBean(SessionsEndpoint.class)); + } + + @Test + void runWhenNotExposedShouldNotHaveEndpointBean() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(SessionsEndpoint.class)); + } + + @Test + void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() { + this.contextRunner.withPropertyValues("management.endpoint.sessions.enabled:false") + .run((context) -> assertThat(context).doesNotHaveBean(SessionsEndpoint.class)); + } + + @Configuration(proxyBeanMethods = false) + static class IndexedSessionRepositoryConfiguration { + + @Bean + FindByIndexNameSessionRepository sessionRepository() { + return mock(FindByIndexNameSessionRepository.class); + } + + } + + @Configuration(proxyBeanMethods = false) + static class SessionRepositoryConfiguration { + + @Bean + SessionRepository sessionRepository() { + return mock(SessionRepository.class); + } + + } - @Test - void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() { - this.contextRunner.withPropertyValues("management.endpoint.sessions.enabled:false") - .run((context) -> assertThat(context).doesNotHaveBean(SessionsEndpoint.class)); } - @Configuration(proxyBeanMethods = false) - static class SessionConfiguration { + @Nested + class ReactiveSessionEndpointConfigurationTests { + + private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SessionsEndpointAutoConfiguration.class)) + .withUserConfiguration(ReactiveSessionRepositoryConfiguration.class, + ReactiveIndexedSessionRepositoryConfiguration.class); + + @Test + void runShouldHaveEndpointBean() { + this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=sessions") + .run((context) -> assertThat(context).hasSingleBean(ReactiveSessionsEndpoint.class)); + } + + @Test + void runWhenNoIndexedSessionRepositoryShouldHaveEndpointBean() { + new ReactiveWebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SessionsEndpointAutoConfiguration.class)) + .withUserConfiguration(ReactiveSessionRepositoryConfiguration.class) + .withPropertyValues("management.endpoints.web.exposure.include=sessions") + .run((context) -> assertThat(context).hasSingleBean(ReactiveSessionsEndpoint.class)); + } + + @Test + void runWhenNotExposedShouldNotHaveEndpointBean() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ReactiveSessionsEndpoint.class)); + } + + @Test + void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() { + this.contextRunner.withPropertyValues("management.endpoint.sessions.enabled:false") + .run((context) -> assertThat(context).doesNotHaveBean(ReactiveSessionsEndpoint.class)); + } + + @Configuration(proxyBeanMethods = false) + static class ReactiveIndexedSessionRepositoryConfiguration { + + @Bean + ReactiveFindByIndexNameSessionRepository indexedSessionRepository() { + return mock(ReactiveFindByIndexNameSessionRepository.class); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ReactiveSessionRepositoryConfiguration { + + @Bean + ReactiveSessionRepository sessionRepository() { + return mock(ReactiveSessionRepository.class); + } - @Bean - FindByIndexNameSessionRepository sessionRepository() { - return mock(FindByIndexNameSessionRepository.class); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BaggagePropagationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BaggagePropagationIntegrationTests.java index 6a243e92284e..c4b2e29de47d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BaggagePropagationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BaggagePropagationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,14 +23,17 @@ import io.micrometer.tracing.Span; import io.micrometer.tracing.Tracer; import io.opentelemetry.context.Context; +import org.assertj.core.api.ThrowingConsumer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.slf4j.MDC; +import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.testsupport.classpath.ForkedClassPath; import org.springframework.context.ApplicationContext; import static org.assertj.core.api.Assertions.assertThat; @@ -40,11 +43,14 @@ * formats. * * @author Marcin Grzejszczak + * @author Moritz Halbritter */ +@ForkedClassPath class BaggagePropagationIntegrationTests { - static final String COUNTRY_CODE = "country-code"; - static final String BUSINESS_PROCESS = "bp"; + private static final String COUNTRY_CODE = "country-code"; + + private static final String BUSINESS_PROCESS = "bp"; @BeforeEach @AfterEach @@ -58,24 +64,23 @@ void shouldSetEntriesToMdcFromSpanWithBaggage(AutoConfig autoConfig) { autoConfig.get().run((context) -> { Tracer tracer = tracer(context); Span span = createSpan(tracer); + BaggageManager baggageManager = baggageManager(context); assertThatTracingContextIsInitialized(autoConfig); try (Tracer.SpanInScope scope = tracer.withSpan(span.start())) { - BaggageManager baggageManager = context.getBean(BaggageManager.class); + assertMdcValue("traceId", span.context().traceId()); try (BaggageInScope fo = baggageManager.createBaggageInScope(span.context(), COUNTRY_CODE, "FO"); BaggageInScope alm = baggageManager.createBaggageInScope(span.context(), BUSINESS_PROCESS, "ALM")) { - assertThat(MDC.get("traceId")).isEqualTo(span.context().traceId()); - assertThat(MDC.get(COUNTRY_CODE)).isEqualTo("FO"); - assertThat(MDC.get(BUSINESS_PROCESS)).isEqualTo("ALM"); + assertMdcValue(COUNTRY_CODE, "FO"); + assertMdcValue(BUSINESS_PROCESS, "ALM"); } } finally { span.end(); } - - assertThatMdcContainsUnsetTraceId(); - assertThat(MDC.get(COUNTRY_CODE)).isNull(); - assertThat(MDC.get(BUSINESS_PROCESS)).isNull(); + assertThatMdcContainsUnsetTraceId(autoConfig); + assertUnsetMdc(COUNTRY_CODE); + assertUnsetMdc(BUSINESS_PROCESS); }); } @@ -85,28 +90,25 @@ void shouldRemoveEntriesFromMdcForNullSpan(AutoConfig autoConfig) { autoConfig.get().run((context) -> { Tracer tracer = tracer(context); Span span = createSpan(tracer); + BaggageManager baggageManager = baggageManager(context); assertThatTracingContextIsInitialized(autoConfig); try (Tracer.SpanInScope scope = tracer.withSpan(span.start())) { - try (BaggageInScope fo = context.getBean(BaggageManager.class) - .createBaggageInScope(span.context(), COUNTRY_CODE, "FO")) { - - assertThat(MDC.get("traceId")).isEqualTo(span.context().traceId()); - assertThat(MDC.get(COUNTRY_CODE)).isEqualTo("FO"); - + assertMdcValue("traceId", span.context().traceId()); + try (BaggageInScope fo = baggageManager.createBaggageInScope(span.context(), COUNTRY_CODE, "FO")) { + assertMdcValue(COUNTRY_CODE, "FO"); try (Tracer.SpanInScope scope2 = tracer.withSpan(null)) { - assertThatMdcContainsUnsetTraceId(); - assertThat(MDC.get(COUNTRY_CODE)).isNull(); + assertThatMdcContainsUnsetTraceId(autoConfig); + assertUnsetMdc(COUNTRY_CODE); } - - assertThat(MDC.get("traceId")).isEqualTo(span.context().traceId()); - assertThat(MDC.get(COUNTRY_CODE)).isEqualTo("FO"); + assertMdcValue("traceId", span.context().traceId()); + assertMdcValue(COUNTRY_CODE, "FO"); } } finally { span.end(); } - assertThatMdcContainsUnsetTraceId(); - assertThat(MDC.get(COUNTRY_CODE)).isNull(); + assertThatMdcContainsUnsetTraceId(autoConfig); + assertUnsetMdc(COUNTRY_CODE); }); } @@ -118,22 +120,36 @@ private Tracer tracer(ApplicationContext context) { return context.getBean(Tracer.class); } + private BaggageManager baggageManager(ApplicationContext context) { + return context.getBean(BaggageManager.class); + } + private void assertThatTracingContextIsInitialized(AutoConfig autoConfig) { - if (autoConfig == AutoConfig.OTEL_B3) { + if (autoConfig.isOtel()) { assertThat(Context.current()).isEqualTo(Context.root()); } } - private void assertThatMdcContainsUnsetTraceId() { - assertThat(isInvalidBraveTraceId() || isInvalidOtelTraceId()).isTrue(); + private void assertThatMdcContainsUnsetTraceId(AutoConfig autoConfig) { + boolean eitherOtelOrBrave = autoConfig.isOtel() || autoConfig.isBrave(); + assertThat(eitherOtelOrBrave).isTrue(); + if (autoConfig.isOtel()) { + ThrowingConsumer isNull = (traceId) -> assertThat(traceId).isNull(); + ThrowingConsumer isZero = (traceId) -> assertThat(traceId) + .isEqualTo("00000000000000000000000000000000"); + assertThat(MDC.get("traceId")).satisfiesAnyOf(isNull, isZero); + } + if (autoConfig.isBrave()) { + assertThat(MDC.get("traceId")).isNull(); + } } - private boolean isInvalidBraveTraceId() { - return MDC.get("traceId") == null; + private void assertUnsetMdc(String key) { + assertThat(MDC.get(key)).as("MDC[%s]", key).isNull(); } - private boolean isInvalidOtelTraceId() { - return MDC.get("traceId").equals("00000000000000000000000000000000"); + private void assertMdcValue(String key, String expected) { + assertThat(MDC.get(key)).as("MDC[%s]", key).isEqualTo(expected); } enum AutoConfig implements Supplier { @@ -151,8 +167,9 @@ public ApplicationContextRunner get() { OTEL_DEFAULT { @Override public ApplicationContextRunner get() { - return new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + return new ApplicationContextRunner().withConfiguration(AutoConfigurations.of( + OpenTelemetryAutoConfiguration.class, + org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration.class)) .withPropertyValues("management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", "management.tracing.baggage.correlation.fields=country-code,bp"); } @@ -172,8 +189,9 @@ public ApplicationContextRunner get() { OTEL_W3C { @Override public ApplicationContextRunner get() { - return new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + return new ApplicationContextRunner().withConfiguration(AutoConfigurations.of( + OpenTelemetryAutoConfiguration.class, + org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration.class)) .withPropertyValues("management.tracing.propagation.type=W3C", "management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", "management.tracing.baggage.correlation.fields=country-code,bp"); @@ -205,8 +223,9 @@ public ApplicationContextRunner get() { OTEL_B3 { @Override public ApplicationContextRunner get() { - return new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + return new ApplicationContextRunner().withConfiguration(AutoConfigurations.of( + OpenTelemetryAutoConfiguration.class, + org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration.class)) .withPropertyValues("management.tracing.propagation.type=B3", "management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", "management.tracing.baggage.correlation.fields=country-code,bp"); @@ -216,12 +235,31 @@ public ApplicationContextRunner get() { OTEL_B3_MULTI { @Override public ApplicationContextRunner get() { - return new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + return new ApplicationContextRunner().withConfiguration(AutoConfigurations.of( + OpenTelemetryAutoConfiguration.class, + org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration.class)) .withPropertyValues("management.tracing.propagation.type=B3_MULTI", "management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", "management.tracing.baggage.correlation.fields=country-code,bp"); } + }, + + BRAVE_LOCAL_FIELDS { + @Override + public ApplicationContextRunner get() { + return new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(BraveAutoConfiguration.class)) + .withPropertyValues("management.tracing.baggage.local-fields=country-code,bp", + "management.tracing.baggage.correlation.fields=country-code,bp"); + } + }; + + boolean isOtel() { + return name().startsWith("OTEL_"); + } + + boolean isBrave() { + return name().startsWith("BRAVE_"); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java index e882188eea6d..0bc08733ff28 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java @@ -17,7 +17,9 @@ package org.springframework.boot.actuate.autoconfigure.tracing; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Stream; import brave.Span; @@ -31,6 +33,7 @@ import brave.propagation.CurrentTraceContext.ScopeDecorator; import brave.propagation.Propagation; import brave.propagation.Propagation.Factory; +import brave.propagation.TraceContext; import brave.sampler.Sampler; import io.micrometer.tracing.brave.bridge.BraveBaggageManager; import io.micrometer.tracing.brave.bridge.BraveSpanCustomizer; @@ -153,9 +156,28 @@ void shouldSupplyB3PropagationFactoryViaProperty() { } @Test - void shouldNotSupplyBeansIfTracingIsDisabled() { - this.contextRunner.withPropertyValues("management.tracing.enabled=false") - .run((context) -> assertThat(context).doesNotHaveBean(BraveAutoConfiguration.class)); + void shouldUseB3SingleWithParentWhenPropagationTypeIsB3() { + this.contextRunner + .withPropertyValues("management.tracing.propagation.type=B3", "management.tracing.sampling.probability=1.0") + .run((context) -> { + Propagation propagation = context.getBean(Factory.class).get(); + Tracer tracer = context.getBean(Tracing.class).tracer(); + Span child; + Span parent = tracer.nextSpan().name("parent"); + try (Tracer.SpanInScope ignored = tracer.withSpanInScope(parent.start())) { + child = tracer.nextSpan().name("child"); + child.start().finish(); + } + finally { + parent.finish(); + } + + Map map = new HashMap<>(); + TraceContext childContext = child.context(); + propagation.injector(this::injectToMap).inject(childContext, map); + assertThat(map).containsExactly(Map.entry("b3", "%s-%s-1-%s".formatted(childContext.traceIdString(), + childContext.spanIdString(), childContext.parentIdString()))); + }); } @Test @@ -311,17 +333,41 @@ void compositeSpanHandlerUsesFilterPredicateAndReportersInOrder() { .getBean(CompositeSpanHandlerComponentsConfiguration.class); CompositeSpanHandler composite = context.getBean(CompositeSpanHandler.class); assertThat(composite).extracting("spanFilters") - .asList() + .asInstanceOf(InstanceOfAssertFactories.LIST) .containsExactly(components.filter1, components.filter2); assertThat(composite).extracting("filters") - .asList() + .asInstanceOf(InstanceOfAssertFactories.LIST) .containsExactly(components.predicate2, components.predicate1); assertThat(composite).extracting("reporters") - .asList() + .asInstanceOf(InstanceOfAssertFactories.LIST) .containsExactly(components.reporter1, components.reporter3, components.reporter2); }); } + @Test + void shouldDisablePropagationIfTracingIsDisabled() { + this.contextRunner.withPropertyValues("management.tracing.enabled=false").run((context) -> { + assertThat(context).hasSingleBean(Factory.class); + Factory factory = context.getBean(Factory.class); + Propagation propagation = factory.get(); + assertThat(propagation.keys()).isEmpty(); + }); + } + + @Test + void shouldConfigureTaggedFields() { + this.contextRunner.withPropertyValues("management.tracing.baggage.tag-fields=t1").run((context) -> { + BraveTracer braveTracer = context.getBean(BraveTracer.class); + assertThat(braveTracer).extracting("braveBaggageManager.tagFields") + .asInstanceOf(InstanceOfAssertFactories.list(String.class)) + .containsExactly("t1"); + }); + } + + private void injectToMap(Map map, String key, String value) { + map.put(key, value); + } + private List getInjectors(Factory factory) { assertThat(factory).as("factory").isNotNull(); if (factory instanceof CompositePropagationFactory compositePropagationFactory) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/CompositePropagationFactoryTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/CompositePropagationFactoryTests.java index 5c756514684c..7073b66d8727 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/CompositePropagationFactoryTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/CompositePropagationFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,6 @@ import java.util.List; import java.util.Map; -import brave.internal.propagation.StringPropagationAdapter; import brave.propagation.Propagation; import brave.propagation.TraceContext; import brave.propagation.TraceContextOrSamplingFlags; @@ -143,9 +142,8 @@ private DummyPropagation(String field) { } @Override - @SuppressWarnings("deprecation") - public Propagation create(Propagation.KeyFactory keyFactory) { - return StringPropagationAdapter.create(this, keyFactory); + public Propagation get() { + return this; } @Override diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/LocalBaggageFieldsTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/LocalBaggageFieldsTests.java index 6f6e99d87d35..97893b62cb13 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/LocalBaggageFieldsTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/LocalBaggageFieldsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ import brave.baggage.BaggagePropagationConfig; import brave.propagation.Propagation; import brave.propagation.Propagation.Factory; -import brave.propagation.Propagation.KeyFactory; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -50,11 +49,10 @@ void empty() { assertThat(LocalBaggageFields.empty().asList()).isEmpty(); } - @SuppressWarnings("deprecation") private static FactoryBuilder createBuilder() { return BaggagePropagation.newFactoryBuilder(new Factory() { @Override - public Propagation create(KeyFactory keyFactory) { + public Propagation get() { return null; } }); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessorTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessorTests.java new file mode 100644 index 000000000000..3cf84a10db6f --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessorTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.logging.LoggingSystem; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.StandardEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link LogCorrelationEnvironmentPostProcessor}. + * + * @author Jonatan Ivanov + * @author Phillip Webb + */ +class LogCorrelationEnvironmentPostProcessorTests { + + private final ConfigurableEnvironment environment = new StandardEnvironment(); + + private final SpringApplication application = new SpringApplication(); + + private final LogCorrelationEnvironmentPostProcessor postProcessor = new LogCorrelationEnvironmentPostProcessor(); + + @Test + void getExpectCorrelationIdPropertyWhenMicrometerTracingPresentReturnsTrue() { + this.postProcessor.postProcessEnvironment(this.environment, this.application); + assertThat(this.environment.getProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, Boolean.class, false)) + .isTrue(); + } + + @Test + @ClassPathExclusions("micrometer-tracing-*.jar") + void getExpectCorrelationIdPropertyWhenMicrometerTracingMissingReturnsFalse() { + this.postProcessor.postProcessEnvironment(this.environment, this.application); + assertThat(this.environment.getProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, Boolean.class, false)) + .isFalse(); + } + + @Test + void getExpectCorrelationIdPropertyWhenTracingDisabledReturnsFalse() { + TestPropertyValues.of("management.tracing.enabled=false").applyTo(this.environment); + this.postProcessor.postProcessEnvironment(this.environment, this.application); + assertThat(this.environment.getProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, Boolean.class, false)) + .isFalse(); + } + + @Test + void postProcessEnvironmentAddsEnumerablePropertySource() { + this.postProcessor.postProcessEnvironment(this.environment, this.application); + PropertySource propertySource = this.environment.getPropertySources().get("logCorrelation"); + assertThat(propertySource).isInstanceOf(EnumerablePropertySource.class); + assertThat(((EnumerablePropertySource) propertySource).getPropertyNames()) + .containsExactly(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java index bfef8cccd539..00a3a3977bbc 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java @@ -18,12 +18,21 @@ import java.util.List; +import io.micrometer.common.annotation.ValueExpressionResolver; +import io.micrometer.common.annotation.ValueResolver; import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.annotation.DefaultNewSpanParser; +import io.micrometer.tracing.annotation.ImperativeMethodInvocationProcessor; +import io.micrometer.tracing.annotation.MethodInvocationProcessor; +import io.micrometer.tracing.annotation.NewSpanParser; +import io.micrometer.tracing.annotation.SpanAspect; +import io.micrometer.tracing.annotation.SpanTagAnnotationHandler; import io.micrometer.tracing.handler.DefaultTracingObservationHandler; import io.micrometer.tracing.handler.PropagatingReceiverTracingObservationHandler; import io.micrometer.tracing.handler.PropagatingSenderTracingObservationHandler; import io.micrometer.tracing.handler.TracingObservationHandler; import io.micrometer.tracing.propagation.Propagator; +import org.aspectj.weaver.Advice; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -39,10 +48,13 @@ * Tests for {@link MicrometerTracingAutoConfiguration}. * * @author Moritz Halbritter + * @author Jonatan Ivanov + * @author Brian Clozel */ class MicrometerTracingAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("management.observations.annotations.enabled=true") .withConfiguration(AutoConfigurations.of(MicrometerTracingAutoConfiguration.class)); @Test @@ -52,6 +64,10 @@ void shouldSupplyBeans() { assertThat(context).hasSingleBean(DefaultTracingObservationHandler.class); assertThat(context).hasSingleBean(PropagatingReceiverTracingObservationHandler.class); assertThat(context).hasSingleBean(PropagatingSenderTracingObservationHandler.class); + assertThat(context).hasSingleBean(DefaultNewSpanParser.class); + assertThat(context).hasSingleBean(ImperativeMethodInvocationProcessor.class); + assertThat(context).hasSingleBean(SpanAspect.class); + assertThat(context).hasSingleBean(SpanTagAnnotationHandler.class); }); } @@ -75,14 +91,23 @@ void shouldSupplyBeansInCorrectOrder() { @Test void shouldBackOffOnCustomBeans() { - this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { - assertThat(context).hasBean("customDefaultTracingObservationHandler"); - assertThat(context).hasSingleBean(DefaultTracingObservationHandler.class); - assertThat(context).hasBean("customPropagatingReceiverTracingObservationHandler"); - assertThat(context).hasSingleBean(PropagatingReceiverTracingObservationHandler.class); - assertThat(context).hasBean("customPropagatingSenderTracingObservationHandler"); - assertThat(context).hasSingleBean(PropagatingSenderTracingObservationHandler.class); - }); + this.contextRunner.withUserConfiguration(TracerConfiguration.class, CustomConfiguration.class) + .run((context) -> { + assertThat(context).hasBean("customDefaultTracingObservationHandler"); + assertThat(context).hasSingleBean(DefaultTracingObservationHandler.class); + assertThat(context).hasBean("customPropagatingReceiverTracingObservationHandler"); + assertThat(context).hasSingleBean(PropagatingReceiverTracingObservationHandler.class); + assertThat(context).hasBean("customPropagatingSenderTracingObservationHandler"); + assertThat(context).hasSingleBean(PropagatingSenderTracingObservationHandler.class); + assertThat(context).hasBean("customDefaultNewSpanParser"); + assertThat(context).hasSingleBean(DefaultNewSpanParser.class); + assertThat(context).hasBean("customImperativeMethodInvocationProcessor"); + assertThat(context).hasSingleBean(ImperativeMethodInvocationProcessor.class); + assertThat(context).hasBean("customSpanAspect"); + assertThat(context).hasSingleBean(SpanAspect.class); + assertThat(context).hasBean("customSpanTagAnnotationHandler"); + assertThat(context).hasSingleBean(SpanTagAnnotationHandler.class); + }); } @Test @@ -91,6 +116,9 @@ void shouldNotSupplyBeansIfMicrometerIsMissing() { assertThat(context).doesNotHaveBean(DefaultTracingObservationHandler.class); assertThat(context).doesNotHaveBean(PropagatingReceiverTracingObservationHandler.class); assertThat(context).doesNotHaveBean(PropagatingSenderTracingObservationHandler.class); + assertThat(context).doesNotHaveBean(DefaultNewSpanParser.class); + assertThat(context).doesNotHaveBean(ImperativeMethodInvocationProcessor.class); + assertThat(context).doesNotHaveBean(SpanAspect.class); }); } @@ -100,25 +128,66 @@ void shouldNotSupplyBeansIfTracerIsMissing() { assertThat(context).doesNotHaveBean(DefaultTracingObservationHandler.class); assertThat(context).doesNotHaveBean(PropagatingReceiverTracingObservationHandler.class); assertThat(context).doesNotHaveBean(PropagatingSenderTracingObservationHandler.class); + assertThat(context).doesNotHaveBean(DefaultNewSpanParser.class); + assertThat(context).doesNotHaveBean(ImperativeMethodInvocationProcessor.class); + assertThat(context).doesNotHaveBean(SpanAspect.class); }); } + @Test + void shouldNotSupplyAspectBeansIfPropertyIsDisabled() { + this.contextRunner.withUserConfiguration(TracerConfiguration.class, PropagatorConfiguration.class) + .withPropertyValues("management.observations.annotations.enabled=false") + .run((context) -> { + assertThat(context).doesNotHaveBean(DefaultNewSpanParser.class); + assertThat(context).doesNotHaveBean(ImperativeMethodInvocationProcessor.class); + assertThat(context).doesNotHaveBean(SpanAspect.class); + }); + } + + @Test + void shouldSupplyAspectBeansIfLegacyPropertyIsEnabled() { + new ApplicationContextRunner().withPropertyValues("micrometer.observations.annotations.enabled=true") + .withConfiguration(AutoConfigurations.of(MicrometerTracingAutoConfiguration.class)) + .withUserConfiguration(TracerConfiguration.class, PropagatorConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(DefaultNewSpanParser.class); + assertThat(context).hasSingleBean(ImperativeMethodInvocationProcessor.class); + assertThat(context).hasSingleBean(SpanAspect.class); + }); + } + + @Test + void shouldNotSupplyBeansIfAspectjIsMissing() { + this.contextRunner.withUserConfiguration(TracerConfiguration.class) + .withClassLoader(new FilteredClassLoader(Advice.class)) + .run((context) -> { + assertThat(context).doesNotHaveBean(DefaultNewSpanParser.class); + assertThat(context).doesNotHaveBean(ImperativeMethodInvocationProcessor.class); + assertThat(context).doesNotHaveBean(SpanAspect.class); + }); + } + @Test void shouldNotSupplyBeansIfPropagatorIsMissing() { this.contextRunner.withUserConfiguration(TracerConfiguration.class).run((context) -> { assertThat(context).doesNotHaveBean(PropagatingSenderTracingObservationHandler.class); assertThat(context).doesNotHaveBean(PropagatingReceiverTracingObservationHandler.class); + + assertThat(context).hasSingleBean(DefaultNewSpanParser.class); + assertThat(context).hasSingleBean(ImperativeMethodInvocationProcessor.class); + assertThat(context).hasSingleBean(SpanAspect.class); }); } @Test - void shouldNotSupplyBeansIfTracingIsDisabled() { - this.contextRunner.withUserConfiguration(TracerConfiguration.class, PropagatorConfiguration.class) - .withPropertyValues("management.tracing.enabled=false") + void shouldConfigureSpanTagAnnotationHandler() { + this.contextRunner.withUserConfiguration(TracerConfiguration.class, SpanTagAnnotationHandlerConfiguration.class) .run((context) -> { - assertThat(context).doesNotHaveBean(DefaultTracingObservationHandler.class); - assertThat(context).doesNotHaveBean(PropagatingReceiverTracingObservationHandler.class); - assertThat(context).doesNotHaveBean(PropagatingSenderTracingObservationHandler.class); + assertThat(context).hasSingleBean(DefaultNewSpanParser.class); + assertThat(context).hasSingleBean(SpanAspect.class); + assertThat(context.getBean(ImperativeMethodInvocationProcessor.class)).hasFieldOrPropertyWithValue( + "spanTagAnnotationHandler", context.getBean(SpanTagAnnotationHandler.class)); }); } @@ -160,6 +229,38 @@ PropagatingSenderTracingObservationHandler customPropagatingSenderTracingObse return mock(PropagatingSenderTracingObservationHandler.class); } + @Bean + DefaultNewSpanParser customDefaultNewSpanParser() { + return new DefaultNewSpanParser(); + } + + @Bean + ImperativeMethodInvocationProcessor customImperativeMethodInvocationProcessor(NewSpanParser newSpanParser, + Tracer tracer) { + return new ImperativeMethodInvocationProcessor(newSpanParser, tracer); + } + + @Bean + SpanAspect customSpanAspect(MethodInvocationProcessor methodInvocationProcessor) { + return new SpanAspect(methodInvocationProcessor); + } + + @Bean + SpanTagAnnotationHandler customSpanTagAnnotationHandler() { + return new SpanTagAnnotationHandler((aClass) -> mock(ValueResolver.class), + (aClass) -> mock(ValueExpressionResolver.class)); + } + + } + + @Configuration(proxyBeanMethods = false) + private static final class SpanTagAnnotationHandlerConfiguration { + + @Bean + SpanTagAnnotationHandler spanTagAnnotationHandler() { + return new SpanTagAnnotationHandler((valueResolverClass) -> null, (valueExpressionResolverClass) -> null); + } + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/NoopTracerAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/NoopTracerAutoConfigurationTests.java new file mode 100644 index 000000000000..1677d97fca58 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/NoopTracerAutoConfigurationTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import io.micrometer.tracing.Tracer; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link NoopTracerAutoConfiguration}. + * + * @author Moritz Halbritter + */ +class NoopTracerAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(NoopTracerAutoConfiguration.class)); + + @Test + void shouldSupplyNoopTracer() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(Tracer.class); + Tracer tracer = context.getBean(Tracer.class); + assertThat(tracer).isEqualTo(Tracer.NOOP); + }); + } + + @Test + void shouldBackOffOnCustomTracer() { + this.contextRunner.withUserConfiguration(CustomTracerConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(Tracer.class); + assertThat(context).hasBean("customTracer"); + Tracer tracer = context.getBean(Tracer.class); + assertThat(tracer).isNotEqualTo(Tracer.NOOP); + }); + } + + @Test + void shouldBackOffIfMicrometerTracingIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer.tracing")) + .run((context) -> assertThat(context).doesNotHaveBean(Tracer.class)); + + } + + @Configuration(proxyBeanMethods = false) + private static final class CustomTracerConfiguration { + + @Bean + Tracer customTracer() { + return mock(Tracer.class); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OnEnabledTracingConditionTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OnEnabledTracingConditionTests.java new file mode 100644 index 000000000000..87172a441e8c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OnEnabledTracingConditionTests.java @@ -0,0 +1,129 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link OnEnabledTracingCondition}. + * + * @author Moritz Halbritter + */ +class OnEnabledTracingConditionTests { + + @Test + void shouldMatchIfNoPropertyIsSet() { + OnEnabledTracingCondition condition = new OnEnabledTracingCondition(); + ConditionOutcome outcome = condition.getMatchOutcome(mockConditionContext(), mockMetaData("")); + assertThat(outcome.isMatch()).isTrue(); + assertThat(outcome.getMessage()).isEqualTo("@ConditionalOnEnabledTracing tracing is enabled by default"); + } + + @Test + void shouldNotMatchIfGlobalPropertyIsFalse() { + OnEnabledTracingCondition condition = new OnEnabledTracingCondition(); + ConditionOutcome outcome = condition + .getMatchOutcome(mockConditionContext(Map.of("management.tracing.enabled", "false")), mockMetaData("")); + assertThat(outcome.isMatch()).isFalse(); + assertThat(outcome.getMessage()).isEqualTo("@ConditionalOnEnabledTracing management.tracing.enabled is false"); + } + + @Test + void shouldMatchIfGlobalPropertyIsTrue() { + OnEnabledTracingCondition condition = new OnEnabledTracingCondition(); + ConditionOutcome outcome = condition + .getMatchOutcome(mockConditionContext(Map.of("management.tracing.enabled", "true")), mockMetaData("")); + assertThat(outcome.isMatch()).isTrue(); + assertThat(outcome.getMessage()).isEqualTo("@ConditionalOnEnabledTracing management.tracing.enabled is true"); + } + + @Test + void shouldNotMatchIfExporterPropertyIsFalse() { + OnEnabledTracingCondition condition = new OnEnabledTracingCondition(); + ConditionOutcome outcome = condition.getMatchOutcome( + mockConditionContext(Map.of("management.zipkin.tracing.export.enabled", "false")), + mockMetaData("zipkin")); + assertThat(outcome.isMatch()).isFalse(); + assertThat(outcome.getMessage()) + .isEqualTo("@ConditionalOnEnabledTracing management.zipkin.tracing.export.enabled is false"); + } + + @Test + void shouldMatchIfExporterPropertyIsTrue() { + OnEnabledTracingCondition condition = new OnEnabledTracingCondition(); + ConditionOutcome outcome = condition.getMatchOutcome( + mockConditionContext(Map.of("management.zipkin.tracing.export.enabled", "true")), + mockMetaData("zipkin")); + assertThat(outcome.isMatch()).isTrue(); + assertThat(outcome.getMessage()) + .isEqualTo("@ConditionalOnEnabledTracing management.zipkin.tracing.export.enabled is true"); + } + + @Test + void exporterPropertyShouldOverrideGlobalPropertyIfTrue() { + OnEnabledTracingCondition condition = new OnEnabledTracingCondition(); + ConditionOutcome outcome = condition.getMatchOutcome(mockConditionContext( + Map.of("management.tracing.enabled", "false", "management.zipkin.tracing.export.enabled", "true")), + mockMetaData("zipkin")); + assertThat(outcome.isMatch()).isTrue(); + assertThat(outcome.getMessage()) + .isEqualTo("@ConditionalOnEnabledTracing management.zipkin.tracing.export.enabled is true"); + } + + @Test + void exporterPropertyShouldOverrideGlobalPropertyIfFalse() { + OnEnabledTracingCondition condition = new OnEnabledTracingCondition(); + ConditionOutcome outcome = condition.getMatchOutcome(mockConditionContext( + Map.of("management.tracing.enabled", "true", "management.zipkin.tracing.export.enabled", "false")), + mockMetaData("zipkin")); + assertThat(outcome.isMatch()).isFalse(); + assertThat(outcome.getMessage()) + .isEqualTo("@ConditionalOnEnabledTracing management.zipkin.tracing.export.enabled is false"); + } + + private ConditionContext mockConditionContext() { + return mockConditionContext(Collections.emptyMap()); + } + + private ConditionContext mockConditionContext(Map properties) { + ConditionContext context = mock(ConditionContext.class); + MockEnvironment environment = new MockEnvironment(); + properties.forEach(environment::setProperty); + given(context.getEnvironment()).willReturn(environment); + return context; + } + + private AnnotatedTypeMetadata mockMetaData(String exporter) { + AnnotatedTypeMetadata metadata = mock(AnnotatedTypeMetadata.class); + given(metadata.getAnnotationAttributes(ConditionalOnEnabledTracing.class.getName())) + .willReturn(Map.of("value", exporter)); + return metadata; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java index 8861c0ef2088..b96ac2da59f3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java @@ -34,9 +34,9 @@ import io.micrometer.tracing.otel.bridge.Slf4JBaggageEventListener; import io.micrometer.tracing.otel.bridge.Slf4JEventListener; import io.micrometer.tracing.otel.propagation.BaggageTextMapPropagator; -import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.propagation.ContextPropagators; @@ -50,10 +50,12 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SpanExporter; import io.opentelemetry.sdk.trace.samplers.Sampler; -import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +import io.opentelemetry.semconv.ResourceAttributes; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -65,6 +67,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; /** @@ -77,7 +82,9 @@ class OpenTelemetryAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of( + org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration.class, + OpenTelemetryAutoConfiguration.class)); @Test void shouldSupplyBeans() { @@ -85,7 +92,6 @@ void shouldSupplyBeans() { assertThat(context).hasSingleBean(OtelTracer.class); assertThat(context).hasSingleBean(EventPublisher.class); assertThat(context).hasSingleBean(OtelCurrentTraceContext.class); - assertThat(context).hasSingleBean(OpenTelemetry.class); assertThat(context).hasSingleBean(SdkTracerProvider.class); assertThat(context).hasSingleBean(ContextPropagators.class); assertThat(context).hasSingleBean(Sampler.class); @@ -96,6 +102,8 @@ void shouldSupplyBeans() { assertThat(context).hasSingleBean(OtelPropagator.class); assertThat(context).hasSingleBean(TextMapPropagator.class); assertThat(context).hasSingleBean(OtelSpanCustomizer.class); + assertThat(context).hasSingleBean(SpanProcessors.class); + assertThat(context).hasSingleBean(SpanExporters.class); }); } @@ -115,7 +123,6 @@ void shouldNotSupplyBeansIfDependencyIsMissing(String packageName) { assertThat(context).doesNotHaveBean(OtelTracer.class); assertThat(context).doesNotHaveBean(EventPublisher.class); assertThat(context).doesNotHaveBean(OtelCurrentTraceContext.class); - assertThat(context).doesNotHaveBean(OpenTelemetry.class); assertThat(context).doesNotHaveBean(SdkTracerProvider.class); assertThat(context).doesNotHaveBean(ContextPropagators.class); assertThat(context).doesNotHaveBean(Sampler.class); @@ -126,6 +133,8 @@ void shouldNotSupplyBeansIfDependencyIsMissing(String packageName) { assertThat(context).doesNotHaveBean(OtelPropagator.class); assertThat(context).doesNotHaveBean(TextMapPropagator.class); assertThat(context).doesNotHaveBean(OtelSpanCustomizer.class); + assertThat(context).doesNotHaveBean(SpanProcessors.class); + assertThat(context).doesNotHaveBean(SpanExporters.class); }); } @@ -138,8 +147,6 @@ void shouldBackOffOnCustomBeans() { assertThat(context).hasSingleBean(EventPublisher.class); assertThat(context).hasBean("customOtelCurrentTraceContext"); assertThat(context).hasSingleBean(OtelCurrentTraceContext.class); - assertThat(context).hasBean("customOpenTelemetry"); - assertThat(context).hasSingleBean(OpenTelemetry.class); assertThat(context).hasBean("customSdkTracerProvider"); assertThat(context).hasSingleBean(SdkTracerProvider.class); assertThat(context).hasBean("customContextPropagators"); @@ -156,6 +163,10 @@ void shouldBackOffOnCustomBeans() { assertThat(context).hasSingleBean(OtelPropagator.class); assertThat(context).hasBean("customSpanCustomizer"); assertThat(context).hasSingleBean(SpanCustomizer.class); + assertThat(context).hasBean("customSpanProcessors"); + assertThat(context).hasSingleBean(SpanProcessors.class); + assertThat(context).hasBean("customSpanExporters"); + assertThat(context).hasSingleBean(SpanExporters.class); }); } @@ -172,7 +183,7 @@ void shouldSetupDefaultResourceAttributes() { exporter.await(Duration.ofSeconds(10)); SpanData spanData = exporter.getExportedSpans().get(0); Map, Object> expectedAttributes = Resource.getDefault() - .merge(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, "application"))) + .merge(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, "unknown_service"))) .getAttributes() .asMap(); assertThat(spanData.getResource().getAttributes().asMap()).isEqualTo(expectedAttributes); @@ -181,9 +192,22 @@ void shouldSetupDefaultResourceAttributes() { @Test void shouldAllowMultipleSpanProcessors() { - this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { + this.contextRunner.withUserConfiguration(AdditionalSpanProcessorConfiguration.class).run((context) -> { assertThat(context.getBeansOfType(SpanProcessor.class)).hasSize(2); assertThat(context).hasBean("customSpanProcessor"); + SpanProcessors spanProcessors = context.getBean(SpanProcessors.class); + assertThat(spanProcessors).hasSize(2); + }); + } + + @Test + void shouldAllowMultipleSpanExporters() { + this.contextRunner.withUserConfiguration(MultipleSpanExporterConfiguration.class).run((context) -> { + assertThat(context.getBeansOfType(SpanExporter.class)).hasSize(2); + assertThat(context).hasBean("spanExporter1"); + assertThat(context).hasBean("spanExporter2"); + SpanExporters spanExporters = context.getBean(SpanExporters.class); + assertThat(spanExporters).hasSize(2); }); } @@ -249,13 +273,20 @@ void shouldSupplyW3CPropagationWithoutBaggageWhenDisabled() { }); } - private List getInjectors(TextMapPropagator propagator) { - assertThat(propagator).as("propagator").isNotNull(); - if (propagator instanceof CompositeTextMapPropagator compositePropagator) { - return compositePropagator.getInjectors().stream().toList(); - } - fail("Expected CompositeTextMapPropagator, found %s".formatted(propagator.getClass())); - throw new AssertionError("Unreachable"); + @Test + void shouldConfigureRemoteAndTaggedFields() { + this.contextRunner + .withPropertyValues("management.tracing.baggage.remote-fields=r1", + "management.tracing.baggage.tag-fields=t1") + .run((context) -> { + CompositeTextMapPropagator propagator = context.getBean(CompositeTextMapPropagator.class); + assertThat(propagator).extracting("baggagePropagator.baggageManager.remoteFields") + .asInstanceOf(InstanceOfAssertFactories.list(String.class)) + .containsExactly("r1"); + assertThat(propagator).extracting("baggagePropagator.baggageManager.tagFields") + .asInstanceOf(InstanceOfAssertFactories.list(String.class)) + .containsExactly("t1"); + }); } @Test @@ -267,9 +298,84 @@ void shouldCustomizeSdkTracerProvider() { }); } + @Test + void defaultSpanProcessorShouldUseMeterProviderIfAvailable() { + this.contextRunner.withUserConfiguration(MeterProviderConfiguration.class).run((context) -> { + MeterProvider meterProvider = context.getBean(MeterProvider.class); + assertThat(Mockito.mockingDetails(meterProvider).isMock()).isTrue(); + then(meterProvider).should().meterBuilder(anyString()); + }); + } + + @Test + void shouldDisablePropagationIfTracingIsDisabled() { + this.contextRunner.withPropertyValues("management.tracing.enabled=false").run((context) -> { + assertThat(context).hasSingleBean(TextMapPropagator.class); + TextMapPropagator propagator = context.getBean(TextMapPropagator.class); + assertThat(propagator.fields()).isEmpty(); + }); + } + + private List getInjectors(TextMapPropagator propagator) { + assertThat(propagator).as("propagator").isNotNull(); + if (propagator instanceof CompositeTextMapPropagator compositePropagator) { + return compositePropagator.getInjectors().stream().toList(); + } + fail("Expected CompositeTextMapPropagator, found %s".formatted(propagator.getClass())); + throw new AssertionError("Unreachable"); + } + + @Configuration(proxyBeanMethods = false) + private static final class MeterProviderConfiguration { + + @Bean + MeterProvider meterProvider() { + MeterProvider mock = mock(MeterProvider.class); + given(mock.meterBuilder(anyString())) + .willAnswer((invocation) -> MeterProvider.noop().meterBuilder(invocation.getArgument(0, String.class))); + return mock; + } + + } + + @Configuration(proxyBeanMethods = false) + private static final class AdditionalSpanProcessorConfiguration { + + @Bean + SpanProcessor customSpanProcessor() { + return mock(SpanProcessor.class); + } + + } + + @Configuration(proxyBeanMethods = false) + private static final class MultipleSpanExporterConfiguration { + + @Bean + SpanExporter spanExporter1() { + return new DummySpanExporter(); + } + + @Bean + SpanExporter spanExporter2() { + return new DummySpanExporter(); + } + + } + @Configuration(proxyBeanMethods = false) private static final class CustomConfiguration { + @Bean + SpanProcessors customSpanProcessors() { + return SpanProcessors.of(mock(SpanProcessor.class)); + } + + @Bean + SpanExporters customSpanExporters() { + return SpanExporters.of(new DummySpanExporter()); + } + @Bean io.micrometer.tracing.Tracer customMicrometerTracer() { return mock(io.micrometer.tracing.Tracer.class); @@ -285,11 +391,6 @@ OtelCurrentTraceContext customOtelCurrentTraceContext() { return mock(OtelCurrentTraceContext.class); } - @Bean - OpenTelemetry customOpenTelemetry() { - return mock(OpenTelemetry.class); - } - @Bean SdkTracerProvider customSdkTracerProvider() { return SdkTracerProvider.builder().build(); @@ -365,6 +466,25 @@ SdkTracerProviderBuilderCustomizer sdkTracerProviderBuilderCustomizerTwo() { } + private static final class DummySpanExporter implements SpanExporter { + + @Override + public CompletableResultCode export(Collection spans) { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + return CompletableResultCode.ofSuccess(); + } + + } + @Configuration(proxyBeanMethods = false) private static final class InMemoryRecordingSpanExporterConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExportersTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExportersTests.java new file mode 100644 index 000000000000..d15f2d1aceb6 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExportersTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import java.util.List; + +import io.opentelemetry.sdk.trace.export.SpanExporter; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link SpanExporters}. + * + * @author Moritz Halbritter + */ +class SpanExportersTests { + + @Test + void ofList() { + SpanExporter spanExporter1 = mock(SpanExporter.class); + SpanExporter spanExporter2 = mock(SpanExporter.class); + SpanExporters spanExporters = SpanExporters.of(List.of(spanExporter1, spanExporter2)); + assertThat(spanExporters).containsExactly(spanExporter1, spanExporter2); + assertThat(spanExporters.list()).containsExactly(spanExporter1, spanExporter2); + } + + @Test + void ofArray() { + SpanExporter spanExporter1 = mock(SpanExporter.class); + SpanExporter spanExporter2 = mock(SpanExporter.class); + SpanExporters spanExporters = SpanExporters.of(spanExporter1, spanExporter2); + assertThat(spanExporters).containsExactly(spanExporter1, spanExporter2); + assertThat(spanExporters.list()).containsExactly(spanExporter1, spanExporter2); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessorsTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessorsTests.java new file mode 100644 index 000000000000..8a5fa76868de --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessorsTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing; + +import java.util.List; + +import io.opentelemetry.sdk.trace.SpanProcessor; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link SpanProcessors}. + * + * @author Moritz Halbritter + */ +class SpanProcessorsTests { + + @Test + void ofList() { + SpanProcessor spanProcessor1 = mock(SpanProcessor.class); + SpanProcessor spanProcessor2 = mock(SpanProcessor.class); + SpanProcessors spanProcessors = SpanProcessors.of(List.of(spanProcessor1, spanProcessor2)); + assertThat(spanProcessors).containsExactly(spanProcessor1, spanProcessor2); + assertThat(spanProcessors.list()).containsExactly(spanProcessor1, spanProcessor2); + } + + @Test + void ofArray() { + SpanProcessor spanProcessor1 = mock(SpanProcessor.class); + SpanProcessor spanProcessor2 = mock(SpanProcessor.class); + SpanProcessors spanProcessors = SpanProcessors.of(spanProcessor1, spanProcessor2); + assertThat(spanProcessors).containsExactly(spanProcessor1, spanProcessor2); + assertThat(spanProcessors.list()).containsExactly(spanProcessor1, spanProcessor2); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java index 47fbcc824a3c..0fcc77811f95 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java @@ -34,8 +34,8 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -50,9 +50,10 @@ class OtlpAutoConfigurationIntegrationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("management.tracing.sampling.probability=1.0") - .withConfiguration( - AutoConfigurations.of(ObservationAutoConfiguration.class, MicrometerTracingAutoConfiguration.class, - OpenTelemetryAutoConfiguration.class, OtlpAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(ObservationAutoConfiguration.class, + MicrometerTracingAutoConfiguration.class, OpenTelemetryAutoConfiguration.class, + org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration.class, + OtlpAutoConfiguration.class)); private final MockWebServer mockWebServer = new MockWebServer(); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java index b46b6ba153dc..eba8fe11251a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java @@ -19,8 +19,10 @@ import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.sdk.trace.export.SpanExporter; +import okhttp3.HttpUrl; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingConfigurations.ConnectionDetails.PropertiesOtlpTracingConnectionDetails; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -33,24 +35,41 @@ * Tests for {@link OtlpAutoConfiguration}. * * @author Jonatan Ivanov + * @author Moritz Halbritter + * @author Eddú Meléndez */ class OtlpAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(OtlpAutoConfiguration.class)); + private final ApplicationContextRunner tracingDisabledContextRunner = this.contextRunner + .withPropertyValues("management.tracing.enabled=false"); + + @Test + void shouldNotSupplyBeansIfPropertyIsNotSet() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(OtlpHttpSpanExporter.class)); + } + @Test void shouldSupplyBeans() { - this.contextRunner.run((context) -> assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class) - .hasSingleBean(SpanExporter.class)); + this.contextRunner.withPropertyValues("management.otlp.tracing.endpoint=http://localhost:4318/v1/traces") + .run((context) -> assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class) + .hasSingleBean(SpanExporter.class)); } @Test - void shouldNotSupplyBeansIfTracingIsDisabled() { + void shouldNotSupplyBeansIfGlobalTracingIsDisabled() { this.contextRunner.withPropertyValues("management.tracing.enabled=false") .run((context) -> assertThat(context).doesNotHaveBean(SpanExporter.class)); } + @Test + void shouldNotSupplyBeansIfOtlpTracingIsDisabled() { + this.contextRunner.withPropertyValues("management.otlp.tracing.export.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(SpanExporter.class)); + } + @Test void shouldNotSupplyBeansIfTracingBridgeIsMissing() { this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer.tracing")) @@ -89,6 +108,30 @@ void shouldBackOffWhenCustomGrpcExporterIsDefined() { .hasSingleBean(SpanExporter.class)); } + @Test + void shouldNotSupplyOtlpHttpSpanExporterIfTracingIsDisabled() { + this.tracingDisabledContextRunner + .withPropertyValues("management.otlp.tracing.endpoint=http://localhost:4318/v1/traces") + .run((context) -> assertThat(context).doesNotHaveBean(OtlpHttpSpanExporter.class)); + } + + @Test + void definesPropertiesBasedConnectionDetailsByDefault() { + this.contextRunner.withPropertyValues("management.otlp.tracing.endpoint=http://localhost:4318/v1/traces") + .run((context) -> assertThat(context).hasSingleBean(PropertiesOtlpTracingConnectionDetails.class)); + } + + @Test + void testConnectionFactoryWithOverridesWhenUsingCustomConnectionDetails() { + this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(OtlpTracingConnectionDetails.class) + .doesNotHaveBean(PropertiesOtlpTracingConnectionDetails.class); + OtlpHttpSpanExporter otlpHttpSpanExporter = context.getBean(OtlpHttpSpanExporter.class); + assertThat(otlpHttpSpanExporter).extracting("delegate.httpSender.url") + .isEqualTo(HttpUrl.get("http://localhost:12345/v1/traces")); + }); + } + @Configuration(proxyBeanMethods = false) private static final class CustomHttpExporterConfiguration { @@ -109,4 +152,14 @@ OtlpGrpcSpanExporter customOtlpGrpcSpanExporter() { } + @Configuration(proxyBeanMethods = false) + static class ConnectionDetailsConfiguration { + + @Bean + OtlpTracingConnectionDetails otlpTracingConnectionDetails() { + return () -> "http://localhost:12345/v1/traces"; + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/LazyTracingSpanContextSupplierTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/LazyTracingSpanContextSupplierTests.java index 2cc20b2ebb3d..867a9cf10e85 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/LazyTracingSpanContextSupplierTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/LazyTracingSpanContextSupplierTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,17 +23,18 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.actuate.autoconfigure.tracing.prometheus.PrometheusExemplarsAutoConfiguration.LazyTracingSpanContextSupplier; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** - * Tests for {@link LazyTracingSpanContextSupplier}. + * Tests for + * {@link org.springframework.boot.actuate.autoconfigure.tracing.prometheus.PrometheusSimpleclientExemplarsAutoConfiguration.LazyTracingSpanContextSupplier}. * * @author Andy Wilkinson */ +@SuppressWarnings("removal") class LazyTracingSpanContextSupplierTests { private final Tracer tracer = mock(Tracer.class); @@ -62,7 +63,7 @@ public Tracer getIfUnique() throws BeansException { }; - private final LazyTracingSpanContextSupplier spanContextSupplier = new LazyTracingSpanContextSupplier( + private final org.springframework.boot.actuate.autoconfigure.tracing.prometheus.PrometheusSimpleclientExemplarsAutoConfiguration.LazyTracingSpanContextSupplier spanContextSupplier = new org.springframework.boot.actuate.autoconfigure.tracing.prometheus.PrometheusSimpleclientExemplarsAutoConfiguration.LazyTracingSpanContextSupplier( this.objectProvider); @Test diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/LazyTracingSpanContextTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/LazyTracingSpanContextTests.java new file mode 100644 index 000000000000..87ab9d805e59 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/LazyTracingSpanContextTests.java @@ -0,0 +1,150 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.prometheus; + +import io.micrometer.tracing.Span; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.Tracer; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.autoconfigure.tracing.prometheus.PrometheusExemplarsAutoConfiguration.LazyTracingSpanContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link LazyTracingSpanContext}. + * + * @author Andy Wilkinson + */ +class LazyTracingSpanContextTests { + + private final Tracer tracer = mock(Tracer.class); + + private final ObjectProvider objectProvider = new ObjectProvider<>() { + + @Override + public Tracer getObject() throws BeansException { + return LazyTracingSpanContextTests.this.tracer; + } + + @Override + public Tracer getObject(Object... args) throws BeansException { + return LazyTracingSpanContextTests.this.tracer; + } + + @Override + public Tracer getIfAvailable() throws BeansException { + return LazyTracingSpanContextTests.this.tracer; + } + + @Override + public Tracer getIfUnique() throws BeansException { + return LazyTracingSpanContextTests.this.tracer; + } + + }; + + private final LazyTracingSpanContext spanContext = new LazyTracingSpanContext(this.objectProvider); + + @Test + void whenCurrentSpanIsNullThenSpanIdIsNull() { + assertThat(this.spanContext.getCurrentSpanId()).isNull(); + } + + @Test + void whenCurrentSpanIsNullThenTraceIdIsNull() { + assertThat(this.spanContext.getCurrentTraceId()).isNull(); + } + + @Test + void whenCurrentSpanIsNullThenSampledIsFalse() { + assertThat(this.spanContext.isCurrentSpanSampled()).isFalse(); + } + + @Test + void whenCurrentSpanHasSpanIdThenSpanIdIsFromSpan() { + Span span = mock(Span.class); + given(this.tracer.currentSpan()).willReturn(span); + TraceContext traceContext = mock(TraceContext.class); + given(traceContext.spanId()).willReturn("span-id"); + given(span.context()).willReturn(traceContext); + assertThat(this.spanContext.getCurrentSpanId()).isEqualTo("span-id"); + } + + @Test + void whenCurrentSpanHasTraceIdThenTraceIdIsFromSpan() { + Span span = mock(Span.class); + given(this.tracer.currentSpan()).willReturn(span); + TraceContext traceContext = mock(TraceContext.class); + given(traceContext.traceId()).willReturn("trace-id"); + given(span.context()).willReturn(traceContext); + assertThat(this.spanContext.getCurrentTraceId()).isEqualTo("trace-id"); + } + + @Test + void whenCurrentSpanHasNoSpanIdThenSpanIdIsNull() { + Span span = mock(Span.class); + given(this.tracer.currentSpan()).willReturn(span); + TraceContext traceContext = mock(TraceContext.class); + given(span.context()).willReturn(traceContext); + assertThat(this.spanContext.getCurrentSpanId()).isNull(); + } + + @Test + void whenCurrentSpanHasNoTraceIdThenTraceIdIsNull() { + Span span = mock(Span.class); + given(this.tracer.currentSpan()).willReturn(span); + TraceContext traceContext = mock(TraceContext.class); + given(span.context()).willReturn(traceContext); + assertThat(this.spanContext.getCurrentTraceId()).isNull(); + } + + @Test + void whenCurrentSpanIsSampledThenSampledIsTrue() { + Span span = mock(Span.class); + given(this.tracer.currentSpan()).willReturn(span); + TraceContext traceContext = mock(TraceContext.class); + given(traceContext.sampled()).willReturn(true); + given(span.context()).willReturn(traceContext); + assertThat(this.spanContext.isCurrentSpanSampled()).isTrue(); + } + + @Test + void whenCurrentSpanIsNotSampledThenSampledIsFalse() { + Span span = mock(Span.class); + given(this.tracer.currentSpan()).willReturn(span); + TraceContext traceContext = mock(TraceContext.class); + given(traceContext.sampled()).willReturn(false); + given(span.context()).willReturn(traceContext); + assertThat(this.spanContext.isCurrentSpanSampled()).isFalse(); + } + + @Test + void whenCurrentSpanHasDeferredSamplingThenSampledIsFalse() { + Span span = mock(Span.class); + given(this.tracer.currentSpan()).willReturn(span); + TraceContext traceContext = mock(TraceContext.class); + given(traceContext.sampled()).willReturn(null); + given(span.context()).willReturn(traceContext); + assertThat(this.spanContext.isCurrentSpanSampled()).isFalse(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfigurationTests.java index e0b5c51f91b0..90aa5e2bac63 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfigurationTests.java @@ -16,11 +16,15 @@ package org.springframework.boot.actuate.autoconfigure.tracing.prometheus; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; -import io.micrometer.prometheus.PrometheusMeterRegistry; -import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier; -import io.prometheus.client.exporter.common.TextFormat; +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; +import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; +import io.prometheus.metrics.tracer.common.SpanContext; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusMetricsExportAutoConfiguration; @@ -33,6 +37,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -40,10 +45,16 @@ /** * Tests for {@link PrometheusExemplarsAutoConfiguration}. * - * * @author Jonatan Ivanov + * @author Jonatan Ivanov */ class PrometheusExemplarsAutoConfigurationTests { + private static final Pattern BUCKET_TRACE_INFO_PATTERN = Pattern.compile( + "^test_observation_seconds_bucket\\{error=\"none\",le=\".+\"} 1 # \\{span_id=\"(\\p{XDigit}+)\",trace_id=\"(\\p{XDigit}+)\"} .+$"); + + private static final Pattern COUNT_TRACE_INFO_PATTERN = Pattern.compile( + "^test_observation_seconds_count\\{error=\"none\"} 1 # \\{span_id=\"(\\p{XDigit}+)\",trace_id=\"(\\p{XDigit}+)\"} .+$"); + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("management.tracing.sampling.probability=1.0", "management.metrics.distribution.percentiles-histogram.all=true") @@ -52,56 +63,98 @@ class PrometheusExemplarsAutoConfigurationTests { AutoConfigurations.of(PrometheusExemplarsAutoConfiguration.class, ObservationAutoConfiguration.class, BraveAutoConfiguration.class, MicrometerTracingAutoConfiguration.class)); - @Test - void shouldNotSupplyBeansIfTracingIsDisabled() { - this.contextRunner.withPropertyValues("management.tracing.enabled=false") - .run((context) -> assertThat(context).doesNotHaveBean(SpanContextSupplier.class)); - } - @Test void shouldNotSupplyBeansIfPrometheusSupportIsMissing() { - this.contextRunner.withClassLoader(new FilteredClassLoader("io.prometheus.client.exemplars")) - .run((context) -> assertThat(context).doesNotHaveBean(SpanContextSupplier.class)); + this.contextRunner.withClassLoader(new FilteredClassLoader("io.prometheus.metrics.tracer")) + .run((context) -> assertThat(context).doesNotHaveBean(SpanContext.class)); } @Test void shouldNotSupplyBeansIfMicrometerTracingIsMissing() { this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer.tracing")) - .run((context) -> assertThat(context).doesNotHaveBean(SpanContextSupplier.class)); + .run((context) -> assertThat(context).doesNotHaveBean(SpanContext.class)); } @Test void shouldSupplyCustomBeans() { this.contextRunner.withUserConfiguration(CustomConfiguration.class) - .run((context) -> assertThat(context).hasSingleBean(SpanContextSupplier.class) - .getBean(SpanContextSupplier.class) - .isSameAs(CustomConfiguration.SUPPLIER)); + .run((context) -> assertThat(context).hasSingleBean(SpanContext.class) + .getBean(SpanContext.class) + .isSameAs(CustomConfiguration.SPAN_CONTEXT)); + } + + @Test + void prometheusOpenMetricsOutputWithoutExemplarsOnHistogramCount() { + this.contextRunner.withPropertyValues( + "management.prometheus.metrics.export.properties.io.prometheus.exporter.exemplarsOnAllMetricTypes=false") + .run((context) -> { + assertThat(context).hasSingleBean(SpanContext.class); + ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); + Observation.start("test.observation", observationRegistry).stop(); + PrometheusMeterRegistry prometheusMeterRegistry = context.getBean(PrometheusMeterRegistry.class); + String openMetricsOutput = prometheusMeterRegistry.scrape(OpenMetricsTextFormatWriter.CONTENT_TYPE); + + assertThat(openMetricsOutput).contains("test_observation_seconds_bucket"); + assertThat(openMetricsOutput).containsOnlyOnce("test_observation_seconds_count"); + assertThat(StringUtils.countOccurrencesOf(openMetricsOutput, "span_id")).isEqualTo(1); + assertThat(StringUtils.countOccurrencesOf(openMetricsOutput, "trace_id")).isEqualTo(1); + + Optional bucketTraceInfo = openMetricsOutput.lines() + .filter((line) -> line.contains("test_observation_seconds_bucket") && line.contains("span_id")) + .map(BUCKET_TRACE_INFO_PATTERN::matcher) + .flatMap(Matcher::results) + .map((matchResult) -> new TraceInfo(matchResult.group(2), matchResult.group(1))) + .findFirst(); + + assertThat(bucketTraceInfo).isNotEmpty(); + }); } @Test void prometheusOpenMetricsOutputShouldContainExemplars() { this.contextRunner.run((context) -> { - assertThat(context).hasSingleBean(SpanContextSupplier.class); + assertThat(context).hasSingleBean(SpanContext.class); ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); Observation.start("test.observation", observationRegistry).stop(); PrometheusMeterRegistry prometheusMeterRegistry = context.getBean(PrometheusMeterRegistry.class); - String openMetricsOutput = prometheusMeterRegistry.scrape(TextFormat.CONTENT_TYPE_OPENMETRICS_100); - assertThat(openMetricsOutput).contains("test_observation_seconds_bucket") - .containsOnlyOnce("trace_id=") - .containsOnlyOnce("span_id="); + String openMetricsOutput = prometheusMeterRegistry.scrape(OpenMetricsTextFormatWriter.CONTENT_TYPE); + + assertThat(openMetricsOutput).contains("test_observation_seconds_bucket"); + assertThat(openMetricsOutput).containsOnlyOnce("test_observation_seconds_count"); + assertThat(StringUtils.countOccurrencesOf(openMetricsOutput, "span_id")).isEqualTo(2); + assertThat(StringUtils.countOccurrencesOf(openMetricsOutput, "trace_id")).isEqualTo(2); + + Optional bucketTraceInfo = openMetricsOutput.lines() + .filter((line) -> line.contains("test_observation_seconds_bucket") && line.contains("span_id")) + .map(BUCKET_TRACE_INFO_PATTERN::matcher) + .flatMap(Matcher::results) + .map((matchResult) -> new TraceInfo(matchResult.group(2), matchResult.group(1))) + .findFirst(); + + Optional counterTraceInfo = openMetricsOutput.lines() + .filter((line) -> line.contains("test_observation_seconds_count") && line.contains("span_id")) + .map(COUNT_TRACE_INFO_PATTERN::matcher) + .flatMap(Matcher::results) + .map((matchResult) -> new TraceInfo(matchResult.group(2), matchResult.group(1))) + .findFirst(); + + assertThat(bucketTraceInfo).isNotEmpty().contains(counterTraceInfo.orElse(null)); }); } @Configuration(proxyBeanMethods = false) private static final class CustomConfiguration { - static final SpanContextSupplier SUPPLIER = mock(SpanContextSupplier.class); + static final SpanContext SPAN_CONTEXT = mock(SpanContext.class); @Bean - SpanContextSupplier customSpanContextSupplier() { - return SUPPLIER; + SpanContext customSpanContext() { + return SPAN_CONTEXT; } } + private record TraceInfo(String traceId, String spanId) { + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusSimpleclientExemplarsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusSimpleclientExemplarsAutoConfigurationTests.java new file mode 100644 index 000000000000..b16f687532fa --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusSimpleclientExemplarsAutoConfigurationTests.java @@ -0,0 +1,135 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.prometheus; + +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; +import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier; +import io.prometheus.client.exporter.common.TextFormat; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusSimpleclientMetricsExportAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun; +import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link PrometheusSimpleclientExemplarsAutoConfiguration}. + * + * @author Jonatan Ivanov + */ +@SuppressWarnings("removal") +class PrometheusSimpleclientExemplarsAutoConfigurationTests { + + private static final Pattern BUCKET_TRACE_INFO_PATTERN = Pattern.compile( + "^test_observation_seconds_bucket\\{error=\"none\",le=\".+\"} 1.0 # \\{span_id=\"(\\p{XDigit}+)\",trace_id=\"(\\p{XDigit}+)\"} .+$"); + + private static final Pattern COUNTER_TRACE_INFO_PATTERN = Pattern.compile( + "^test_observation_seconds_count\\{error=\"none\"} 1.0 # \\{span_id=\"(\\p{XDigit}+)\",trace_id=\"(\\p{XDigit}+)\"} .+$"); + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("management.tracing.sampling.probability=1.0", + "management.metrics.distribution.percentiles-histogram.all=true") + .with(MetricsRun.limitedTo()) + .withConfiguration(AutoConfigurations.of(PrometheusSimpleclientMetricsExportAutoConfiguration.class, + PrometheusSimpleclientExemplarsAutoConfiguration.class, ObservationAutoConfiguration.class, + BraveAutoConfiguration.class, MicrometerTracingAutoConfiguration.class)); + + @Test + void shouldNotSupplyBeansIfPrometheusSupportIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.prometheus.client.exemplars")) + .run((context) -> assertThat(context).doesNotHaveBean(SpanContextSupplier.class)); + } + + @Test + void shouldNotSupplyBeansIfMicrometerTracingIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer.tracing")) + .run((context) -> assertThat(context).doesNotHaveBean(SpanContextSupplier.class)); + } + + @Test + void shouldSupplyCustomBeans() { + this.contextRunner.withUserConfiguration(CustomConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(SpanContextSupplier.class) + .getBean(SpanContextSupplier.class) + .isSameAs(CustomConfiguration.SUPPLIER)); + } + + @Test + @SuppressWarnings("deprecation") + void prometheusOpenMetricsOutputShouldContainExemplars() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(SpanContextSupplier.class); + ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); + Observation.start("test.observation", observationRegistry).stop(); + io.micrometer.prometheus.PrometheusMeterRegistry prometheusMeterRegistry = context + .getBean(io.micrometer.prometheus.PrometheusMeterRegistry.class); + String openMetricsOutput = prometheusMeterRegistry.scrape(TextFormat.CONTENT_TYPE_OPENMETRICS_100); + + assertThat(openMetricsOutput).contains("test_observation_seconds_bucket"); + assertThat(openMetricsOutput).containsOnlyOnce("test_observation_seconds_count"); + assertThat(StringUtils.countOccurrencesOf(openMetricsOutput, "span_id")).isEqualTo(2); + assertThat(StringUtils.countOccurrencesOf(openMetricsOutput, "trace_id")).isEqualTo(2); + + Optional bucketTraceInfo = openMetricsOutput.lines() + .filter((line) -> line.contains("test_observation_seconds_bucket") && line.contains("span_id")) + .map(BUCKET_TRACE_INFO_PATTERN::matcher) + .flatMap(Matcher::results) + .map((matchResult) -> new TraceInfo(matchResult.group(2), matchResult.group(1))) + .findFirst(); + + Optional counterTraceInfo = openMetricsOutput.lines() + .filter((line) -> line.contains("test_observation_seconds_count") && line.contains("span_id")) + .map(COUNTER_TRACE_INFO_PATTERN::matcher) + .flatMap(Matcher::results) + .map((matchResult) -> new TraceInfo(matchResult.group(2), matchResult.group(1))) + .findFirst(); + + assertThat(bucketTraceInfo).isNotEmpty().contains(counterTraceInfo.orElse(null)); + }); + } + + @Configuration(proxyBeanMethods = false) + private static final class CustomConfiguration { + + static final SpanContextSupplier SUPPLIER = mock(SpanContextSupplier.class); + + @Bean + SpanContextSupplier customSpanContextSupplier() { + return SUPPLIER; + } + + } + + private record TraceInfo(String traceId, String spanId) { + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfigurationTests.java index 6f9f85069d2f..802209c3ca30 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfigurationTests.java @@ -82,12 +82,22 @@ void shouldNotSupplyBeansIfMicrometerReporterWavefrontIsMissing() { } @Test - void shouldNotSupplyBeansIfTracingIsDisabled() { + void shouldNotSupplyBeansIfGlobalTracingIsDisabled() { this.contextRunner.withPropertyValues("management.tracing.enabled=false") .withUserConfiguration(WavefrontSenderConfiguration.class) .run((context) -> { assertThat(context).doesNotHaveBean(WavefrontSpanHandler.class); - assertThat(context).doesNotHaveBean(SpanMetrics.class); + assertThat(context).doesNotHaveBean(WavefrontBraveSpanHandler.class); + assertThat(context).doesNotHaveBean(WavefrontOtelSpanExporter.class); + }); + } + + @Test + void shouldNotSupplyBeansIfWavefrontTracingIsDisabled() { + this.contextRunner.withPropertyValues("management.wavefront.tracing.export.enabled=false") + .withUserConfiguration(WavefrontSenderConfiguration.class) + .run((context) -> { + assertThat(context).doesNotHaveBean(WavefrontSpanHandler.class); assertThat(context).doesNotHaveBean(WavefrontBraveSpanHandler.class); assertThat(context).doesNotHaveBean(WavefrontOtelSpanExporter.class); }); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/DefaultEncodingConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/DefaultEncodingConfiguration.java new file mode 100644 index 000000000000..b1551b95cc7f --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/DefaultEncodingConfiguration.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; + +import zipkin2.reporter.Encoding; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; + +/** + * Configures the bean {@linkplain ZipkinAutoConfiguration} would from properties. + */ +@TestConfiguration(proxyBeanMethods = false) +class DefaultEncodingConfiguration { + + @Bean + @ConditionalOnMissingBean + Encoding zipkinReporterEncoding() { + return Encoding.JSON; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/NoopSender.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/NoopSender.java index 48a7926e7818..5bb3d4088fca 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/NoopSender.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/NoopSender.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,18 +16,16 @@ package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; +import java.io.IOException; import java.util.List; -import zipkin2.Call; -import zipkin2.Callback; -import zipkin2.codec.Encoding; -import zipkin2.reporter.Sender; +import zipkin2.reporter.BytesMessageSender; +import zipkin2.reporter.Encoding; -class NoopSender extends Sender { +class NoopSender extends BytesMessageSender.Base { - @Override - public Encoding encoding() { - return Encoding.JSON; + NoopSender(Encoding encoding) { + super(encoding); } @Override @@ -36,27 +34,11 @@ public int messageMaxBytes() { } @Override - public int messageSizeInBytes(List encodedSpans) { - return encoding().listSizeInBytes(encodedSpans); + public void send(List encodedSpans) { } @Override - public Call sendSpans(List encodedSpans) { - return new Call.Base<>() { - @Override - public Call clone() { - return this; - } - - @Override - protected Void doExecute() { - return null; - } - - @Override - protected void doEnqueue(Callback callback) { - } - }; + public void close() throws IOException { } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/TestHttpEndpointSupplier.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/TestHttpEndpointSupplier.java new file mode 100644 index 000000000000..27f20a07747a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/TestHttpEndpointSupplier.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; + +import java.util.concurrent.atomic.AtomicInteger; + +import zipkin2.reporter.HttpEndpointSupplier; + +/** + * Test {@link HttpEndpointSupplier}. + * + * @author Moritz Halbritter + */ +class TestHttpEndpointSupplier implements HttpEndpointSupplier { + + private final String url; + + private final AtomicInteger suffix = new AtomicInteger(); + + TestHttpEndpointSupplier(String url) { + this.url = url; + } + + @Override + public String get() { + return this.url + "/" + this.suffix.incrementAndGet(); + } + + @Override + public void close() { + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfigurationTests.java index 6453d24137dd..3a5323cef723 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfigurationTests.java @@ -17,9 +17,7 @@ package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; import org.junit.jupiter.api.Test; -import zipkin2.Span; -import zipkin2.codec.BytesEncoder; -import zipkin2.codec.SpanBytesEncoder; +import zipkin2.reporter.Encoding; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.FilteredClassLoader; @@ -41,30 +39,24 @@ class ZipkinAutoConfigurationTests { @Test void shouldSupplyBeans() { - this.contextRunner.run((context) -> assertThat(context).hasSingleBean(BytesEncoder.class) + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(Encoding.class) .hasSingleBean(PropertiesZipkinConnectionDetails.class)); } @Test void shouldNotSupplyBeansIfZipkinReporterIsMissing() { this.contextRunner.withClassLoader(new FilteredClassLoader("zipkin2.reporter")) - .run((context) -> assertThat(context).doesNotHaveBean(BytesEncoder.class)); + .run((context) -> assertThat(context).doesNotHaveBean(Encoding.class)); } @Test void shouldBackOffOnCustomBeans() { this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { - assertThat(context).hasBean("customBytesEncoder"); - assertThat(context).hasSingleBean(BytesEncoder.class); + assertThat(context).hasBean("customEncoding"); + assertThat(context).hasSingleBean(Encoding.class); }); } - @Test - void shouldNotSupplyBeansIfTracingIsDisabled() { - this.contextRunner.withPropertyValues("management.tracing.enabled=false") - .run((context) -> assertThat(context).doesNotHaveBean(BytesEncoder.class)); - } - @Test void definesPropertiesBasedConnectionDetailsByDefault() { this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesZipkinConnectionDetails.class)); @@ -72,14 +64,8 @@ void definesPropertiesBasedConnectionDetailsByDefault() { @Test void shouldUseCustomConnectionDetailsWhenDefined() { - this.contextRunner.withBean(ZipkinConnectionDetails.class, () -> new ZipkinConnectionDetails() { - - @Override - public String getSpanEndpoint() { - return "http://localhost"; - } - - }) + this.contextRunner + .withBean(ZipkinConnectionDetails.class, () -> new FixedZipkinConnectionDetails("http://localhost")) .run((context) -> assertThat(context).hasSingleBean(ZipkinConnectionDetails.class) .doesNotHaveBean(PropertiesZipkinConnectionDetails.class)); } @@ -92,12 +78,27 @@ void shouldWorkWithoutSenders() { .run((context) -> assertThat(context).hasNotFailed()); } + private static final class FixedZipkinConnectionDetails implements ZipkinConnectionDetails { + + private final String spanEndpoint; + + private FixedZipkinConnectionDetails(String spanEndpoint) { + this.spanEndpoint = spanEndpoint; + } + + @Override + public String getSpanEndpoint() { + return this.spanEndpoint; + } + + } + @Configuration(proxyBeanMethods = false) private static final class CustomConfiguration { @Bean - BytesEncoder customBytesEncoder() { - return SpanBytesEncoder.JSON_V2; + Encoding customEncoding() { + return Encoding.PROTO3; } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsBraveConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsBraveConfigurationTests.java index ba3c26c1f91d..8926921ff53e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsBraveConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsBraveConfigurationTests.java @@ -16,11 +16,17 @@ package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; +import java.nio.charset.StandardCharsets; + +import brave.Tag; +import brave.handler.MutableSpan; import brave.handler.SpanHandler; +import brave.propagation.TraceContext; import org.junit.jupiter.api.Test; -import zipkin2.Span; -import zipkin2.reporter.Reporter; -import zipkin2.reporter.brave.ZipkinSpanHandler; +import zipkin2.reporter.BytesEncoder; +import zipkin2.reporter.BytesMessageSender; +import zipkin2.reporter.Encoding; +import zipkin2.reporter.brave.AsyncZipkinSpanHandler; import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.BraveConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -40,52 +46,127 @@ class ZipkinConfigurationsBraveConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(BraveConfiguration.class)); + .withConfiguration(AutoConfigurations.of(DefaultEncodingConfiguration.class, BraveConfiguration.class)); @Test void shouldSupplyBeans() { - this.contextRunner.withUserConfiguration(ReporterConfiguration.class) - .run((context) -> assertThat(context).hasSingleBean(ZipkinSpanHandler.class)); + this.contextRunner.withUserConfiguration(SenderConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(AsyncZipkinSpanHandler.class)); } @Test void shouldNotSupplySpanHandlerIfReporterIsMissing() { - this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ZipkinSpanHandler.class)); + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(AsyncZipkinSpanHandler.class)); } @Test void shouldNotSupplyIfZipkinReporterBraveIsNotOnClasspath() { + // Note: Technically, Brave can work without zipkin-reporter. For example, + // WavefrontSpanHandler doesn't require this to operate. If we remove this + // dependency enforcement when WavefrontSpanHandler is in use, we can resolve + // micrometer-metrics/tracing#509. We also need this for any configuration that + // uses senders defined in the Spring Boot source tree, such as HttpSender. this.contextRunner.withClassLoader(new FilteredClassLoader("zipkin2.reporter.brave")) - .withUserConfiguration(ReporterConfiguration.class) - .run((context) -> assertThat(context).doesNotHaveBean(ZipkinSpanHandler.class)); - + .withUserConfiguration(SenderConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(AsyncZipkinSpanHandler.class)); } @Test void shouldBackOffOnCustomBeans() { - this.contextRunner.withUserConfiguration(ReporterConfiguration.class, CustomConfiguration.class) + this.contextRunner.withUserConfiguration(SenderConfiguration.class, CustomConfiguration.class) .run((context) -> { - assertThat(context).hasBean("customZipkinSpanHandler"); - assertThat(context).hasSingleBean(ZipkinSpanHandler.class); + assertThat(context).hasBean("customAsyncZipkinSpanHandler"); + assertThat(context).hasSingleBean(AsyncZipkinSpanHandler.class); }); } @Test - void shouldSupplyZipkinSpanHandlerWithCustomSpanHandler() { - this.contextRunner.withUserConfiguration(ReporterConfiguration.class, CustomSpanHandlerConfiguration.class) + void shouldSupplyAsyncZipkinSpanHandlerWithCustomSpanHandler() { + this.contextRunner.withUserConfiguration(SenderConfiguration.class, CustomSpanHandlerConfiguration.class) .run((context) -> { assertThat(context).hasBean("customSpanHandler"); - assertThat(context).hasSingleBean(ZipkinSpanHandler.class); + assertThat(context).hasSingleBean(AsyncZipkinSpanHandler.class); + }); + } + + @Test + void shouldNotSupplyAsyncZipkinSpanHandlerIfGlobalTracingIsDisabled() { + this.contextRunner.withPropertyValues("management.tracing.enabled=false") + .withUserConfiguration(SenderConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(AsyncZipkinSpanHandler.class)); + } + + @Test + void shouldNotSupplyAsyncZipkinSpanHandlerIfZipkinTracingIsDisabled() { + this.contextRunner.withPropertyValues("management.zipkin.tracing.export.enabled=false") + .withUserConfiguration(SenderConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(AsyncZipkinSpanHandler.class)); + } + + @Test + void shouldUseCustomEncoderBean() { + this.contextRunner.withUserConfiguration(SenderConfiguration.class, CustomEncoderConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(AsyncZipkinSpanHandler.class); + assertThat(context.getBean(AsyncZipkinSpanHandler.class)).extracting("spanReporter.encoder") + .isInstanceOf(CustomMutableSpanEncoder.class) + .extracting("encoding") + .isEqualTo(Encoding.JSON); + }); + } + + @Test + void shouldUseCustomEncodingBean() { + this.contextRunner + .withUserConfiguration(SenderConfiguration.class, CustomEncodingConfiguration.class, + CustomEncoderConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(AsyncZipkinSpanHandler.class); + assertThat(context.getBean(AsyncZipkinSpanHandler.class)).extracting("encoding") + .isEqualTo(Encoding.PROTO3); + }); + } + + @Test + void shouldUseDefaultThrowableTagBean() { + this.contextRunner.withUserConfiguration(SenderConfiguration.class).run((context) -> { + @SuppressWarnings("unchecked") + BytesEncoder encoder = context.getBean(BytesEncoder.class); + MutableSpan span = createTestSpan(); + // default tag key name is "error", and doesn't overwrite + assertThat(new String(encoder.encode(span), StandardCharsets.UTF_8)).isEqualTo( + "{\"traceId\":\"0000000000000001\",\"id\":\"0000000000000001\",\"tags\":{\"error\":\"true\"}}"); + }); + } + + @Test + void shouldUseCustomThrowableTagBean() { + this.contextRunner.withUserConfiguration(SenderConfiguration.class, CustomThrowableTagConfiguration.class) + .run((context) -> { + @SuppressWarnings("unchecked") + BytesEncoder encoder = context.getBean(BytesEncoder.class); + MutableSpan span = createTestSpan(); + // The custom throwable parser doesn't use the key "error" we can see both + assertThat(new String(encoder.encode(span), StandardCharsets.UTF_8)).isEqualTo( + "{\"traceId\":\"0000000000000001\",\"id\":\"0000000000000001\",\"tags\":{\"error\":\"true\",\"exception\":\"ice cream\"}}"); }); } + private MutableSpan createTestSpan() { + MutableSpan span = new MutableSpan(); + span.traceId("1"); + span.id("1"); + span.tag("error", "true"); + span.error(new RuntimeException("ice cream")); + return span; + } + @Configuration(proxyBeanMethods = false) - private static final class ReporterConfiguration { + private static final class SenderConfiguration { @Bean - @SuppressWarnings("unchecked") - Reporter reporter() { - return mock(Reporter.class); + BytesMessageSender sender(Encoding encoding) { + return new NoopSender(encoding); } } @@ -94,9 +175,23 @@ Reporter reporter() { private static final class CustomConfiguration { @Bean - @SuppressWarnings("unchecked") - ZipkinSpanHandler customZipkinSpanHandler() { - return (ZipkinSpanHandler) ZipkinSpanHandler.create(mock(Reporter.class)); + AsyncZipkinSpanHandler customAsyncZipkinSpanHandler() { + return AsyncZipkinSpanHandler.create(new NoopSender(Encoding.JSON)); + } + + } + + @Configuration(proxyBeanMethods = false) + private static final class CustomThrowableTagConfiguration { + + @Bean + Tag throwableTag() { + return new Tag<>("exception") { + @Override + protected String parseValue(Throwable throwable, TraceContext traceContext) { + return throwable.getMessage(); + } + }; } } @@ -111,4 +206,38 @@ SpanHandler customSpanHandler() { } + @Configuration(proxyBeanMethods = false) + private static final class CustomEncodingConfiguration { + + @Bean + Encoding encoding() { + return Encoding.PROTO3; + } + + } + + @Configuration(proxyBeanMethods = false) + private static final class CustomEncoderConfiguration { + + @Bean + BytesEncoder encoder(Encoding encoding) { + return new CustomMutableSpanEncoder(encoding); + } + + } + + private record CustomMutableSpanEncoder(Encoding encoding) implements BytesEncoder { + + @Override + public int sizeInBytes(MutableSpan span) { + throw new UnsupportedOperationException(); + } + + @Override + public byte[] encode(MutableSpan span) { + throw new UnsupportedOperationException(); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsOpenTelemetryConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsOpenTelemetryConfigurationTests.java index 27fd9be141d1..7b1c09fc06ae 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsOpenTelemetryConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsOpenTelemetryConfigurationTests.java @@ -19,13 +19,12 @@ import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter; import org.junit.jupiter.api.Test; import zipkin2.Span; -import zipkin2.codec.BytesEncoder; -import zipkin2.codec.SpanBytesEncoder; -import zipkin2.reporter.Sender; +import zipkin2.reporter.BytesEncoder; +import zipkin2.reporter.BytesMessageSender; +import zipkin2.reporter.Encoding; import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.OpenTelemetryConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -41,27 +40,46 @@ class ZipkinConfigurationsOpenTelemetryConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(BaseConfiguration.class, OpenTelemetryConfiguration.class)); + .withConfiguration(AutoConfigurations.of(DefaultEncodingConfiguration.class, OpenTelemetryConfiguration.class)); @Test void shouldSupplyBeans() { - this.contextRunner.withUserConfiguration(SenderConfiguration.class) - .run((context) -> assertThat(context).hasSingleBean(ZipkinSpanExporter.class)); + this.contextRunner.withUserConfiguration(SenderConfiguration.class, CustomEncoderConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(ZipkinSpanExporter.class); + assertThat(context).hasBean("customSpanEncoder"); + }); } @Test void shouldNotSupplyZipkinSpanExporterIfSenderIsMissing() { - this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ZipkinSpanExporter.class)); + this.contextRunner.run((context) -> { + assertThat(context).doesNotHaveBean(ZipkinSpanExporter.class); + assertThat(context).hasBean("spanBytesEncoder"); + }); } @Test void shouldNotSupplyZipkinSpanExporterIfNotOnClasspath() { this.contextRunner.withClassLoader(new FilteredClassLoader("io.opentelemetry.exporter.zipkin")) .withUserConfiguration(SenderConfiguration.class) - .run((context) -> assertThat(context).doesNotHaveBean(ZipkinSpanExporter.class)); + .run((context) -> { + assertThat(context).doesNotHaveBean(ZipkinSpanExporter.class); + assertThat(context).doesNotHaveBean("spanBytesEncoder"); + }); } + @Test + void shouldBackOffIfZipkinIsNotOnClasspath() { + this.contextRunner.withClassLoader(new FilteredClassLoader("zipkin2.Span")) + .withUserConfiguration(SenderConfiguration.class) + .run((context) -> { + assertThat(context).doesNotHaveBean(ZipkinSpanExporter.class); + assertThat(context).doesNotHaveBean("spanBytesEncoder"); + }); + } + @Test void shouldBackOffOnCustomBeans() { this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { @@ -70,12 +88,64 @@ void shouldBackOffOnCustomBeans() { }); } + @Test + void shouldNotSupplyZipkinSpanExporterIfGlobalTracingIsDisabled() { + this.contextRunner.withPropertyValues("management.tracing.enabled=false") + .withUserConfiguration(SenderConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(ZipkinSpanExporter.class)); + } + + @Test + void shouldNotSupplyZipkinSpanExporterIfZipkinTracingIsDisabled() { + this.contextRunner.withPropertyValues("management.zipkin.tracing.export.enabled=false") + .withUserConfiguration(SenderConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(ZipkinSpanExporter.class)); + } + + @Test + void shouldUseCustomEncoderBean() { + this.contextRunner.withUserConfiguration(SenderConfiguration.class, CustomEncoderConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(ZipkinSpanExporter.class); + assertThat(context).hasBean("customSpanEncoder"); + assertThat(context.getBean(ZipkinSpanExporter.class)).extracting("encoder") + .isInstanceOf(CustomSpanEncoder.class) + .extracting("encoding") + .isEqualTo(Encoding.JSON); + }); + } + + @Test + void shouldUseCustomEncodingBean() { + this.contextRunner + .withUserConfiguration(SenderConfiguration.class, CustomEncodingConfiguration.class, + CustomEncoderConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(ZipkinSpanExporter.class); + assertThat(context).hasBean("customSpanEncoder"); + assertThat(context.getBean(ZipkinSpanExporter.class)).extracting("encoder") + .isInstanceOf(CustomSpanEncoder.class) + .extracting("encoding") + .isEqualTo(Encoding.PROTO3); + }); + } + + @Configuration(proxyBeanMethods = false) + private static final class CustomEncodingConfiguration { + + @Bean + Encoding encoding() { + return Encoding.PROTO3; + } + + } + @Configuration(proxyBeanMethods = false) private static final class SenderConfiguration { @Bean - Sender sender() { - return new NoopSender(); + BytesMessageSender sender(Encoding encoding) { + return new NoopSender(encoding); } } @@ -91,12 +161,25 @@ ZipkinSpanExporter customZipkinSpanExporter() { } @Configuration(proxyBeanMethods = false) - private static final class BaseConfiguration { + private static final class CustomEncoderConfiguration { @Bean - @ConditionalOnMissingBean - BytesEncoder spanBytesEncoder() { - return SpanBytesEncoder.JSON_V2; + BytesEncoder customSpanEncoder(Encoding encoding) { + return new CustomSpanEncoder(encoding); + } + + } + + record CustomSpanEncoder(Encoding encoding) implements BytesEncoder { + + @Override + public int sizeInBytes(Span span) { + throw new UnsupportedOperationException(); + } + + @Override + public byte[] encode(Span span) { + throw new UnsupportedOperationException(); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsReporterConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsReporterConfigurationTests.java deleted file mode 100644 index 45a9f3f1d22d..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsReporterConfigurationTests.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2012-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; - -import org.junit.jupiter.api.Test; -import zipkin2.Span; -import zipkin2.codec.BytesEncoder; -import zipkin2.codec.SpanBytesEncoder; -import zipkin2.reporter.Reporter; -import zipkin2.reporter.Sender; - -import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.ReporterConfiguration; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link ReporterConfiguration}. - * - * @author Moritz Halbritter - */ -class ZipkinConfigurationsReporterConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(BaseConfiguration.class, ReporterConfiguration.class)); - - @Test - void shouldSupplyBeans() { - this.contextRunner.withUserConfiguration(SenderConfiguration.class) - .run((context) -> assertThat(context).hasSingleBean(Reporter.class)); - } - - @Test - void shouldNotSupplyReporterIfSenderIsMissing() { - this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(Reporter.class)); - } - - @Test - void shouldBackOffOnCustomBeans() { - this.contextRunner.withUserConfiguration(SenderConfiguration.class, CustomConfiguration.class) - .run((context) -> { - assertThat(context).hasBean("customReporter"); - assertThat(context).hasSingleBean(Reporter.class); - }); - } - - @Configuration(proxyBeanMethods = false) - private static final class SenderConfiguration { - - @Bean - Sender sender() { - return new NoopSender(); - } - - } - - @Configuration(proxyBeanMethods = false) - private static final class CustomConfiguration { - - @Bean - @SuppressWarnings("unchecked") - Reporter customReporter() { - return mock(Reporter.class); - } - - } - - @Configuration(proxyBeanMethods = false) - private static final class BaseConfiguration { - - @Bean - @ConditionalOnMissingBean - BytesEncoder spanBytesEncoder() { - return SpanBytesEncoder.JSON_V2; - } - - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsSenderConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsSenderConfigurationTests.java index 9b98d24710a4..5e7d7340b5bf 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsSenderConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsSenderConfigurationTests.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.concurrent.TimeUnit; import okhttp3.mockwebserver.MockResponse; @@ -25,7 +26,8 @@ import okhttp3.mockwebserver.RecordedRequest; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; -import zipkin2.reporter.Sender; +import zipkin2.reporter.BytesMessageSender; +import zipkin2.reporter.HttpEndpointSupplier; import zipkin2.reporter.urlconnection.URLConnectionSender; import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.SenderConfiguration; @@ -48,33 +50,48 @@ * * @author Moritz Halbritter */ +@SuppressWarnings("removal") class ZipkinConfigurationsSenderConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(SenderConfiguration.class)); + .withConfiguration(AutoConfigurations.of(DefaultEncodingConfiguration.class, SenderConfiguration.class)); private final ReactiveWebApplicationContextRunner reactiveContextRunner = new ReactiveWebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(SenderConfiguration.class)); + .withConfiguration(AutoConfigurations.of(DefaultEncodingConfiguration.class, SenderConfiguration.class)); private final WebApplicationContextRunner servletContextRunner = new WebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(SenderConfiguration.class)); + .withConfiguration(AutoConfigurations.of(DefaultEncodingConfiguration.class, SenderConfiguration.class)); @Test void shouldSupplyBeans() { this.contextRunner.run((context) -> { - assertThat(context).hasSingleBean(Sender.class); + assertThat(context).hasSingleBean(BytesMessageSender.class); assertThat(context).hasSingleBean(URLConnectionSender.class); assertThat(context).doesNotHaveBean(ZipkinRestTemplateSender.class); }); } + @Test + void shouldUseHttpClientIfUrlSenderIsNotAvailable() { + this.contextRunner.withUserConfiguration(HttpClientConfiguration.class) + .withClassLoader(new FilteredClassLoader("zipkin2.reporter.urlconnection", "org.springframework.web.client", + "org.springframework.web.reactive.function.client")) + .run((context) -> { + assertThat(context).doesNotHaveBean(URLConnectionSender.class); + assertThat(context).hasSingleBean(BytesMessageSender.class); + assertThat(context).hasSingleBean(ZipkinHttpClientSender.class); + then(context.getBean(ZipkinHttpClientBuilderCustomizer.class)).should() + .customize(ArgumentMatchers.any()); + }); + } + @Test void shouldPreferWebClientSenderIfWebApplicationIsReactiveAndUrlSenderIsNotAvailable() { this.reactiveContextRunner.withUserConfiguration(RestTemplateConfiguration.class, WebClientConfiguration.class) .withClassLoader(new FilteredClassLoader("zipkin2.reporter.urlconnection")) .run((context) -> { assertThat(context).doesNotHaveBean(URLConnectionSender.class); - assertThat(context).hasSingleBean(Sender.class); + assertThat(context).hasSingleBean(BytesMessageSender.class); assertThat(context).hasSingleBean(ZipkinWebClientSender.class); then(context.getBean(ZipkinWebClientBuilderCustomizer.class)).should() .customize(ArgumentMatchers.any()); @@ -87,7 +104,7 @@ void shouldPreferWebClientSenderIfWebApplicationIsServletAndUrlSenderIsNotAvaila .withClassLoader(new FilteredClassLoader("zipkin2.reporter.urlconnection")) .run((context) -> { assertThat(context).doesNotHaveBean(URLConnectionSender.class); - assertThat(context).hasSingleBean(Sender.class); + assertThat(context).hasSingleBean(BytesMessageSender.class); assertThat(context).hasSingleBean(ZipkinWebClientSender.class); }); } @@ -98,7 +115,7 @@ void shouldPreferWebClientInNonWebApplicationAndUrlConnectionSenderIsNotAvailabl .withClassLoader(new FilteredClassLoader("zipkin2.reporter.urlconnection")) .run((context) -> { assertThat(context).doesNotHaveBean(URLConnectionSender.class); - assertThat(context).hasSingleBean(Sender.class); + assertThat(context).hasSingleBean(BytesMessageSender.class); assertThat(context).hasSingleBean(ZipkinWebClientSender.class); }); } @@ -109,7 +126,7 @@ void willUseRestTemplateInNonWebApplicationIfUrlConnectionSenderAndWebClientAreN .withClassLoader(new FilteredClassLoader(URLConnectionSender.class, WebClient.class)) .run((context) -> { assertThat(context).doesNotHaveBean(URLConnectionSender.class); - assertThat(context).hasSingleBean(Sender.class); + assertThat(context).hasSingleBean(BytesMessageSender.class); assertThat(context).hasSingleBean(ZipkinRestTemplateSender.class); }); } @@ -120,7 +137,7 @@ void willUseRestTemplateInServletWebApplicationIfUrlConnectionSenderAndWebClient .withClassLoader(new FilteredClassLoader(URLConnectionSender.class, WebClient.class)) .run((context) -> { assertThat(context).doesNotHaveBean(URLConnectionSender.class); - assertThat(context).hasSingleBean(Sender.class); + assertThat(context).hasSingleBean(BytesMessageSender.class); assertThat(context).hasSingleBean(ZipkinRestTemplateSender.class); }); } @@ -131,7 +148,7 @@ void willUseRestTemplateInReactiveWebApplicationIfUrlConnectionSenderAndWebClien .withClassLoader(new FilteredClassLoader(URLConnectionSender.class, WebClient.class)) .run((context) -> { assertThat(context).doesNotHaveBean(URLConnectionSender.class); - assertThat(context).hasSingleBean(Sender.class); + assertThat(context).hasSingleBean(BytesMessageSender.class); assertThat(context).hasSingleBean(ZipkinRestTemplateSender.class); }); } @@ -140,7 +157,7 @@ void willUseRestTemplateInReactiveWebApplicationIfUrlConnectionSenderAndWebClien void shouldNotUseWebClientSenderIfNoBuilderIsAvailable() { this.reactiveContextRunner.run((context) -> { assertThat(context).doesNotHaveBean(ZipkinWebClientSender.class); - assertThat(context).hasSingleBean(Sender.class); + assertThat(context).hasSingleBean(BytesMessageSender.class); assertThat(context).hasSingleBean(URLConnectionSender.class); }); } @@ -149,7 +166,7 @@ void shouldNotUseWebClientSenderIfNoBuilderIsAvailable() { void shouldBackOffOnCustomBeans() { this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { assertThat(context).hasBean("customSender"); - assertThat(context).hasSingleBean(Sender.class); + assertThat(context).hasSingleBean(BytesMessageSender.class); }); } @@ -164,7 +181,7 @@ void shouldApplyZipkinRestTemplateBuilderCustomizers() throws IOException { .run((context) -> { assertThat(context).hasSingleBean(ZipkinRestTemplateSender.class); ZipkinRestTemplateSender sender = context.getBean(ZipkinRestTemplateSender.class); - sender.sendSpans("spans".getBytes(StandardCharsets.UTF_8)).execute(); + sender.send(List.of("spans".getBytes(StandardCharsets.UTF_8))); RecordedRequest recordedRequest = mockWebServer.takeRequest(1, TimeUnit.SECONDS); assertThat(recordedRequest).isNotNull(); assertThat(recordedRequest.getHeaders().get("x-dummy")).isEqualTo("dummy"); @@ -172,6 +189,32 @@ void shouldApplyZipkinRestTemplateBuilderCustomizers() throws IOException { } } + @Test + void shouldUseCustomHttpEndpointSupplierFactory() { + this.contextRunner.withUserConfiguration(CustomHttpEndpointSupplierFactoryConfiguration.class) + .run((context) -> assertThat(context.getBean(URLConnectionSender.class)) + .extracting("delegate.endpointSupplier") + .isInstanceOf(CustomHttpEndpointSupplier.class)); + } + + @Test + void shouldUseCustomHttpEndpointSupplierFactoryWhenReactive() { + this.reactiveContextRunner.withUserConfiguration(WebClientConfiguration.class) + .withClassLoader(new FilteredClassLoader(URLConnectionSender.class)) + .withUserConfiguration(CustomHttpEndpointSupplierFactoryConfiguration.class) + .run((context) -> assertThat(context.getBean(ZipkinWebClientSender.class)).extracting("endpointSupplier") + .isInstanceOf(CustomHttpEndpointSupplier.class)); + } + + @Test + void shouldUseCustomHttpEndpointSupplierFactoryWhenRestTemplate() { + this.contextRunner.withUserConfiguration(RestTemplateConfiguration.class) + .withClassLoader(new FilteredClassLoader(URLConnectionSender.class, WebClient.class)) + .withUserConfiguration(CustomHttpEndpointSupplierFactoryConfiguration.class) + .run((context) -> assertThat(context.getBean(ZipkinRestTemplateSender.class)).extracting("endpointSupplier") + .isInstanceOf(CustomHttpEndpointSupplier.class)); + } + @Configuration(proxyBeanMethods = false) private static final class RestTemplateConfiguration { @@ -192,12 +235,22 @@ ZipkinWebClientBuilderCustomizer webClientBuilder() { } + @Configuration(proxyBeanMethods = false) + private static final class HttpClientConfiguration { + + @Bean + ZipkinHttpClientBuilderCustomizer httpClientBuilderCustomizer() { + return mock(ZipkinHttpClientBuilderCustomizer.class); + } + + } + @Configuration(proxyBeanMethods = false) private static final class CustomConfiguration { @Bean - Sender customSender() { - return mock(Sender.class); + BytesMessageSender customSender() { + return mock(BytesMessageSender.class); } } @@ -211,4 +264,35 @@ public RestTemplateBuilder customize(RestTemplateBuilder restTemplateBuilder) { } + @Configuration(proxyBeanMethods = false) + private static final class CustomHttpEndpointSupplierFactoryConfiguration { + + @Bean + HttpEndpointSupplier.Factory httpEndpointSupplier() { + return new CustomHttpEndpointSupplierFactory(); + } + + } + + private static final class CustomHttpEndpointSupplierFactory implements HttpEndpointSupplier.Factory { + + @Override + public HttpEndpointSupplier create(String endpoint) { + return new CustomHttpEndpointSupplier(endpoint); + } + + } + + private record CustomHttpEndpointSupplier(String endpoint) implements HttpEndpointSupplier { + + @Override + public String get() { + return this.endpoint; + } + + @Override + public void close() { + } + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpClientSenderTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpClientSenderTests.java new file mode 100644 index 000000000000..f91b13e53231 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpClientSenderTests.java @@ -0,0 +1,170 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.time.Duration; +import java.util.Base64; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import zipkin2.reporter.BytesMessageSender; +import zipkin2.reporter.Encoding; +import zipkin2.reporter.HttpEndpointSupplier; +import zipkin2.reporter.HttpEndpointSuppliers; + +import org.springframework.http.HttpHeaders; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatException; +import static org.assertj.core.api.Assertions.assertThatIOException; + +/** + * Tests for {@link ZipkinHttpClientSender}. + * + * @author Moritz Halbritter + */ +class ZipkinHttpClientSenderTests extends ZipkinHttpSenderTests { + + private MockWebServer mockBackEnd; + + private String zipkinUrl; + + @Override + @BeforeEach + void beforeEach() throws Exception { + this.mockBackEnd = new MockWebServer(); + this.mockBackEnd.start(); + this.zipkinUrl = this.mockBackEnd.url("/api/v2/spans").toString(); + super.beforeEach(); + } + + @Override + void afterEach() throws IOException { + super.afterEach(); + this.mockBackEnd.shutdown(); + } + + @Override + BytesMessageSender createSender() { + return createSender(Encoding.JSON, Duration.ofSeconds(10)); + } + + ZipkinHttpClientSender createSender(Encoding encoding, Duration timeout) { + return createSender(HttpEndpointSuppliers.constantFactory(), encoding, timeout); + } + + ZipkinHttpClientSender createSender(HttpEndpointSupplier.Factory endpointSupplierFactory, Encoding encoding, + Duration timeout) { + HttpClient httpClient = HttpClient.newBuilder().connectTimeout(timeout).build(); + return new ZipkinHttpClientSender(encoding, endpointSupplierFactory, this.zipkinUrl, httpClient, timeout); + } + + @Test + void sendShouldSendSpansToZipkin() throws IOException, InterruptedException { + this.mockBackEnd.enqueue(new MockResponse()); + List encodedSpans = List.of(toByteArray("span1"), toByteArray("span2")); + this.sender.send(encodedSpans); + requestAssertions((request) -> { + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getHeader("Content-Type")).isEqualTo("application/json"); + assertThat(request.getBody().readUtf8()).isEqualTo("[span1,span2]"); + }); + } + + @Test + void sendShouldSendSpansToZipkinInProto3() throws IOException, InterruptedException { + this.mockBackEnd.enqueue(new MockResponse()); + List encodedSpans = List.of(toByteArray("span1"), toByteArray("span2")); + try (BytesMessageSender sender = createSender(Encoding.PROTO3, Duration.ofSeconds(10))) { + sender.send(encodedSpans); + } + requestAssertions((request) -> { + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getHeader("Content-Type")).isEqualTo("application/x-protobuf"); + assertThat(request.getBody().readUtf8()).isEqualTo("span1span2"); + }); + } + + /** + * This tests that a dynamic {@linkplain HttpEndpointSupplier} updates are visible to + * {@link HttpSender#postSpans(URI, HttpHeaders, byte[])}. + */ + @Test + void sendUsesDynamicEndpoint() throws Exception { + this.mockBackEnd.enqueue(new MockResponse()); + this.mockBackEnd.enqueue(new MockResponse()); + try (TestHttpEndpointSupplier httpEndpointSupplier = new TestHttpEndpointSupplier(this.zipkinUrl)) { + try (BytesMessageSender sender = createSender((endpoint) -> httpEndpointSupplier, Encoding.JSON, + Duration.ofSeconds(10))) { + sender.send(Collections.emptyList()); + sender.send(Collections.emptyList()); + } + assertThat(this.mockBackEnd.takeRequest().getPath()).endsWith("/1"); + assertThat(this.mockBackEnd.takeRequest().getPath()).endsWith("/2"); + } + } + + @Test + void sendShouldHandleHttpFailures() throws InterruptedException { + this.mockBackEnd.enqueue(new MockResponse().setResponseCode(500)); + assertThatException().isThrownBy(() -> this.sender.send(Collections.emptyList())) + .withMessageContaining("Expected HTTP status 2xx, got 500"); + requestAssertions((request) -> assertThat(request.getMethod()).isEqualTo("POST")); + } + + @Test + void sendShouldCompressData() throws IOException, InterruptedException { + String uncompressed = "a".repeat(10000); + // This is gzip compressed 10000 times 'a' + byte[] compressed = Base64.getDecoder() + .decode("H4sIAAAAAAAA/+3BMQ0AAAwDIKFLj/k3UR8NcA8AAAAAAAAAAAADUsAZfeASJwAA"); + this.mockBackEnd.enqueue(new MockResponse()); + this.sender.send(List.of(toByteArray(uncompressed))); + requestAssertions((request) -> { + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getHeader("Content-Type")).isEqualTo("application/json"); + assertThat(request.getHeader("Content-Encoding")).isEqualTo("gzip"); + assertThat(request.getBody().readByteArray()).isEqualTo(compressed); + }); + } + + @Test + void shouldTimeout() throws IOException { + try (BytesMessageSender sender = createSender(Encoding.JSON, Duration.ofMillis(1))) { + MockResponse response = new MockResponse().setResponseCode(200).setHeadersDelay(100, TimeUnit.MILLISECONDS); + this.mockBackEnd.enqueue(response); + assertThatIOException().isThrownBy(() -> sender.send(Collections.emptyList())) + .withMessageContaining("timed out"); + } + } + + private void requestAssertions(Consumer assertions) throws InterruptedException { + RecordedRequest request = this.mockBackEnd.takeRequest(); + assertThat(request).satisfies(assertions); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpSenderTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpSenderTests.java index 541747968f24..5de178b69134 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpSenderTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpSenderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,20 +18,14 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.time.Duration; import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicReference; -import org.awaitility.Awaitility; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import zipkin2.Callback; +import zipkin2.reporter.BytesMessageSender; import zipkin2.reporter.ClosedSenderException; -import zipkin2.reporter.Sender; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** @@ -42,57 +36,29 @@ */ abstract class ZipkinHttpSenderTests { - protected Sender sut; + protected BytesMessageSender sender; - abstract Sender createSut(); + abstract BytesMessageSender createSender(); @BeforeEach - void setUp() { - this.sut = createSut(); + void beforeEach() throws Exception { + this.sender = createSender(); } - @Test - void sendSpansShouldThrowIfCloseWasCalled() throws IOException { - this.sut.close(); - assertThatExceptionOfType(ClosedSenderException.class) - .isThrownBy(() -> this.sut.sendSpans(Collections.emptyList())); - } - - protected void makeRequest(List encodedSpans, boolean async) throws IOException { - if (async) { - CallbackResult callbackResult = makeAsyncRequest(encodedSpans); - assertThat(callbackResult.success()).isTrue(); - } - else { - makeSyncRequest(encodedSpans); - } + @AfterEach + void afterEach() throws IOException { + this.sender.close(); } - protected CallbackResult makeAsyncRequest(List encodedSpans) { - AtomicReference callbackResult = new AtomicReference<>(); - this.sut.sendSpans(encodedSpans).enqueue(new Callback<>() { - @Override - public void onSuccess(Void value) { - callbackResult.set(new CallbackResult(true, null)); - } - - @Override - public void onError(Throwable t) { - callbackResult.set(new CallbackResult(false, t)); - } - }); - return Awaitility.await().atMost(Duration.ofSeconds(5)).until(callbackResult::get, Objects::nonNull); - } - - protected void makeSyncRequest(List encodedSpans) throws IOException { - this.sut.sendSpans(encodedSpans).execute(); + @Test + void sendShouldThrowIfCloseWasCalled() throws IOException { + this.sender.close(); + assertThatExceptionOfType(ClosedSenderException.class) + .isThrownBy(() -> this.sender.send(Collections.emptyList())); } protected byte[] toByteArray(String input) { return input.getBytes(StandardCharsets.UTF_8); } - record CallbackResult(boolean success, Throwable error) { - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateSenderTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateSenderTests.java index 195345199065..7e891d7138c9 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateSenderTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateSenderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,23 +17,24 @@ package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; import java.io.IOException; +import java.net.URI; import java.util.Base64; import java.util.Collections; import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import zipkin2.CheckResult; -import zipkin2.reporter.Sender; +import zipkin2.reporter.BytesMessageSender; +import zipkin2.reporter.Encoding; +import zipkin2.reporter.HttpEndpointSupplier; +import zipkin2.reporter.HttpEndpointSuppliers; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.web.client.RestTemplate; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatException; import static org.springframework.test.web.client.match.MockRestRequestMatchers.content; import static org.springframework.test.web.client.match.MockRestRequestMatchers.header; @@ -47,74 +48,87 @@ * @author Moritz Halbritter * @author Stefan Bratanov */ +@SuppressWarnings("removal") class ZipkinRestTemplateSenderTests extends ZipkinHttpSenderTests { private static final String ZIPKIN_URL = "http://localhost:9411/api/v2/spans"; + private RestTemplate restTemplate; + private MockRestServiceServer mockServer; @Override - Sender createSut() { - RestTemplate restTemplate = new RestTemplate(); - this.mockServer = MockRestServiceServer.createServer(restTemplate); - return new ZipkinRestTemplateSender(ZIPKIN_URL, restTemplate); + BytesMessageSender createSender() { + this.restTemplate = new RestTemplate(); + this.mockServer = MockRestServiceServer.createServer(this.restTemplate); + return createSender(Encoding.JSON); + } + + BytesMessageSender createSender(Encoding encoding) { + return createSender(HttpEndpointSuppliers.constantFactory(), encoding); + } + + BytesMessageSender createSender(HttpEndpointSupplier.Factory endpointSupplierFactory, Encoding encoding) { + return new ZipkinRestTemplateSender(encoding, endpointSupplierFactory, ZIPKIN_URL, this.restTemplate); } @AfterEach - void tearDown() { + @Override + void afterEach() throws IOException { + super.afterEach(); this.mockServer.verify(); } @Test - void checkShouldSendEmptySpanList() { + void sendShouldSendSpansToZipkin() throws IOException { this.mockServer.expect(requestTo(ZIPKIN_URL)) .andExpect(method(HttpMethod.POST)) - .andExpect(content().string("[]")) + .andExpect(content().contentType("application/json")) + .andExpect(content().string("[span1,span2]")) .andRespond(withStatus(HttpStatus.ACCEPTED)); - assertThat(this.sut.check()).isEqualTo(CheckResult.OK); + this.sender.send(List.of(toByteArray("span1"), toByteArray("span2"))); } @Test - void checkShouldNotRaiseException() { + void sendShouldSendSpansToZipkinInProto3() throws IOException { this.mockServer.expect(requestTo(ZIPKIN_URL)) .andExpect(method(HttpMethod.POST)) - .andRespond(withStatus(HttpStatus.INTERNAL_SERVER_ERROR)); - CheckResult result = this.sut.check(); - assertThat(result.ok()).isFalse(); - assertThat(result.error()).hasMessageContaining("500 Internal Server Error"); + .andExpect(content().contentType("application/x-protobuf")) + .andExpect(content().string("span1span2")) + .andRespond(withStatus(HttpStatus.ACCEPTED)); + try (BytesMessageSender sender = createSender(Encoding.PROTO3)) { + sender.send(List.of(toByteArray("span1"), toByteArray("span2"))); + } } - @ParameterizedTest - @ValueSource(booleans = { true, false }) - void sendSpansShouldSendSpansToZipkin(boolean async) throws IOException { - this.mockServer.expect(requestTo(ZIPKIN_URL)) - .andExpect(method(HttpMethod.POST)) - .andExpect(content().contentType("application/json")) - .andExpect(content().string("[span1,span2]")) - .andRespond(withStatus(HttpStatus.ACCEPTED)); - makeRequest(List.of(toByteArray("span1"), toByteArray("span2")), async); + /** + * This tests that a dynamic {@linkplain HttpEndpointSupplier} updates are visible to + * {@link HttpSender#postSpans(URI, HttpHeaders, byte[])}. + */ + @Test + void sendUsesDynamicEndpoint() throws Exception { + this.mockServer.expect(requestTo(ZIPKIN_URL + "/1")).andRespond(withStatus(HttpStatus.ACCEPTED)); + this.mockServer.expect(requestTo(ZIPKIN_URL + "/2")).andRespond(withStatus(HttpStatus.ACCEPTED)); + try (HttpEndpointSupplier httpEndpointSupplier = new TestHttpEndpointSupplier(ZIPKIN_URL)) { + try (BytesMessageSender sender = createSender((endpoint) -> httpEndpointSupplier, Encoding.JSON)) { + sender.send(Collections.emptyList()); + sender.send(Collections.emptyList()); + } + } } - @ParameterizedTest - @ValueSource(booleans = { true, false }) - void sendSpansShouldHandleHttpFailures(boolean async) { + @Test + void sendShouldHandleHttpFailures() { this.mockServer.expect(requestTo(ZIPKIN_URL)) .andExpect(method(HttpMethod.POST)) .andRespond(withStatus(HttpStatus.INTERNAL_SERVER_ERROR)); - if (async) { - CallbackResult callbackResult = makeAsyncRequest(Collections.emptyList()); - assertThat(callbackResult.success()).isFalse(); - assertThat(callbackResult.error()).isNotNull().hasMessageContaining("500 Internal Server Error"); - } - else { - assertThatException().isThrownBy(() -> makeSyncRequest(Collections.emptyList())) - .withMessageContaining("500 Internal Server Error"); - } + + assertThatException().isThrownBy(() -> this.sender.send(Collections.emptyList())) + .withMessageContaining("500 Internal Server Error"); } - @ParameterizedTest - @ValueSource(booleans = { true, false }) - void sendSpansShouldCompressData(boolean async) throws IOException { + @Test + void sendShouldCompressData() throws IOException { String uncompressed = "a".repeat(10000); // This is gzip compressed 10000 times 'a' byte[] compressed = Base64.getDecoder() @@ -125,7 +139,7 @@ void sendSpansShouldCompressData(boolean async) throws IOException { .andExpect(content().contentType("application/json")) .andExpect(content().bytes(compressed)) .andRespond(withStatus(HttpStatus.ACCEPTED)); - makeRequest(List.of(toByteArray(uncompressed)), async); + this.sender.send(List.of(toByteArray(uncompressed))); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientSenderTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientSenderTests.java index 0b6236585c10..c917e9f346c5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientSenderTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientSenderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,22 +17,29 @@ package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; import java.io.IOException; +import java.net.URI; +import java.time.Duration; import java.util.Base64; import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.QueueDispatcher; import okhttp3.mockwebserver.RecordedRequest; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import zipkin2.CheckResult; -import zipkin2.reporter.Sender; +import zipkin2.reporter.BytesMessageSender; +import zipkin2.reporter.Encoding; +import zipkin2.reporter.HttpEndpointSupplier; +import zipkin2.reporter.HttpEndpointSuppliers; +import org.springframework.http.HttpHeaders; import org.springframework.web.reactive.function.client.WebClient; import static org.assertj.core.api.Assertions.assertThat; @@ -43,101 +50,129 @@ * * @author Stefan Bratanov */ +@SuppressWarnings("removal") class ZipkinWebClientSenderTests extends ZipkinHttpSenderTests { + private static ClearableDispatcher dispatcher; + private static MockWebServer mockBackEnd; private static String ZIPKIN_URL; @BeforeAll static void beforeAll() throws IOException { + dispatcher = new ClearableDispatcher(); mockBackEnd = new MockWebServer(); + mockBackEnd.setDispatcher(dispatcher); mockBackEnd.start(); - ZIPKIN_URL = "http://localhost:%s/api/v2/spans".formatted(mockBackEnd.getPort()); + ZIPKIN_URL = mockBackEnd.url("/api/v2/spans").toString(); } @AfterAll - static void tearDown() throws IOException { + static void afterAll() throws IOException { mockBackEnd.shutdown(); } @Override - Sender createSut() { + @BeforeEach + void beforeEach() throws Exception { + super.beforeEach(); + clearResponses(); + clearRequests(); + } + + @Override + BytesMessageSender createSender() { + return createSender(Encoding.JSON, Duration.ofSeconds(10)); + } + + ZipkinWebClientSender createSender(Encoding encoding, Duration timeout) { + return createSender(HttpEndpointSuppliers.constantFactory(), encoding, timeout); + } + + ZipkinWebClientSender createSender(HttpEndpointSupplier.Factory endpointSupplierFactory, Encoding encoding, + Duration timeout) { WebClient webClient = WebClient.builder().build(); - return new ZipkinWebClientSender(ZIPKIN_URL, webClient); + return new ZipkinWebClientSender(encoding, endpointSupplierFactory, ZIPKIN_URL, webClient, timeout); } @Test - void checkShouldSendEmptySpanList() throws InterruptedException { + void sendShouldSendSpansToZipkin() throws IOException, InterruptedException { mockBackEnd.enqueue(new MockResponse()); - assertThat(this.sut.check()).isEqualTo(CheckResult.OK); - + List encodedSpans = List.of(toByteArray("span1"), toByteArray("span2")); + this.sender.send(encodedSpans); requestAssertions((request) -> { assertThat(request.getMethod()).isEqualTo("POST"); - assertThat(request.getBody().readUtf8()).isEqualTo("[]"); + assertThat(request.getHeader("Content-Type")).isEqualTo("application/json"); + assertThat(request.getBody().readUtf8()).isEqualTo("[span1,span2]"); }); } @Test - void checkShouldNotRaiseException() throws InterruptedException { - mockBackEnd.enqueue(new MockResponse().setResponseCode(500)); - CheckResult result = this.sut.check(); - assertThat(result.ok()).isFalse(); - assertThat(result.error()).hasMessageContaining("500 Internal Server Error"); - - requestAssertions((request) -> assertThat(request.getMethod()).isEqualTo("POST")); - } - - @ParameterizedTest - @ValueSource(booleans = { true, false }) - void sendSpansShouldSendSpansToZipkin(boolean async) throws IOException, InterruptedException { + void sendShouldSendSpansToZipkinInProto3() throws IOException, InterruptedException { mockBackEnd.enqueue(new MockResponse()); List encodedSpans = List.of(toByteArray("span1"), toByteArray("span2")); - makeRequest(encodedSpans, async); - + try (BytesMessageSender sender = createSender(Encoding.PROTO3, Duration.ofSeconds(10))) { + sender.send(encodedSpans); + } requestAssertions((request) -> { assertThat(request.getMethod()).isEqualTo("POST"); - assertThat(request.getHeader("Content-Type")).isEqualTo("application/json"); - assertThat(request.getBody().readUtf8()).isEqualTo("[span1,span2]"); + assertThat(request.getHeader("Content-Type")).isEqualTo("application/x-protobuf"); + assertThat(request.getBody().readUtf8()).isEqualTo("span1span2"); }); } - @ParameterizedTest - @ValueSource(booleans = { true, false }) - void sendSpansShouldHandleHttpFailures(boolean async) throws InterruptedException { - mockBackEnd.enqueue(new MockResponse().setResponseCode(500)); - if (async) { - CallbackResult callbackResult = makeAsyncRequest(Collections.emptyList()); - assertThat(callbackResult.success()).isFalse(); - assertThat(callbackResult.error()).isNotNull().hasMessageContaining("500 Internal Server Error"); - } - else { - assertThatException().isThrownBy(() -> makeSyncRequest(Collections.emptyList())) - .withMessageContaining("500 Internal Server Error"); + /** + * This tests that a dynamic {@linkplain HttpEndpointSupplier} updates are visible to + * {@link HttpSender#postSpans(URI, HttpHeaders, byte[])}. + */ + @Test + void sendUsesDynamicEndpoint() throws Exception { + mockBackEnd.enqueue(new MockResponse()); + mockBackEnd.enqueue(new MockResponse()); + try (HttpEndpointSupplier httpEndpointSupplier = new TestHttpEndpointSupplier(ZIPKIN_URL)) { + try (BytesMessageSender sender = createSender((endpoint) -> httpEndpointSupplier, Encoding.JSON, + Duration.ofSeconds(10))) { + sender.send(Collections.emptyList()); + sender.send(Collections.emptyList()); + } + assertThat(mockBackEnd.takeRequest().getPath()).endsWith("/1"); + assertThat(mockBackEnd.takeRequest().getPath()).endsWith("/2"); } + } + @Test + void sendShouldHandleHttpFailures() throws InterruptedException { + mockBackEnd.enqueue(new MockResponse().setResponseCode(500)); + assertThatException().isThrownBy(() -> this.sender.send(Collections.emptyList())) + .withMessageContaining("500 Internal Server Error"); requestAssertions((request) -> assertThat(request.getMethod()).isEqualTo("POST")); } - @ParameterizedTest - @ValueSource(booleans = { true, false }) - void sendSpansShouldCompressData(boolean async) throws IOException, InterruptedException { + @Test + void sendShouldCompressData() throws IOException, InterruptedException { String uncompressed = "a".repeat(10000); // This is gzip compressed 10000 times 'a' byte[] compressed = Base64.getDecoder() .decode("H4sIAAAAAAAA/+3BMQ0AAAwDIKFLj/k3UR8NcA8AAAAAAAAAAAADUsAZfeASJwAA"); - mockBackEnd.enqueue(new MockResponse()); - - makeRequest(List.of(toByteArray(uncompressed)), async); - + this.sender.send(List.of(toByteArray(uncompressed))); requestAssertions((request) -> { assertThat(request.getMethod()).isEqualTo("POST"); assertThat(request.getHeader("Content-Type")).isEqualTo("application/json"); assertThat(request.getHeader("Content-Encoding")).isEqualTo("gzip"); assertThat(request.getBody().readByteArray()).isEqualTo(compressed); }); + } + @Test + void shouldTimeout() throws IOException { + try (BytesMessageSender sender = createSender(Encoding.JSON, Duration.ofMillis(1))) { + MockResponse response = new MockResponse().setResponseCode(200).setHeadersDelay(100, TimeUnit.MILLISECONDS); + mockBackEnd.enqueue(response); + assertThatException().isThrownBy(() -> sender.send(Collections.emptyList())) + .withCauseInstanceOf(TimeoutException.class); + } } private void requestAssertions(Consumer assertions) throws InterruptedException { @@ -145,4 +180,24 @@ private void requestAssertions(Consumer assertions) throws Inte assertThat(request).satisfies(assertions); } + private static void clearRequests() throws InterruptedException { + RecordedRequest request; + do { + request = mockBackEnd.takeRequest(0, TimeUnit.SECONDS); + } + while (request != null); + } + + private static void clearResponses() { + dispatcher.clear(); + } + + private static final class ClearableDispatcher extends QueueDispatcher { + + void clear() { + getResponseQueue().clear(); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontPropertiesTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontPropertiesTests.java index 3e697616afbc..b9aaca914390 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontPropertiesTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontPropertiesTests.java @@ -18,8 +18,12 @@ import java.net.URI; +import com.wavefront.sdk.common.clients.service.token.TokenService.Type; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.springframework.boot.actuate.autoconfigure.wavefront.WavefrontProperties.TokenType; import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; import static org.assertj.core.api.Assertions.assertThat; @@ -34,21 +38,54 @@ class WavefrontPropertiesTests { @Test void apiTokenIsOptionalWhenUsingProxy() { - WavefrontProperties sut = new WavefrontProperties(); - sut.setUri(URI.create("proxy://localhost:2878")); - sut.setApiToken(null); - assertThat(sut.getApiTokenOrThrow()).isNull(); - assertThat(sut.getEffectiveUri()).isEqualTo(URI.create("http://localhost:2878")); + WavefrontProperties properties = new WavefrontProperties(); + properties.setUri(URI.create("proxy://localhost:2878")); + properties.setApiToken(null); + assertThat(properties.getApiTokenOrThrow()).isNull(); + assertThat(properties.getEffectiveUri()).isEqualTo(URI.create("http://localhost:2878")); } @Test void apiTokenIsMandatoryWhenNotUsingProxy() { - WavefrontProperties sut = new WavefrontProperties(); - sut.setUri(URI.create("http://localhost:2878")); - sut.setApiToken(null); - assertThat(sut.getEffectiveUri()).isEqualTo(URI.create("http://localhost:2878")); - assertThatExceptionOfType(InvalidConfigurationPropertyValueException.class).isThrownBy(sut::getApiTokenOrThrow) + WavefrontProperties properties = new WavefrontProperties(); + properties.setUri(URI.create("http://localhost:2878")); + properties.setApiToken(null); + assertThat(properties.getEffectiveUri()).isEqualTo(URI.create("http://localhost:2878")); + assertThatExceptionOfType(InvalidConfigurationPropertyValueException.class) + .isThrownBy(properties::getApiTokenOrThrow) .withMessageContaining("management.wavefront.api-token"); } + @Test + void shouldNotFailIfTokenTypeIsSetToNoToken() { + WavefrontProperties properties = new WavefrontProperties(); + properties.setUri(URI.create("http://localhost:2878")); + properties.setApiTokenType(TokenType.NO_TOKEN); + properties.setApiToken(null); + assertThat(properties.getApiTokenOrThrow()).isNull(); + } + + @Test + void wavefrontApiTokenTypeWhenUsingProxy() { + WavefrontProperties properties = new WavefrontProperties(); + properties.setUri(URI.create("proxy://localhost:2878")); + assertThat(properties.getWavefrontApiTokenType()).isEqualTo(Type.NO_TOKEN); + } + + @Test + void wavefrontApiTokenTypeWhenNotUsingProxy() { + WavefrontProperties properties = new WavefrontProperties(); + properties.setUri(URI.create("http://localhost:2878")); + assertThat(properties.getWavefrontApiTokenType()).isEqualTo(Type.WAVEFRONT_API_TOKEN); + } + + @ParameterizedTest + @EnumSource(TokenType.class) + void wavefrontApiTokenMapping(TokenType from) { + WavefrontProperties properties = new WavefrontProperties(); + properties.setApiTokenType(from); + Type expected = Type.valueOf(from.name()); + assertThat(properties.getWavefrontApiTokenType()).isEqualTo(expected); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontSenderConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontSenderConfigurationTests.java index a75203e4fedf..b0afc5f46a0c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontSenderConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontSenderConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,9 @@ import java.util.concurrent.LinkedBlockingQueue; import com.wavefront.sdk.common.WavefrontSender; +import com.wavefront.sdk.common.clients.service.token.CSPTokenService; +import com.wavefront.sdk.common.clients.service.token.NoopProxyTokenService; +import com.wavefront.sdk.common.clients.service.token.WavefrontTokenService; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; @@ -42,6 +45,9 @@ class WavefrontSenderConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(WavefrontSenderConfiguration.class)); + private final ApplicationContextRunner metricsDisabledContextRunner = this.contextRunner.withPropertyValues( + "management.defaults.metrics.export.enabled=false", "management.simple.metrics.export.enabled=true"); + @Test void shouldNotFailIfWavefrontIsMissing() { this.contextRunner.withClassLoader(new FilteredClassLoader("com.wavefront")) @@ -83,12 +89,89 @@ void configureWavefrontSender() { }); } + @Test + void shouldNotSupplyWavefrontSenderIfMetricsAndGlobalTracingIsDisabled() { + this.metricsDisabledContextRunner + .withPropertyValues("management.tracing.enabled=false", "management.wavefront.api-token=abcde") + .run((context) -> assertThat(context).doesNotHaveBean(WavefrontSender.class)); + } + + @Test + void shouldNotSupplyWavefrontSenderIfMetricsAndWavefrontTracingIsDisabled() { + this.metricsDisabledContextRunner + .withPropertyValues("management.wavefront.tracing.export.enabled=false", + "management.wavefront.api-token=abcde") + .run((context) -> assertThat(context).doesNotHaveBean(WavefrontSender.class)); + } + + @Test + void shouldSupplyWavefrontSenderIfOnlyGlobalTracingIsDisabled() { + this.contextRunner + .withPropertyValues("management.tracing.enabled=false", "management.wavefront.api-token=abcde") + .run((context) -> assertThat(context).hasSingleBean(WavefrontSender.class)); + } + + @Test + void shouldSupplyWavefrontSenderIfOnlyWavefrontTracingIsDisabled() { + this.contextRunner + .withPropertyValues("management.wavefront.tracing.export.enabled=false", + "management.wavefront.api-token=abcde") + .run((context) -> assertThat(context).hasSingleBean(WavefrontSender.class)); + } + + @Test + void shouldSupplyWavefrontSenderIfOnlyMetricsAreDisabled() { + this.metricsDisabledContextRunner.withPropertyValues("management.wavefront.api-token=abcde") + .run((context) -> assertThat(context).hasSingleBean(WavefrontSender.class)); + } + @Test void allowsWavefrontSenderToBeCustomized() { this.contextRunner.withUserConfiguration(CustomSenderConfiguration.class) .run((context) -> assertThat(context).hasSingleBean(WavefrontSender.class).hasBean("customSender")); } + @Test + void shouldApplyTokenTypeWavefrontApiToken() { + this.contextRunner + .withPropertyValues("management.wavefront.api-token-type=WAVEFRONT_API_TOKEN", + "management.wavefront.api-token=abcde") + .run((context) -> { + WavefrontSender sender = context.getBean(WavefrontSender.class); + assertThat(sender).extracting("tokenService").isInstanceOf(WavefrontTokenService.class); + }); + } + + @Test + void shouldApplyTokenTypeCspApiToken() { + this.contextRunner + .withPropertyValues("management.wavefront.api-token-type=CSP_API_TOKEN", + "management.wavefront.api-token=abcde") + .run((context) -> { + WavefrontSender sender = context.getBean(WavefrontSender.class); + assertThat(sender).extracting("tokenService").isInstanceOf(CSPTokenService.class); + }); + } + + @Test + void shouldApplyTokenTypeCspClientCredentials() { + this.contextRunner + .withPropertyValues("management.wavefront.api-token-type=CSP_CLIENT_CREDENTIALS", + "management.wavefront.api-token=clientid=cid,clientsecret=csec") + .run((context) -> { + WavefrontSender sender = context.getBean(WavefrontSender.class); + assertThat(sender).extracting("tokenService").isInstanceOf(CSPTokenService.class); + }); + } + + @Test + void shouldApplyTokenTypeNoToken() { + this.contextRunner.withPropertyValues("management.wavefront.api-token-type=NO_TOKEN").run((context) -> { + WavefrontSender sender = context.getBean(WavefrontSender.class); + assertThat(sender).extracting("tokenService").isInstanceOf(NoopProxyTokenService.class); + }); + } + @Configuration(proxyBeanMethods = false) static class CustomSenderConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfigurationTests.java index 1d4e7c731983..a617750be9ed 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfigurationTests.java @@ -55,6 +55,33 @@ void childManagementContextShouldStartForEmbeddedServer(CapturedOutput output) { .run((context) -> assertThat(output).satisfies(numberOfOccurrences("Tomcat started on port", 2))); } + @Test + void childManagementContextShouldNotStartWithoutEmbeddedServer(CapturedOutput output) { + WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class, + ServletWebServerFactoryAutoConfiguration.class, ServletManagementContextAutoConfiguration.class, + WebEndpointAutoConfiguration.class, EndpointAutoConfiguration.class)); + contextRunner.withPropertyValues("server.port=0", "management.server.port=0").run((context) -> { + assertThat(context).hasNotFailed(); + assertThat(output).doesNotContain("Tomcat started"); + }); + } + + @Test + void childManagementContextShouldRestartWhenParentIsStoppedThenStarted(CapturedOutput output) { + WebApplicationContextRunner contextRunner = new WebApplicationContextRunner( + AnnotationConfigServletWebServerApplicationContext::new) + .withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class, + ServletWebServerFactoryAutoConfiguration.class, ServletManagementContextAutoConfiguration.class, + WebEndpointAutoConfiguration.class, EndpointAutoConfiguration.class)); + contextRunner.withPropertyValues("server.port=0", "management.server.port=0").run((context) -> { + assertThat(output).satisfies(numberOfOccurrences("Tomcat started on port", 2)); + context.getSourceApplicationContext().stop(); + context.getSourceApplicationContext().start(); + assertThat(output).satisfies(numberOfOccurrences("Tomcat started on port", 4)); + }); + } + @Test void givenSamePortManagementServerWhenManagementServerAddressIsConfiguredThenContextRefreshFails() { WebApplicationContextRunner contextRunner = new WebApplicationContextRunner( diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationIntegrationTests.java index 4dfd5c0732e1..1699f1091dd8 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import reactor.core.publisher.Mono; @@ -143,7 +144,8 @@ void errorEndpointIsUsedWithRestControllerEndpointOnBindingError() { (value) -> assertThat(value).asString().contains("MethodArgumentNotValidException")); assertThat(body).hasEntrySatisfying("message", (value) -> assertThat(value).asString().contains("Validation failed")); - assertThat(body).hasEntrySatisfying("errors", (value) -> assertThat(value).asList().isNotEmpty()); + assertThat(body).hasEntrySatisfying("errors", + (value) -> assertThat(value).asInstanceOf(InstanceOfAssertFactories.LIST).isNotEmpty()); })); } @@ -215,6 +217,7 @@ String fail() { } @RestControllerEndpoint(id = "failController") + @SuppressWarnings("removal") static class FailingControllerEndpoint { @GetMapping diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/resources/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/sample.log b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/resources/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/sample.log index 9b8f1ab7eced..b1f92c2d2c35 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/resources/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/sample.log +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/resources/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/sample.log @@ -9,7 +9,7 @@ 2017-08-08 17:12:30.910 INFO 19866 --- [ main] s.f.SampleWebFreeMarkerApplication : Starting SampleWebFreeMarkerApplication with PID 19866 2017-08-08 17:12:30.913 INFO 19866 --- [ main] s.f.SampleWebFreeMarkerApplication : No active profile set, falling back to default profiles: default 2017-08-08 17:12:30.952 INFO 19866 --- [ main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@76b10754: startup date [Tue Aug 08 17:12:30 BST 2017]; root of context hierarchy -2017-08-08 17:12:31.878 INFO 19866 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) +2017-08-08 17:12:31.878 INFO 19866 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http) 2017-08-08 17:12:31.889 INFO 19866 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2017-08-08 17:12:31.890 INFO 19866 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.16 2017-08-08 17:12:31.978 INFO 19866 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext @@ -27,5 +27,5 @@ 2017-08-08 17:12:32.471 INFO 19866 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2017-08-08 17:12:32.600 INFO 19866 --- [ main] o.s.w.s.v.f.FreeMarkerConfigurer : ClassTemplateLoader for Spring macros added to FreeMarker configuration 2017-08-08 17:12:32.681 INFO 19866 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup -2017-08-08 17:12:32.744 INFO 19866 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) +2017-08-08 17:12:32.744 INFO 19866 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) 2017-08-08 17:12:32.750 INFO 19866 --- [ main] s.f.SampleWebFreeMarkerApplication : Started SampleWebFreeMarkerApplication in 2.172 seconds (JVM running for 2.479) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/resources/sbom/cyclonedx.json b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/resources/sbom/cyclonedx.json new file mode 100644 index 000000000000..d5c78df8ea6f --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/resources/sbom/cyclonedx.json @@ -0,0 +1,4615 @@ +{ + "bomFormat" : "CycloneDX", + "specVersion" : "1.5", + "serialNumber" : "urn:uuid:13862013-3360-43e5-8055-3645aa43c548", + "version" : 1, + "metadata" : { + "timestamp" : "2024-01-12T11:10:49Z", + "tools" : [ + { + "vendor" : "CycloneDX", + "name" : "cyclonedx-gradle-plugin", + "version" : "1.8.1" + } + ], + "component" : { + "group" : "org.example", + "name" : "cyclonedx", + "version" : "0.0.1-SNAPSHOT", + "purl" : "pkg:maven/org.example/cyclonedx@0.0.1-SNAPSHOT?type=jar", + "type" : "library", + "bom-ref" : "pkg:maven/org.example/cyclonedx@0.0.1-SNAPSHOT?type=jar" + } + }, + "components" : [ + { + "publisher" : "Spring IO", + "group" : "org.springframework", + "name" : "spring-aop", + "version" : "6.1.2", + "description" : "Spring AOP", + "hashes" : [ + { + "alg" : "MD5", + "content" : "c9b8757051ed6c1cc9fda0e379283348" + }, + { + "alg" : "SHA-1", + "content" : "a247bd81df8fa9c6a002b95969692bfd146a70b2" + }, + { + "alg" : "SHA-256", + "content" : "e47b66833ebec281374d55b4e36352b80fe3fa64c94252481a8a7e8d31d9d601" + }, + { + "alg" : "SHA-512", + "content" : "b1cb69feb2931bd4af48b2329614f8e2a0d1afe77267af5f5ea9717ab24c83fd524c8bc7aa8d357a6ccbc497535c4fd282ddfb6d78364a349895a14825af8b9c" + }, + { + "alg" : "SHA-384", + "content" : "09c3c2711a054993922d28b76357c376649a942bf0d7410915e540339c3fa42d5a498211b02e0b09493e68fac7a0d833" + }, + { + "alg" : "SHA3-384", + "content" : "b30a6ea50e454373bd74779d983fc941bb1775368ea67ff0464edbdf0dd3d1c137760bee64a620bd51daf5b65281f15e" + }, + { + "alg" : "SHA3-256", + "content" : "291404410acd2cfbcc804bd91a9777276f622fb3b82788298254c0bf1856b49f" + }, + { + "alg" : "SHA3-512", + "content" : "8101ef2cc88af43b2bfc6126547de4e4a4cc29bf49bffd83aa9d299cab9e9cdb6a5246d46c00119dd88ca02dbf7959c3076dbd32d23e8e1366144ccbbda13316" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework/spring-aop@6.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io/projects/spring-framework" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-framework/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-framework" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework/spring-aop@6.1.2?type=jar" + }, + { + "group" : "com.fasterxml.jackson.datatype", + "name" : "jackson-datatype-jdk8", + "version" : "2.15.3", + "description" : "Add-on module for Jackson (http://jackson.codehaus.org) to support JDK 8 data types.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "3b6579ff944e128c4eccb34e76ff67e0" + }, + { + "alg" : "SHA-1", + "content" : "80158cb020c7bd4e4ba94d8d752a65729dc943b2" + }, + { + "alg" : "SHA-256", + "content" : "29995d3677f72dde74bf32bbf268b96beb952492b742d93f4c70af6c44b2156e" + }, + { + "alg" : "SHA-512", + "content" : "1b13d4f0a955af18a2c68ca45deca79c38d7f9f065d7053bddf2a3dc2fafe729b3355676f7442012451e363aa0da0cd8a0b7a44ded7057cf513df98a475cbbf6" + }, + { + "alg" : "SHA-384", + "content" : "9a29961097a15d3aeabc1ab870699dce827511df9902fc66fe9f836d294c8cea68617498d52fe7dbe920bb5c745f2789" + }, + { + "alg" : "SHA3-384", + "content" : "55570097f9979197eafda91156db909f25dd1b37387656893564060a673dcbc6d85c1f5dc6fd5c8b379b48a4974e6757" + }, + { + "alg" : "SHA3-256", + "content" : "362c3a494e16016f7adc3f512ebe8c8f8da4dbdfc1ca285d05ac085a9198258f" + }, + { + "alg" : "SHA3-512", + "content" : "1aebbe19a11236b7dbf85fd4c457e1a9b5a60fad9c818cc9fd462d7eb489dd5d3a378b4c7c42c6e3777e0b70263968c964cf1aaf8247fc97ec445481af2418a8" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jdk8@2.15.3?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jdk8@2.15.3?type=jar" + }, + { + "group" : "org.apiguardian", + "name" : "apiguardian-api", + "version" : "1.1.2", + "description" : "@API Guardian", + "hashes" : [ + { + "alg" : "MD5", + "content" : "8c7de3f82037fa4a2e8be2a2f13092af" + }, + { + "alg" : "SHA-1", + "content" : "a231e0d844d2721b0fa1b238006d15c6ded6842a" + }, + { + "alg" : "SHA-256", + "content" : "b509448ac506d607319f182537f0b35d71007582ec741832a1f111e5b5b70b38" + }, + { + "alg" : "SHA-512", + "content" : "d7ccd0e7019f1a997de39d66dc0ad4efe150428fdd7f4c743c93884f1602a3e90135ad34baea96d5b6d925ad6c0c8487c8e78304f0a089a12383d4a62e2c9a61" + }, + { + "alg" : "SHA-384", + "content" : "5ae11cfedcee7da43a506a67946ddc8a7a2622284a924ba78f74541e9a22db6868a15f5d84edb91a541e38afded734ea" + }, + { + "alg" : "SHA3-384", + "content" : "c146116b3dfd969200b2ce52d96b92dd02d6f5a45a86e7e85edf35600ddbc2f3c6e8a1ad7e2db4dcd2c398c09fad0927" + }, + { + "alg" : "SHA3-256", + "content" : "b4b436d7f615fc0b820204e69f83c517d1c1ccc5f6b99e459209ede4482268de" + }, + { + "alg" : "SHA3-512", + "content" : "7b95b7ac68a6891b8901b5507acd2c24a0c1e20effa63cd513764f513eab4eb55f8de5178edbe0a400c11f3a18d3f56243569d6d663100f06dd98288504c09c5" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.apiguardian/apiguardian-api@1.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/apiguardian-team/apiguardian" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.apiguardian/apiguardian-api@1.1.2?type=jar" + }, + { + "group" : "jakarta.annotation", + "name" : "jakarta.annotation-api", + "version" : "2.1.1", + "description" : "Jakarta Annotations API", + "hashes" : [ + { + "alg" : "MD5", + "content" : "5dac2f68e8288d0add4dc92cb161711d" + }, + { + "alg" : "SHA-1", + "content" : "48b9bda22b091b1f48b13af03fe36db3be6e1ae3" + }, + { + "alg" : "SHA-256", + "content" : "5f65fdaf424eee2b55e1d882ba9bb376be93fb09b37b808be6e22e8851c909fe" + }, + { + "alg" : "SHA-512", + "content" : "eabe8b855b735663684052ec4cc357cc737936fa57cebf144eb09f70b3b6c600db7fa6f1c93a4f36c5994b1b37dad2dfcec87a41448872e69552accfd7f52af6" + }, + { + "alg" : "SHA-384", + "content" : "798597a6b80b423844d70609c54b00d725a357031888da7e5c3efd3914d1770be69aa7135de13ddb89a4420a5550e35b" + }, + { + "alg" : "SHA3-384", + "content" : "9629b8ca82f61674f5573723bbb3c137060e1442062eb52fa9c90fc8f57ea7d836eb2fb765d160ec8bf300bcb6b820be" + }, + { + "alg" : "SHA3-256", + "content" : "f71ffc2a2c2bd1a00dfc00c4be67dbe5f374078bd50d5b24c0b29fbcc6634ecb" + }, + { + "alg" : "SHA3-512", + "content" : "aa4e29025a55878db6edb0d984bd3a0633f3af03fa69e1d26c97c87c6d29339714003c96e29ff0a977132ce9c2729d0e27e36e9e245a7488266138239bdba15e" + } + ], + "licenses" : [ + { + "license" : { + "id" : "EPL-2.0" + } + }, + { + "license" : { + "id" : "GPL-2.0-with-classpath-exception" + } + } + ], + "purl" : "pkg:maven/jakarta.annotation/jakarta.annotation-api@2.1.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "issue-tracker", + "url" : "https://github.com/eclipse-ee4j/common-annotations-api/issues" + }, + { + "type" : "mailing-list", + "url" : "https://dev.eclipse.org/mhonarc/lists/ca-dev" + }, + { + "type" : "vcs", + "url" : "https://github.com/eclipse-ee4j/common-annotations-api" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/jakarta.annotation/jakarta.annotation-api@2.1.1?type=jar" + }, + { + "group" : "com.fasterxml.jackson.core", + "name" : "jackson-annotations", + "version" : "2.15.3", + "description" : "Core annotations used for value types, used by Jackson data binding package.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "f478f693731e4a2f0f0d3c7bba119b32" + }, + { + "alg" : "SHA-1", + "content" : "79baf4e605eb3bbb60b1c475d44a7aecceea1d60" + }, + { + "alg" : "SHA-256", + "content" : "aae865c3d88256d61b11523cb1e88bd48d5b9ad5855fa1fc859504fd2204708a" + }, + { + "alg" : "SHA-512", + "content" : "c496afd736fa8acbf8126887e2ff375f162212f451326451fbb4b9194231d814e25bccacbaead9db98beec454f6b8d9ed706c5c88e2145bf7e1a37e13fd81af0" + }, + { + "alg" : "SHA-384", + "content" : "13b4d153cc113a69008147974d8887f868f2f3f0a551ef0bacaccf0add17a3168465a94a471e075913f9c6649980a3cb" + }, + { + "alg" : "SHA3-384", + "content" : "dcf8ed73f748eb32e1ab25eba3c294344cc0ddb2cc7bb4376814f1866df42c3093f1336291ce9ed9e1c8730663e0017c" + }, + { + "alg" : "SHA3-256", + "content" : "59f42bc85ee3a8a5b422085b0462aed2a770cf52d7a3660f2cd6dd257ec6e694" + }, + { + "alg" : "SHA3-512", + "content" : "1d1a6fd0e6851d419e79f82170f4060981c233ec8dc61656b84ce7988e9b71bbeecd7364cdadac066ddaf0b3de4dc8aa5acc411ebd1641f549a3af5ba214667b" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.15.3?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/FasterXML/jackson-annotations" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.15.3?type=jar" + }, + { + "publisher" : "Spring IO", + "group" : "org.springframework", + "name" : "spring-jcl", + "version" : "6.1.2", + "description" : "Spring Commons Logging Bridge", + "hashes" : [ + { + "alg" : "MD5", + "content" : "1638acc7030a001c37f803185dbd6eaf" + }, + { + "alg" : "SHA-1", + "content" : "285eb725861c9eacf2a3e4965d4e897932e335ea" + }, + { + "alg" : "SHA-256", + "content" : "eb9ebadb1581f0fe598216f7cf032a3b44a84c96de06ffa8d6f41bcc47305134" + }, + { + "alg" : "SHA-512", + "content" : "2e80d7485b7ad4de6cc372d86ed73db9808be6a5a33e3c9fabccc7915fe57b73011bed75b4567c44456fedad5ae2186658a7f5cc331b4aad64e2a7cc78acdcfa" + }, + { + "alg" : "SHA-384", + "content" : "a6a6422a6c2654eff951af0d6dfb6e93501bdcb4e38ec353d515ca8de919a34b9e1fe37c562106f3f33f844cf071e010" + }, + { + "alg" : "SHA3-384", + "content" : "71098eb263af3ab42d93b8e7a96ceb90fb2069f2ecca85754e702b82f9876255abf5e3f9b48beb4a200f2d9e13599794" + }, + { + "alg" : "SHA3-256", + "content" : "7f49ddd5db9841bb2d7ca8cb5ce52fa1e8982c7c37bc0c6e987eca8f5fc70d38" + }, + { + "alg" : "SHA3-512", + "content" : "4a417d058ecd3619a9716c5d47ecc506f4cb9c3684ee589c443c7b7996b630949932295186135cb3ce5fb0154c29436de4b6c1dbf7f135563449050973510200" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework/spring-jcl@6.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io/projects/spring-framework" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-framework/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-framework" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework/spring-jcl@6.1.2?type=jar" + }, + { + "publisher" : "Spring IO", + "group" : "org.springframework", + "name" : "spring-webmvc", + "version" : "6.1.2", + "description" : "Spring Web MVC", + "hashes" : [ + { + "alg" : "MD5", + "content" : "0fcf00ac160e0d42ad9cd242c796e47a" + }, + { + "alg" : "SHA-1", + "content" : "906ee995372076e22ef9666d8628845c75bf5c42" + }, + { + "alg" : "SHA-256", + "content" : "de42748c3c94c06131c3fe97d81f5c685e4492b9e986baa88af768bb12ea7738" + }, + { + "alg" : "SHA-512", + "content" : "8e7ad7afa2a605d8dbb6cb36c11caf0e626a5ca5849c06f0b35524e5ad6a13eec1ddff8625e1cc278b3082555a940ec3865657828458ab8d60d1c99d513aba0f" + }, + { + "alg" : "SHA-384", + "content" : "5ec328ff12f857baf85ce6f44c849f8818658aaabb4e4d0940ea6b5ad2b009ce3c7717b6b02843f641f8125d0cec4291" + }, + { + "alg" : "SHA3-384", + "content" : "75605b286d839df688bbfb9594dbb83d1eb22f2cae52a6f4b35d485e91ab94a55e94158086684ef3b059f1346af6dc85" + }, + { + "alg" : "SHA3-256", + "content" : "2e67bcc31eede462f5105a09dbf5b40a3e0ccc52d637c6e2720b43412da01525" + }, + { + "alg" : "SHA3-512", + "content" : "d7c5330069c3c0f5eda1417a52384a4b5adc4451c405315a992ed147f26466a19487ffc5e39b90a1ec4cb0df3f804a4d26203f9aaf4e74cf906d1e811abfbf3b" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework/spring-webmvc@6.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io/projects/spring-framework" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-framework/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-framework" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework/spring-webmvc@6.1.2?type=jar" + }, + { + "group" : "org.apache.tomcat.embed", + "name" : "tomcat-embed-websocket", + "version" : "10.1.17", + "description" : "Core Tomcat implementation", + "hashes" : [ + { + "alg" : "MD5", + "content" : "cfc1778713fba9b5bc33d3db64071dff" + }, + { + "alg" : "SHA-1", + "content" : "9ee2f34b51144b75878c9b42768e17de8fbdc74b" + }, + { + "alg" : "SHA-256", + "content" : "00b16e507bea58c6e8a7cb64f129cd2ffd62da092a67a693a8a6af1efdc7dd6d" + }, + { + "alg" : "SHA-512", + "content" : "72da073d4ec4f7473c9a91b4d11607d02a3d18ca8af10348f9130a280f898814625a5865cb44244e6be6d6ab915099805bf06a60f80fd9b8ff2c47840d5266e9" + }, + { + "alg" : "SHA-384", + "content" : "3f4c1d108ca60a7a658839b8ac45eba94354ad20e641d36d2ecf777bac252d371df1e8806a5460ccaf9da222f72a4a9c" + }, + { + "alg" : "SHA3-384", + "content" : "2d0703de58338d38fbae7f4a38390a766d66e3875e3a6a7f2620ae478c838c8f306a39cdac8652890e1116a3859e56e1" + }, + { + "alg" : "SHA3-256", + "content" : "e594abbc4cb6dc0896c08a89cb3fa376980587d5995bace2b3c0798d99c1e454" + }, + { + "alg" : "SHA3-512", + "content" : "3a35964398627fc8bcd323dd9fb6d4e51ea183b704074320822906c074aeb50a0f8732e42b98bdad9c5f0aa4eb421da96dde7e97f094ccdbcb70f668c6d4ff6e" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.apache.tomcat.embed/tomcat-embed-websocket@10.1.17?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.apache.tomcat.embed/tomcat-embed-websocket@10.1.17?type=jar" + }, + { + "group" : "net.bytebuddy", + "name" : "byte-buddy", + "version" : "1.14.10", + "description" : "Byte Buddy is a Java library for creating Java classes at run time. This artifact is a build of Byte Buddy with all ASM dependencies repackaged into its own name space.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "4e5bd83559bf8533b51f92dcd911d16c" + }, + { + "alg" : "SHA-1", + "content" : "8117daf4a612122eb4f517f66adff778cb8b4737" + }, + { + "alg" : "SHA-256", + "content" : "30e6e0446437a67db37e2b7f7d33f50787ddfd970359319dfd05469daa2dcbce" + }, + { + "alg" : "SHA-512", + "content" : "583512f3c47513cf17735aad4e600be44c97e9978c9f6a45227de8a160a879960b1fe01672751e7583176935e0db5477aba581bf68ef5c94f52436a0683a306e" + }, + { + "alg" : "SHA-384", + "content" : "efcce5a139f498de410e182a52e5b2465823a2ebf845001c9a733d87418118342c3854d00a0fae7945ae8dcb1916ba90" + }, + { + "alg" : "SHA3-384", + "content" : "cace3217b1c2c77a4bc194ecc602a28886d9e448efa26b1985e9fd09d90c92bc2e1b50ed70475106ddf266f8c2d14160" + }, + { + "alg" : "SHA3-256", + "content" : "71647273afb1561b70d2cfa519f707a98711f9ae5b891249ae5803c00c25a788" + }, + { + "alg" : "SHA3-512", + "content" : "4aba6f5dcac177c8f8aed902307c62916c32be61841adcf12b9c9885de2de9795a965c0b939729ed67ee7d49b0fbfaf0dfd922be1bf1cdbfbe7b1f09e083831b" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/net.bytebuddy/byte-buddy@1.14.10?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/net.bytebuddy/byte-buddy@1.14.10?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-test-autoconfigure", + "version" : "3.2.1", + "description" : "Spring Boot Test AutoConfigure", + "hashes" : [ + { + "alg" : "MD5", + "content" : "d6f93aa42df4cb27a58835750597d835" + }, + { + "alg" : "SHA-1", + "content" : "bfc34c523b3ab295fb01f46373e903f9729cdd43" + }, + { + "alg" : "SHA-256", + "content" : "86c51c743babfc591be09af7fedcd778410706e567e9ed27218448ccd2297ef4" + }, + { + "alg" : "SHA-512", + "content" : "701b6ee27c87081e4a65ba76fe721f74e917a655575b19b9205b314f4a561889564e09ceadaa880aaf30f70cd8b48dc70fc5e32f511204b1ea031a12349fd9be" + }, + { + "alg" : "SHA-384", + "content" : "74d4cf202399e946789a5572007aa4fbf1daf26cfac27f83a3d8550711f99700083029b1f900037b8f263543ac9824a1" + }, + { + "alg" : "SHA3-384", + "content" : "ac0b64ec94b558b4f806c09f68247eff80bcc8e33b97f5d09f5517a2339187e4b11c8e2287400a173cb128e3fdb4ab06" + }, + { + "alg" : "SHA3-256", + "content" : "5ca85cd0c052076d625c262cf445e4e8fb255b13323ba4ab08cbfcf32ec236b3" + }, + { + "alg" : "SHA3-512", + "content" : "04ce88c724852938057c723a7ec637af2f8e601879a592a6fe135eaa26940f8fd9d9ac8f6917e761cb0ff31547bb849ff88a66e1f6e93c1032a4009fe1fdef1d" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-test-autoconfigure@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-test-autoconfigure@3.2.1?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-starter-json", + "version" : "3.2.1", + "description" : "Starter for reading and writing json", + "hashes" : [ + { + "alg" : "MD5", + "content" : "bea54cf408b022894c0b1b013c58c0a9" + }, + { + "alg" : "SHA-1", + "content" : "ecda50de20ab6d3c49ea30df4c1982048f5d31ac" + }, + { + "alg" : "SHA-256", + "content" : "572f1a4171dff33b5a9260bbd704473442adf24f890386abe33ecc18c047836a" + }, + { + "alg" : "SHA-512", + "content" : "c611e0d07093d99dbcded7a00e7c00355a7c13c24a69d33105ca88ec63cc68ba76339b5a96b84f2b666bb883849980776e1e24ee2df9c7dd07b2dde0992289b5" + }, + { + "alg" : "SHA-384", + "content" : "ed40ffb527cf8442dbe3eb7b542970317e4827ed00196387d78f123490a77b08b3bc2fd5f53b83f6bee1d4eed29215bf" + }, + { + "alg" : "SHA3-384", + "content" : "26d5852f479f1c72f501569a8ea0c0e4c93f9049676921dca94b467e68f221214e4485c41647e6a92005e5090a6a7c80" + }, + { + "alg" : "SHA3-256", + "content" : "dc69eefb2f1441bbec58c219ccedd895b863b1e1d25cc3805936f0c9b072f2e6" + }, + { + "alg" : "SHA3-512", + "content" : "bf6fce60937e78550fb3d411c19aad2200d8129138fade809e9d0abc307c7f06b54732f1e94fa86ebb82d4da0293f7bce43345416b3fdae1b3c2edbac6706310" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-starter-json@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-json@3.2.1?type=jar" + }, + { + "group" : "com.fasterxml.jackson.datatype", + "name" : "jackson-datatype-jsr310", + "version" : "2.15.3", + "description" : "Add-on module to support JSR-310 (Java 8 Date & Time API) data types.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "acd8ae6da000eb831a69b4acdc182b7f" + }, + { + "alg" : "SHA-1", + "content" : "4a20a0e104931bfa72f24ef358c2eb63f1ef2aaf" + }, + { + "alg" : "SHA-256", + "content" : "bea1d78009ebc4e5d54918a3f7aec5da9fbd09f662c191a217ffcf37e8527c5e" + }, + { + "alg" : "SHA-512", + "content" : "1c5bde6c91a2a89f3c1f231f4e17c435063d9012babbfcba509a3b25363b1fd99f0dcd4234f1e00559e43d3dc8e6c71834282c72f2ebf15484ae900754c5d757" + }, + { + "alg" : "SHA-384", + "content" : "cc72f54d89bc0f7ffae9af36dfba38e5a61ac83db2f0d8de3c74e405a0bfd77b6d463217ece19c64eeb16291d80a69f5" + }, + { + "alg" : "SHA3-384", + "content" : "096944bac7583e5c97e8afcfbc928ca4a87a7d3e5eb74cc77394a19ca8bc6f26185da7fdf5d6bd2179582bf51940edc5" + }, + { + "alg" : "SHA3-256", + "content" : "0301cf719fd327643b3228b91c36688aaea3fccf3487c3e09bae3de636340dc7" + }, + { + "alg" : "SHA3-512", + "content" : "b9a4a8c9785e8ec2786690bfede18c76e08d81fc9c77bb2dad88e1a034f97f7d20020531ac1cb9b0b6e61645b08ea441aba35fc0732edc2fc1dc4b36d6f1695c" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310@2.15.3?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310@2.15.3?type=jar" + }, + { + "group" : "org.hdrhistogram", + "name" : "HdrHistogram", + "version" : "2.1.12", + "description" : "HdrHistogram supports the recording and analyzing sampled data value counts across a configurable integer value range with configurable value precision within the range. Value precision is expressed as the number of significant digits in the value recording, and provides control over value quantization behavior across the value range and the subsequent value resolution at any given level.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "4b1acf3448b750cb485da7e37384fcd8" + }, + { + "alg" : "SHA-1", + "content" : "6eb7552156e0d517ae80cc2247be1427c8d90452" + }, + { + "alg" : "SHA-256", + "content" : "9b47fbae444feaac4b7e04f0ea294569e4bc282bc69d8c2ce2ac3f23577281e2" + }, + { + "alg" : "SHA-512", + "content" : "b03b7270eb7962c88324858f94313adb3a53876f1e11568a78a5b7e00a9419e4d7ab8774747427bff6974b971b6dfc47a127fca11cb30eaf7d83b716e09b1a0d" + }, + { + "alg" : "SHA-384", + "content" : "06977d680dafd803d32441994474e598384a584411a67c95ab4a64698c9e4cbd613e0119b54685cea275b507a0a6f362" + }, + { + "alg" : "SHA3-384", + "content" : "b5ccb4d39bf7cc8ccc33f0f8fcbab0a63c99a94feda840b5d80fc3ae061127f1475cfb869b060933783a1f2eafb103a1" + }, + { + "alg" : "SHA3-256", + "content" : "ef2113f27862af1d24d90c2028fc566902720248468d3c0f2f1807cc86918882" + }, + { + "alg" : "SHA3-512", + "content" : "4fca2f75bdfd3f2ac40dc227ae2ef0272142802b1546d4f5edf9155eaeed84eff07b0c3a978291a1df096ec94724b0defb045365e6a51acfdd5da68d72c5a8eb" + } + ], + "licenses" : [ + { + "license" : { + "id" : "CC0-1.0" + } + }, + { + "license" : { + "id" : "BSD-2-Clause", + "url" : "https://opensource.org/licenses/BSD-2-Clause" + } + } + ], + "purl" : "pkg:maven/org.hdrhistogram/HdrHistogram@2.1.12?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "issue-tracker", + "url" : "https://github.com/HdrHistogram/HdrHistogram/issues" + }, + { + "type" : "vcs", + "url" : "scm:git:git://github.com/HdrHistogram/HdrHistogram.git" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.hdrhistogram/HdrHistogram@2.1.12?type=jar" + }, + { + "group" : "io.micrometer", + "name" : "micrometer-commons", + "version" : "1.12.1", + "description" : "Module containing common code", + "hashes" : [ + { + "alg" : "MD5", + "content" : "2518ae277e56aea5e37e3fc2f578dfa4" + }, + { + "alg" : "SHA-1", + "content" : "abcc6b294e60582afdfae6c559c94ad1d412ce2d" + }, + { + "alg" : "SHA-256", + "content" : "295785b04cd4de7711bb16730da5e9829bac55a8879d52120625dac6c89904ed" + }, + { + "alg" : "SHA-512", + "content" : "25d65699a25fe3b90de17a0539233fdad37df864f6d493475976e9a513bd7767520a882cbf6bbd98714a1fe94acdb77a160cd68f549475d2b93624ffe8672a00" + }, + { + "alg" : "SHA-384", + "content" : "8523ae45ce6dd4a068cce108cd31da24629839d3d293fca92353cf45db9eae88107744c9e66b82ed14abb96782c562da" + }, + { + "alg" : "SHA3-384", + "content" : "9af1fc3aad2d0131c337b843c38b05510d31e7931a48841a4bdb618257f185286ed393f8a4418ae4c5f91da7f9c76cbf" + }, + { + "alg" : "SHA3-256", + "content" : "d5dbeadc5f629430202c81a6736dff2efbfbf3ea2c09844b1194f316772a93f7" + }, + { + "alg" : "SHA3-512", + "content" : "c7b1dd1727000936bf51c02f9bf9b262a412e2b815531df4a9f7aad675ef0f728d4492327a404b37b1ef36d41a240b83dbfeea3367b3b4faa22cdc2decc5bac9" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/io.micrometer/micrometer-commons@1.12.1?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/io.micrometer/micrometer-commons@1.12.1?type=jar" + }, + { + "group" : "org.mockito", + "name" : "mockito-core", + "version" : "5.7.0", + "description" : "Mockito mock objects library core API and implementation", + "hashes" : [ + { + "alg" : "MD5", + "content" : "4df8dd230071bc192161d0e54a76f6b5" + }, + { + "alg" : "SHA-1", + "content" : "a1c258331ab91d66863c983aff7136357e9de056" + }, + { + "alg" : "SHA-256", + "content" : "dbad5e746654910a11a59ecb4d01e38461f3e5d16161689dc2588d5554432521" + }, + { + "alg" : "SHA-512", + "content" : "5a2f00df2b1b2dbca06686f88806b86990f1eea6f7c25281c0e7ec7cf7904a0a9227477279b11630d80f8e88d6b6e9dbdb40ad094a4077cc6a44cd2072d12662" + }, + { + "alg" : "SHA-384", + "content" : "3f2caa05fe4a5d5b385654ce60d0655724200fdd333652459b86848c3b895a9ad0b0daca8a014851d6b5c744cd0e9372" + }, + { + "alg" : "SHA3-384", + "content" : "06ba4583220a4aaa47d79ccab11783d48900d8850a346e4a1efc61c057630fcf0bb9c95cec74833ab5e6ee08e55625ec" + }, + { + "alg" : "SHA3-256", + "content" : "f1f9899edf629fffaf8b4483ac04430945996393f4fdcedc38eba22a9a5c715d" + }, + { + "alg" : "SHA3-512", + "content" : "d6f479d52534b382088012e3d1a83fa267dfb046322a72e84438d21973165617d1d710bb42f1cb2d2d3d7f891969320232031be33f4abb2ea1526217e16e8c63" + } + ], + "licenses" : [ + { + "license" : { + "id" : "MIT", + "url" : "https://opensource.org/licenses/MIT" + } + } + ], + "purl" : "pkg:maven/org.mockito/mockito-core@5.7.0?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "build-system", + "url" : "https://github.com/mockito/mockito/actions" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/mockito/mockito/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/mockito/mockito.git" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.mockito/mockito-core@5.7.0?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-actuator-autoconfigure", + "version" : "3.2.1", + "description" : "Spring Boot Actuator AutoConfigure", + "hashes" : [ + { + "alg" : "MD5", + "content" : "3afea56b25f872cee2c929c761b0790d" + }, + { + "alg" : "SHA-1", + "content" : "0fe81034352a15731322fba326447ba70bfa3962" + }, + { + "alg" : "SHA-256", + "content" : "3850d85c0f6074fe9286dece9b44f8bded5e194e9b816860735e0fc728173d65" + }, + { + "alg" : "SHA-512", + "content" : "7197158ef14a580edc836ab7af10a9f5f567ba60e21267b624fc4143debd2638c7b8bd8e2e5973fdd5c5d512be73df96500fb0a4273f20a21b42161e9f7add75" + }, + { + "alg" : "SHA-384", + "content" : "4a35eb1f124d8d7812d32f87b16a24dd56d4cb43278ce66f216f4a4af34db357e7481fc1b26de9bde7c2dd6847687721" + }, + { + "alg" : "SHA3-384", + "content" : "8369a8b49cae80b92abbfcc0218d55b9cecd86778735c66b9b0cc6fbc7251784725249392e716c314e3ec08c995557bb" + }, + { + "alg" : "SHA3-256", + "content" : "ee742160e4951e1f6145d575f6c6ebb908a46f38a8b3b81b7d61aac7c111a87f" + }, + { + "alg" : "SHA3-512", + "content" : "dcb1b214577203c9b3e2e5dcb3aaef8e46aec5f75a40a606f42e230c6e1af39c37250d58de6bf694c5a62d70fb1a6dcba436d696f71d7aa1a52b9f4dea5aa9a9" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-actuator-autoconfigure@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-actuator-autoconfigure@3.2.1?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-starter-tomcat", + "version" : "3.2.1", + "description" : "Starter for using Tomcat as the embedded servlet container. Default servlet container starter used by spring-boot-starter-web", + "hashes" : [ + { + "alg" : "MD5", + "content" : "db4df0f653e84bfd545894c4567b19ff" + }, + { + "alg" : "SHA-1", + "content" : "d8efc48034015522958cb3fea5831b4cbcd4fcfb" + }, + { + "alg" : "SHA-256", + "content" : "bf93da73a8fb4caf9fa68e4f3b97adcc9dbb8c79220a828b3d70ecf12d410117" + }, + { + "alg" : "SHA-512", + "content" : "d2bce5bb0271525766283e17160513de530c20e0452cecc3c9d5be3890986cc071c1423a3c11c54a36d2f83bd3a238b0fcbcc6218976a5633f0753a313418f6f" + }, + { + "alg" : "SHA-384", + "content" : "1f9ae7504b1345595377a4d35163315824dcf25f29ac9d522385e6e1672b813719655989708eb03b419e808f1f102be9" + }, + { + "alg" : "SHA3-384", + "content" : "9d890c3314b5ec30f39de30bf70471aef5f19e64d6d2f60b6fe66b3c57978bbda0a981cf92e42f18f27b72ed2ddb3574" + }, + { + "alg" : "SHA3-256", + "content" : "43d38219fbe556c2bac8670fa0aa4f89e2ac273fda77d8bceac8d9d34d7b27c2" + }, + { + "alg" : "SHA3-512", + "content" : "6a4e9a2ff89293c60c8a05cb79a65695dbe9823978be93f1b309d702338f87f108aabeaeafe8ff0ebf08bcd5483efbbb4a85c566e1357acd1d2fab565c910a80" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-starter-tomcat@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-tomcat@3.2.1?type=jar" + }, + { + "group" : "org.apache.logging.log4j", + "name" : "log4j-to-slf4j", + "version" : "2.21.1", + "description" : "The Apache Log4j binding between Log4j 2 API and SLF4J.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "00b957af4a40bea6a7bf61400b6ccf63" + }, + { + "alg" : "SHA-1", + "content" : "d77b2ba81711ed596cd797cc2b5b5bd7409d841c" + }, + { + "alg" : "SHA-256", + "content" : "de143c565ba78b0f2c0be58f132c7aec75e6e1a10845ebda5a4f17c2a35d9990" + }, + { + "alg" : "SHA-512", + "content" : "8a7a682dc5ae6a123c8de6002f1470ad2682795c65b47b06397d9ad9a31729e588c406013bfa989f9c2a51750c353cd7a147bc036f2d66b0f8f0b3f13798a637" + }, + { + "alg" : "SHA-384", + "content" : "8f3e4f1eea069f47b2c6111f1233448ea9ccc723b7c8a8bd308b7317a6ec1f47008d2952c1cb274152a38d3e21da750b" + }, + { + "alg" : "SHA3-384", + "content" : "822f93c3bba450b89a7f64b4d81aab48a7f5c2f693b53a4dcc83eba3a8300ff90c9e7727223f3491c782c80bee9dc707" + }, + { + "alg" : "SHA3-256", + "content" : "1f3f3aace32b45e9a6271c7b4ac76ddf86eb4f32e28e147a3e054dc8c836def1" + }, + { + "alg" : "SHA3-512", + "content" : "bb61c16d22aeed2d6b18972f68a6c4670fb8a07eeb79407748a7d499bc64e8ad8eb9774d372d9286227665686fe90878f2ef7e7f8595b74cd448d0f847aec02e" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0", + "url" : "https://www.apache.org/licenses/LICENSE-2.0" + } + } + ], + "purl" : "pkg:maven/org.apache.logging.log4j/log4j-to-slf4j@2.21.1?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.apache.logging.log4j/log4j-to-slf4j@2.21.1?type=jar" + }, + { + "group" : "jakarta.xml.bind", + "name" : "jakarta.xml.bind-api", + "version" : "4.0.1", + "hashes" : [ + { + "alg" : "MD5", + "content" : "e62084f1afb23eccde6645bf3a9eb06f" + }, + { + "alg" : "SHA-1", + "content" : "ca2330866cbc624c7e5ce982e121db1125d23e15" + }, + { + "alg" : "SHA-256", + "content" : "287f3b6d0600082e0b60265d7de32be403ee7d7269369c9718d9424305b89d95" + }, + { + "alg" : "SHA-512", + "content" : "dcc70e8301a7f274bbb6d6b3fe84ad8c9e5beda318699c05aeac0c42b9e1e210fc6953911be2cb1a2ef49ac5159c331608365b1b83a14a8e86f89f630830dd28" + }, + { + "alg" : "SHA-384", + "content" : "16ff377d0cfd7d8f23f45417e1e0df72de7f77780832ae78a1d2c51d77c4b2f8d270bd9ce4b73d07b70b060a9c39c56e" + }, + { + "alg" : "SHA3-384", + "content" : "773fd2d1e1a647bea7a5365490483fd56e7a49d9b731298d3202b4f93602c9a1a7add0eee868bc5a7ac961da7dda8c8e" + }, + { + "alg" : "SHA3-256", + "content" : "26214bba5cee45014859be8018dc631c14146e0a5959bb88e05d98472c88de8b" + }, + { + "alg" : "SHA3-512", + "content" : "32bdc043b7d616d73bbc26e0b36308126b15658cd032a354770760c5b5656429a4240dd3ddcea835556e813b6ae8618307ebeb96e2e46ba8ab16f6a485fa4d32" + } + ], + "licenses" : [ + { + "license" : { + "id" : "BSD-3-Clause" + } + } + ], + "purl" : "pkg:maven/jakarta.xml.bind/jakarta.xml.bind-api@4.0.1?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/jakarta.xml.bind/jakarta.xml.bind-api@4.0.1?type=jar" + }, + { + "group" : "org.yaml", + "name" : "snakeyaml", + "version" : "2.2", + "description" : "YAML 1.1 parser and emitter for Java", + "hashes" : [ + { + "alg" : "MD5", + "content" : "d78aacf5f2de5b52f1a327470efd1ad7" + }, + { + "alg" : "SHA-1", + "content" : "3af797a25458550a16bf89acc8e4ab2b7f2bfce0" + }, + { + "alg" : "SHA-256", + "content" : "1467931448a0817696ae2805b7b8b20bfb082652bf9c4efaed528930dc49389b" + }, + { + "alg" : "SHA-512", + "content" : "11547e75cc80bee26f532e2598bc6e4ffa802941496dc0d8ce017f1b15e01ebbb80e91ed17d1047916e32bf2fc58da532bc71a1dfe93afccc277a296d86634ba" + }, + { + "alg" : "SHA-384", + "content" : "dae0cb1a7ab9ccc75413f46f18ae160e12e91dfef0c17a07ea547a365e9fb422c071aa01579f2a320f15ce6ee4c29038" + }, + { + "alg" : "SHA3-384", + "content" : "654b418f330fa02f1111a20c27395ec5c7f463907ae44f60057c94da04f81e815cf1c3959f005026381ef79030049694" + }, + { + "alg" : "SHA3-256", + "content" : "2c4deb8d79876b80b210ef72dc5de2b19607e50fbe3abf09a4324576ca0881fc" + }, + { + "alg" : "SHA3-512", + "content" : "0d9be5610b2bcb6bb7562ee8bcc0d68f81d3771958ce9299c5e57e8ec952c96906d711587b7f72936328c72fb41687b4f908c4de3070b78cc1f3e257cf4b715e" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.yaml/snakeyaml@2.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "issue-tracker", + "url" : "https://bitbucket.org/snakeyaml/snakeyaml/issues" + }, + { + "type" : "vcs", + "url" : "https://bitbucket.org/snakeyaml/snakeyaml/src" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.yaml/snakeyaml@2.2?type=jar" + }, + { + "group" : "org.junit.platform", + "name" : "junit-platform-commons", + "version" : "1.10.1", + "description" : "Module \"junit-platform-commons\" of JUnit 5.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "cd430f3f7345c0888f8408ce8795c751" + }, + { + "alg" : "SHA-1", + "content" : "2bfcd4a4e38b10c671b6916d7e543c20afe25579" + }, + { + "alg" : "SHA-256", + "content" : "7d9855ee3f3f71f015eb1479559bf923783243c24fbfbd8b29bed8e8099b5672" + }, + { + "alg" : "SHA-512", + "content" : "4aa83350e7a6df21feb9ba8756bb4a68986f33f8c6e384720d1daa448444016c0def1781729788e3e884664abd6703b1e3c0ec6b79893a9d5645c3a4809c0ad2" + }, + { + "alg" : "SHA-384", + "content" : "d264f2c8ceaff384b0f22ee77890195ed3d918b01f338e35fc2ee12f82df15e59533918509f535883b4f4befed28595e" + }, + { + "alg" : "SHA3-384", + "content" : "d1fa76d6b2567e831b37ff7843df6d7d65028d4e53c570c6f580cbbf13269d2aa2afedfedfe5a3f2cf92d7de6d3c89b2" + }, + { + "alg" : "SHA3-256", + "content" : "eef0f968f2d2fc31f8b4a4ed43bafeb46977de1ac3d59477ab6e2b014f97e070" + }, + { + "alg" : "SHA3-512", + "content" : "93340cc2c378c830c755b25006bc4f73ec77ad10661f05625b23efa0854d456da8e62bdbe7e7edf3418dda864e6e0d7a6b9d34cea23d525b8991258f3d75fc9c" + } + ], + "licenses" : [ + { + "license" : { + "id" : "EPL-2.0" + } + } + ], + "purl" : "pkg:maven/org.junit.platform/junit-platform-commons@1.10.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/junit-team/junit5" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.junit.platform/junit-platform-commons@1.10.1?type=jar" + }, + { + "publisher" : "Spring IO", + "group" : "org.springframework", + "name" : "spring-web", + "version" : "6.1.2", + "description" : "Spring Web", + "hashes" : [ + { + "alg" : "MD5", + "content" : "a39761bc7a706c70f6ca3ab805a97b34" + }, + { + "alg" : "SHA-1", + "content" : "0f26b98778376cc39afb04fbb6fdd7543bef89f2" + }, + { + "alg" : "SHA-256", + "content" : "3f2012a24c6213f155b6bc69aa3ecafe2a373c1e92a26dbecc62ff575c3a1fb3" + }, + { + "alg" : "SHA-512", + "content" : "f07f054feaf53c2a97b82150882281035824cf0b815f317a22ba1954afa721bc5d57cb07faa19bad99fc235373b62edd7013f7ac2cd0a3d0db91faf49f216741" + }, + { + "alg" : "SHA-384", + "content" : "57418cf2a9b3256201c0874e7721966b09929030c64f5e5a85007bd645294dfbf1a14d4632a5aa5fcf70af5bf733d542" + }, + { + "alg" : "SHA3-384", + "content" : "83daa608abc0124ec237f65231d5f1dd1a5d751e459d3ea255a3d12a56e92ac83037fb72c5793f497fbecb9e389eb299" + }, + { + "alg" : "SHA3-256", + "content" : "1a17acdfa8920b1849a16e4260bb4b960f60da07732148a5281cfcba21d1e4a8" + }, + { + "alg" : "SHA3-512", + "content" : "3e5e020cb1068250eb0e58e9bc0368c44db96d59022047ecffe286a51b0896e4320d9696f2f9136b4c0aed547d8dd1af1bbc2b4b053aa994246bb43bd7397f05" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework/spring-web@6.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io/projects/spring-framework" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-framework/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-framework" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework/spring-web@6.1.2?type=jar" + }, + { + "group" : "org.objenesis", + "name" : "objenesis", + "version" : "3.3", + "description" : "A library for instantiating Java objects", + "hashes" : [ + { + "alg" : "MD5", + "content" : "ab0e0b2ab81affdd7f38bcc60fd85571" + }, + { + "alg" : "SHA-1", + "content" : "1049c09f1de4331e8193e579448d0916d75b7631" + }, + { + "alg" : "SHA-256", + "content" : "02dfd0b0439a5591e35b708ed2f5474eb0948f53abf74637e959b8e4ef69bfeb" + }, + { + "alg" : "SHA-512", + "content" : "1fa990d15bd179f07ffbc460d580a6fd0562e45dee8bd4a9405917536b78f45c0d6f644b67f85d781c758aa56eff90aef23eedcc9bd7f5ff887a67b716083e61" + }, + { + "alg" : "SHA-384", + "content" : "2f6878f91a12db32c244afcee619d57c3ad6ff0297f4e41c2247e737c1ccc5fcc1ce03256b479b0f9b87900410bc4502" + }, + { + "alg" : "SHA3-384", + "content" : "a3dd9f6908fe732900d50eb209988183ffcf511afb4e401ef95b75c51777709d2d10e1dc9ee386b7357c5c2cbcf8c00e" + }, + { + "alg" : "SHA3-256", + "content" : "fd2b66d174ed68cbfcda41d5cbd29db766c5676866d6b2324b446a87afab3a9f" + }, + { + "alg" : "SHA3-512", + "content" : "ef509e8bcea73bc282287205ffc7625508080be44c16948137274f189459624891dcf109118c9feff109e1aa99becf176f8db837ac4fd586201510c3ae2ea30a" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.objenesis/objenesis@3.3?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.objenesis/objenesis@3.3?type=jar" + }, + { + "group" : "com.vaadin.external.google", + "name" : "android-json", + "version" : "0.0.20131108.vaadin1", + "description" : "  JSON (JavaScript Object Notation) is a lightweight data-interchange format. This is the org.json compatible Android implementation extracted from the Android SDK  ", + "hashes" : [ + { + "alg" : "MD5", + "content" : "10612241a9cc269501a7a2b8a984b949" + }, + { + "alg" : "SHA-1", + "content" : "fa26d351fe62a6a17f5cda1287c1c6110dec413f" + }, + { + "alg" : "SHA-256", + "content" : "dfb7bae2f404cfe0b72b4d23944698cb716b7665171812a0a4d0f5926c0fac79" + }, + { + "alg" : "SHA-512", + "content" : "c4a06a0a3ce7bdbee702c06944265c050a4c8d2fbd21c248936e2edfdab63acea30f2cf3568d3c21a559940d939985a8b10d30aff972a3e8cbeb392c0b02da3a" + }, + { + "alg" : "SHA-384", + "content" : "60d1044b5439cdf5eb621118cb0581365ab4f023a30998b238b87854236f03d8395d45b0262fb812335ff904cb77f25f" + }, + { + "alg" : "SHA3-384", + "content" : "b80ebdbec2127279ca402ca52e50374d3ca773376258f6aa588b442822ee7362de8cca206db71b79862bde84018cf450" + }, + { + "alg" : "SHA3-256", + "content" : "6285b1ac8ec5fd339c7232affd9c08e6daf91dfa18ef8ae7855f52281d76627e" + }, + { + "alg" : "SHA3-512", + "content" : "de7ed83f73670213b4eeacfd7b3ceb7fec7d88ac877f41aeaacf43351d04b34572f2edc9a8f623af5b3fccab3dac2cc048f5c8803c1d4dcd1ff975cd6005124d" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0", + "url" : "https://www.apache.org/licenses/LICENSE-2.0" + } + } + ], + "purl" : "pkg:maven/com.vaadin.external.google/android-json@0.0.20131108.vaadin1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "distribution", + "url" : "http://oss.sonatype.org/content/repositories/vaadin-releases/" + }, + { + "type" : "vcs", + "url" : "http://developer.android.com/sdk/" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/com.vaadin.external.google/android-json@0.0.20131108.vaadin1?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-starter-logging", + "version" : "3.2.1", + "description" : "Starter for logging using Logback. Default logging starter", + "hashes" : [ + { + "alg" : "MD5", + "content" : "7ac01b9dee045285c365cf6a3d8d8451" + }, + { + "alg" : "SHA-1", + "content" : "0df8ec78dc87885298998ca3c9bd603ee7bfe5b8" + }, + { + "alg" : "SHA-256", + "content" : "0b7e411cfc44a15fc63a36cd05a73b34c3558f1b06e4f297b1919361b8a351a7" + }, + { + "alg" : "SHA-512", + "content" : "23baf0a59d56809db43101fbddb712b515012c64530362665cebe84c53bbd716218d3602024315f3250dea923138845c09d5c56dd9c7fb26a53d5e21a325e52e" + }, + { + "alg" : "SHA-384", + "content" : "f5ff55d346828eaec7b535bdd1d6096acc3819e81f6fa0a3d2396d523616e2e356d58115de8b8c49adf035216fa6ea83" + }, + { + "alg" : "SHA3-384", + "content" : "6e5bd5c09d127a2984a55bbfc296cc515e399f35ee2ca949b10639c5ef583bee58dc9eeb60f6bec1f05904f8b91b4a26" + }, + { + "alg" : "SHA3-256", + "content" : "99b21628e6efb820b4955e0e17bb54345a6974dc785b79abb7af8186a261159e" + }, + { + "alg" : "SHA3-512", + "content" : "91625907d0200fb80f025aa6ed098372955053bfb277db124d95ce2dd5049c20e9e7f2b97cffd6f247d9ae8da1bc26c004b688687056a87ccb3033d57a7c20f3" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-starter-logging@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-logging@3.2.1?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-actuator", + "version" : "3.2.1", + "description" : "Spring Boot Actuator", + "hashes" : [ + { + "alg" : "MD5", + "content" : "d5ede97972b567fe75db1d2bbfc035d8" + }, + { + "alg" : "SHA-1", + "content" : "9089b9fff0c17eae54aabc466b78e010eac3a04f" + }, + { + "alg" : "SHA-256", + "content" : "b870c0a601dc0d6d98b33a6b59d41799285848de267f7cfb466a6f167f30c4d2" + }, + { + "alg" : "SHA-512", + "content" : "9577f4ba268b688ad100d4038f6dba97139a29b82127f6a581b948f0ee08fc8159f51fa5f7deb200e5a61559fd321559d2255af75c3e28cf293e815b8b1bb8ac" + }, + { + "alg" : "SHA-384", + "content" : "96adde3cd5a4f729a6d382566800e62e89c93d1c3b9120ffefcd9a666d755fc5d6dc3dd12577f927bcaf03b7f1b0922b" + }, + { + "alg" : "SHA3-384", + "content" : "c3f71bfae2d560ec46f76e833aee6964b5ad57639cb4ded937cd6d1e39b213a4c255d9b83ba59882d22dd31a4ef7b5f5" + }, + { + "alg" : "SHA3-256", + "content" : "d7a251040e99b14a5d926f86bdcb1fcf505518d31cb421e6aaf32d59d8f7f2eb" + }, + { + "alg" : "SHA3-512", + "content" : "3b642b5433989ba548cffebd7c155d5ada680b96996eac432895de56a27d7529c795d7263e8419854c9d118cddc0492d142d260a2e5434058134c9bc17ab8253" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-actuator@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-actuator@3.2.1?type=jar" + }, + { + "group" : "ch.qos.logback", + "name" : "logback-core", + "version" : "1.4.14", + "description" : "logback-core module", + "hashes" : [ + { + "alg" : "MD5", + "content" : "7367629d307fa3d0b82d76b9d3f1d09a" + }, + { + "alg" : "SHA-1", + "content" : "4d3c2248219ac0effeb380ed4c5280a80bf395e8" + }, + { + "alg" : "SHA-256", + "content" : "f8c2f05f42530b1852739507c1792f0080167850ed8f396444c6913d6617a293" + }, + { + "alg" : "SHA-512", + "content" : "d18159d4b378973e49182c4711b3d5b1f3600674ddd7bde26793247854bbd3a7233df7f74c356ecc86e4160ac6f866e0b32c109df6e1b428a10cddd4bc7f44e8" + }, + { + "alg" : "SHA-384", + "content" : "afe21cf21e8804d069514a1f0d57c92b4caf56f8b010bd681d19fff67f237fcf0bbe1e1c9bfc4cedcfe602a3ea859b57" + }, + { + "alg" : "SHA3-384", + "content" : "38cc28c8a578f4053412440d88b41938fa029a8ee3d350fe7474b34afa0f17889298d00f3c2cec4510d72d3342d29a77" + }, + { + "alg" : "SHA3-256", + "content" : "6c7d3be575969be97a49e90a97a8dc1bb25380b1b302073e00d2e21cb266e6a6" + }, + { + "alg" : "SHA3-512", + "content" : "8e9ce45d599bffac71e35a0d59c4dcff067f628157a75e9e28c1930f31537fb1dd058ddd9906322c1154f29436252a36bc50595578bfee9bcad4a9705c85726a" + } + ], + "licenses" : [ + { + "license" : { + "id" : "EPL-1.0" + } + }, + { + "license" : { + "name" : "GNU Lesser General Public License", + "url" : "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" + } + } + ], + "purl" : "pkg:maven/ch.qos.logback/logback-core@1.4.14?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/ch.qos.logback/logback-core@1.4.14?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-test", + "version" : "3.2.1", + "description" : "Spring Boot Test", + "hashes" : [ + { + "alg" : "MD5", + "content" : "5c793b3b61ba2637840a6c865aa2901e" + }, + { + "alg" : "SHA-1", + "content" : "142fbe3cfe3370c57d0ed55cca0d8d96e1d6f26e" + }, + { + "alg" : "SHA-256", + "content" : "0fb27aeb59ab757e60c48f9810d0ab54dc858a4c1cd9cc75b4ad07456c9c3e7c" + }, + { + "alg" : "SHA-512", + "content" : "975428c3f753ec1375f9c0ca2c47756a22896cc510193b53f7a8501255634a2e0d2165e699055667f4127cbaa8e79c9c128aef6de0854fccd4e158dce4422939" + }, + { + "alg" : "SHA-384", + "content" : "c3abb4c4a9961cab0fde6119d5b86755ea0c43fdd266b51d369a8544818463ce1876df2b13b0a2478f36b1e5282a305d" + }, + { + "alg" : "SHA3-384", + "content" : "641f9090f373f299d61bf54dd06e7ea15217c5b06424e970ddaed1f64e2a25aae74bdc10e04c9c4e934f2a3a5ab95c4b" + }, + { + "alg" : "SHA3-256", + "content" : "45d05dd704757c997b11f13961762e371309bec11292b32af3f244ca3b49642c" + }, + { + "alg" : "SHA3-512", + "content" : "53001dd1610347d6cf92f737067271fe3c638828a0b1e0b6aca62429e97a85018daf6ab3e10f065acd79ed7c93dc3a4c57f89eda3e2feb48ab548ca7e906b414" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-test@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-test@3.2.1?type=jar" + }, + { + "group" : "io.micrometer", + "name" : "micrometer-jakarta9", + "version" : "1.12.1", + "description" : "Module for Jakarta 9+ based instrumentations", + "hashes" : [ + { + "alg" : "MD5", + "content" : "0e247019d91d3c357b440436e1af2fba" + }, + { + "alg" : "SHA-1", + "content" : "2dc7257970669fa45e342b0b36902d868af2dbed" + }, + { + "alg" : "SHA-256", + "content" : "e8c66d7aee8fbc8a9d2e15c6c53df92bd7ecbf94f1ca8562d62d9a2693aa4633" + }, + { + "alg" : "SHA-512", + "content" : "3a481de081b216d42bd9b741b3a830c93d917c5ae8a11f670785b53b55cff601e1cdfd037b12d8b95cd8557c4493d6e04e51980860e421f444f2b4a953070969" + }, + { + "alg" : "SHA-384", + "content" : "cdbca1958c2502bcdad18446401f7f21ec2bc2c4055fd2fafa8fdad30cb8c8fd9aa9863de5ddd9cb852cafda487d29b0" + }, + { + "alg" : "SHA3-384", + "content" : "13f29eca056350277ee80d786945386abdd1c8b7c04dc35a94c7ac8146e7b6cafa617652fca15e79b8376341ae5576d0" + }, + { + "alg" : "SHA3-256", + "content" : "f095b2247aa3ada3c824121b4720dcceb3b65f7a2b9e880acdedc613a62d9be6" + }, + { + "alg" : "SHA3-512", + "content" : "773cd6f711b68a27d958ecb01f85d8480835014d23d3484e69e1c63bc736f50697bd6cf7d5e7776a13ae946ed10621334cb84ba8357b26d45cb6c9990826f993" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/io.micrometer/micrometer-jakarta9@1.12.1?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/io.micrometer/micrometer-jakarta9@1.12.1?type=jar" + }, + { + "group" : "com.fasterxml.jackson.module", + "name" : "jackson-module-parameter-names", + "version" : "2.15.3", + "description" : "Add-on module for Jackson (http://jackson.codehaus.org) to support introspection of method/constructor parameter names, without having to add explicit property name annotation.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "495868f770056602bfe13ea781656f03" + }, + { + "alg" : "SHA-1", + "content" : "8d251b90c5358677e7d8161e0c2488e6f84f49da" + }, + { + "alg" : "SHA-256", + "content" : "baf1a3156a23cb407e05374161a07ed8560f78a7ae249955de04a9a2fa2d0f2b" + }, + { + "alg" : "SHA-512", + "content" : "497b08f55f601b7ff6294e0b8307e015e60ad45c7949bd80ed3f5ee19daa93fad7f0b5a93abb8082ec46480667ab8539337633213d0fd5992e4a10c710f0a7aa" + }, + { + "alg" : "SHA-384", + "content" : "1a50ca6c0e0b4e3ecf83e3f327670a3b36f2b847b46ab5e193e9bccc36fee3bd41c1aa937dda88c4936339eafc73fc93" + }, + { + "alg" : "SHA3-384", + "content" : "30d05f1dd78a796ba4abb79be93dae2d7e4e5269de18d85a9d89b1c92f6ff8fe09ac1953a48a0b2b51906bbaadb56fca" + }, + { + "alg" : "SHA3-256", + "content" : "9e50d137efbe3de957a64fa4b90532cbb67efc2b09ba11824362315d1f57b812" + }, + { + "alg" : "SHA3-512", + "content" : "9418c5c18e429e201d7f6a4d5f05a52a433dbe4bf72a82e3ea69010c1d4b9ec99fc651804f2f8339a53841f88416318e3ab7fb1a07391cde5ea745ebbfcf98bc" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/com.fasterxml.jackson.module/jackson-module-parameter-names@2.15.3?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/com.fasterxml.jackson.module/jackson-module-parameter-names@2.15.3?type=jar" + }, + { + "group" : "org.junit.platform", + "name" : "junit-platform-engine", + "version" : "1.10.1", + "description" : "Module \"junit-platform-engine\" of JUnit 5.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "4d571057589cd109f3f4bedf7bbf5e7a" + }, + { + "alg" : "SHA-1", + "content" : "f32ae4af74fde68414b8a3d2b7cf1fb43824a83a" + }, + { + "alg" : "SHA-256", + "content" : "baa48e470d6dee7369a0a8820c51da89c1463279eda6e13a304d11f45922c760" + }, + { + "alg" : "SHA-512", + "content" : "52ea2f11ec2ef0457384335d1b09263f4efecf63d9df99c5f8396f74d972722c51f8f766370e85e030f4476e805dac72603296942593c5bbe56993454b9d8e30" + }, + { + "alg" : "SHA-384", + "content" : "7c520e04c995a47c19c94fdcbbcba9bb117696191e6a989a82d9f960e0e315e5cf87d28022ac5cb2701c85d5f38eefde" + }, + { + "alg" : "SHA3-384", + "content" : "79d4f2fb987d6a44174dda99b1bd827e8dfd0399495c3e994371d4f69631212768dee8b891313aac89045388a1bed9db" + }, + { + "alg" : "SHA3-256", + "content" : "5c3fcec688368188688cb6949c1230c2822211e53f3a65b7b3abf4a38051798b" + }, + { + "alg" : "SHA3-512", + "content" : "30a0834e88bbc62287e5f49302c4a07b6da1bf4d9774faddbe7e606fb296c0dcd71c7e90ef8fff3e18dd050e5a19f7b903c91674ff4806cdb97111e4f0cfc199" + } + ], + "licenses" : [ + { + "license" : { + "id" : "EPL-2.0" + } + } + ], + "purl" : "pkg:maven/org.junit.platform/junit-platform-engine@1.10.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/junit-team/junit5" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.junit.platform/junit-platform-engine@1.10.1?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-autoconfigure", + "version" : "3.2.1", + "description" : "Spring Boot AutoConfigure", + "hashes" : [ + { + "alg" : "MD5", + "content" : "29fb14fe1d383588e87a73da4508604d" + }, + { + "alg" : "SHA-1", + "content" : "b100d2d21d45dddd740d496357ca6f3813d777d0" + }, + { + "alg" : "SHA-256", + "content" : "371f0f36d226a8db972c37c73f0a0896ee4d3e77c29b54dbce8a64af731a6e53" + }, + { + "alg" : "SHA-512", + "content" : "42bc3a99f9c9ffc9fd08447303a946fce1c81e3a869a5788c7d3b669536455eedc8009428ae4660d66b0d74ab170968b6aad905455b53342d7c521e7ec4c262f" + }, + { + "alg" : "SHA-384", + "content" : "f47603c4009bb767f9d5cb0bf3fcba69167daab53cbfafd217450977464073e8b814c76aa545b1eccee587201fe93eef" + }, + { + "alg" : "SHA3-384", + "content" : "bbd77376c9a46de290522662f327a8e6b0221a6c0105632e73b527799bec8a162d98948d0d05b32509650b4f47a6465e" + }, + { + "alg" : "SHA3-256", + "content" : "9e9549dda419ad6f482e3b376c595c69ccb93cebf365c1b18a59bf226c3264db" + }, + { + "alg" : "SHA3-512", + "content" : "1473f0de013447eb40d0b6d2a30013d2a7d262ce1e0259d4a27f88e421e5538234a46704f88b27c227aab7ae2261995a73f4075a6a43124e39c7234c6d164fe2" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.2.1?type=jar" + }, + { + "group" : "org.junit.jupiter", + "name" : "junit-jupiter-engine", + "version" : "5.10.1", + "description" : "Module \"junit-jupiter-engine\" of JUnit 5.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "71d86cd027062c4da0796c2493ae94fe" + }, + { + "alg" : "SHA-1", + "content" : "6c9ff773f9aa842b91d1f2fe4658973252ce2428" + }, + { + "alg" : "SHA-256", + "content" : "02930dfe495f93fe70b26550ace3a28f7e1b900c84426c2e4626ce020c7282d6" + }, + { + "alg" : "SHA-512", + "content" : "1fcc9406d1e0301e27538757c9649545d784e83743a8800932971881cfd78a14a264ad13c0b92fad9ae1be50963c540427a19cb2d1fee06888ef48105aad4c8b" + }, + { + "alg" : "SHA-384", + "content" : "6657ac1bb11d7a40bbcb020add01e57edbbc521645116908d857074d9ea319eab3e7b7f2e9fa1ff8df08b5db3774f4dc" + }, + { + "alg" : "SHA3-384", + "content" : "607313914c11274c577b0aaaae6c68aa6ecf25d8302f55d4e334aa6b58df2e543d2399785e2019a56b85aac7716c9623" + }, + { + "alg" : "SHA3-256", + "content" : "be3560971111d3f548bef24aa6660ec2a126fd17b3bd68b7deeb1ab48735a9d1" + }, + { + "alg" : "SHA3-512", + "content" : "4ba6cb70f8fc1918dcedc874340488909c48e0f976d1834ec433f4b5c6cff55b16a996a0443a1b68a0d0ad84a37bf51386633905628728bde08b5820ee67dfaa" + } + ], + "licenses" : [ + { + "license" : { + "id" : "EPL-2.0" + } + } + ], + "purl" : "pkg:maven/org.junit.jupiter/junit-jupiter-engine@5.10.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/junit-team/junit5" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.junit.jupiter/junit-jupiter-engine@5.10.1?type=jar" + }, + { + "group" : "io.micrometer", + "name" : "micrometer-observation", + "version" : "1.12.1", + "description" : "Module containing Observation related code", + "hashes" : [ + { + "alg" : "MD5", + "content" : "b55c9caac5c8f778996937c3f6cf4101" + }, + { + "alg" : "SHA-1", + "content" : "fbd0e0e9b6a36effd53e0eee35b050ed1f548ae5" + }, + { + "alg" : "SHA-256", + "content" : "48f6607b248e8b77ee9f7b3934f70124471daf947b30480c1b9c0e9d9f996c83" + }, + { + "alg" : "SHA-512", + "content" : "3e12e101b161715e5c30eb166578de7ae76749a2c4d22435bc57395be14d1313073d5fa76dcc883ed807d4982d343addfa24540e283cd0432f1428ff00962d98" + }, + { + "alg" : "SHA-384", + "content" : "791f99b503d7fa16733a74d92ebd02e72dfce4d648245f149f5363019beabe7e317e7ef0df0bcb67832dbab03943ff53" + }, + { + "alg" : "SHA3-384", + "content" : "ccb83eb15cd8004295bdb40b948cb9d3efaa4281b0d02a97b49970a2699822d7cd15b83206c236c3a41e49063caa5ded" + }, + { + "alg" : "SHA3-256", + "content" : "773e3647329d707d79efcb92c88cbe0719b4dcd820f06983e6e283e666875acc" + }, + { + "alg" : "SHA3-512", + "content" : "922f6c81c3a7b8e8c1296eb3359723161e91bac646d4bef954904c70a40ccfd9dc95c783715fcedc788f67ef06ea5514a918c7cc6811f2bdd39eb011a36698e7" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/io.micrometer/micrometer-observation@1.12.1?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/io.micrometer/micrometer-observation@1.12.1?type=jar" + }, + { + "group" : "org.awaitility", + "name" : "awaitility", + "version" : "4.2.0", + "description" : "A Java DSL for synchronizing asynchronous operations", + "hashes" : [ + { + "alg" : "MD5", + "content" : "8f3644827b9e3037de42068c57006260" + }, + { + "alg" : "SHA-1", + "content" : "2c39784846001a9cffd6c6b89c78de62c0d80fb8" + }, + { + "alg" : "SHA-256", + "content" : "2d23b79211fdd19036f6940cc783543779320aaf86f38d6e385a2ff26da41272" + }, + { + "alg" : "SHA-512", + "content" : "4c422b4aef3dfceb040898f45cd1b2efb7bbf213ef9487334a0d0e674e494e120fef61348f8a81ce726f2f66dc426e133917de20c52b5d39d792e2dca7bc82d8" + }, + { + "alg" : "SHA-384", + "content" : "11d15d6efb32707cae528eefb8fa4ab7820649ed528c3447660efd984518ee2906421af5ee76ea8181c904d594e8e719" + }, + { + "alg" : "SHA3-384", + "content" : "71eff4441379fb1d13bec42264d48dd1ed4817c7a226a4ef1e5255e5afcc8e5e61aa92677ae98fdce2bf4824b4dbe4fc" + }, + { + "alg" : "SHA3-256", + "content" : "4fc8b38b34625336be520d2be1edcab4c8dd8e0667fecb2aa6aea83b9bad7f28" + }, + { + "alg" : "SHA3-512", + "content" : "074f8629ab499c28155e505513e0a25c83ce722747d196966eac6327de604853503ca5f54b84effe8e2e3ab78d9ce285bdba82bf738ff8bff0f1009549230521" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.awaitility/awaitility@4.2.0?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.awaitility/awaitility@4.2.0?type=jar" + }, + { + "group" : "org.hamcrest", + "name" : "hamcrest", + "version" : "2.2", + "description" : "Core API and libraries of hamcrest matcher framework.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "10b47e837f271d0662f28780e60388e8" + }, + { + "alg" : "SHA-1", + "content" : "1820c0968dba3a11a1b30669bb1f01978a91dedc" + }, + { + "alg" : "SHA-256", + "content" : "5e62846a89f05cd78cd9c1a553f340d002458380c320455dd1f8fc5497a8a1c1" + }, + { + "alg" : "SHA-512", + "content" : "6b1141329b83224f69f074cb913dbff6921d6b8693ede8d2599acb626481255dae63de42eb123cbd5f59a261ac32faae012be64e8e90406ae9215543fbca5546" + }, + { + "alg" : "SHA-384", + "content" : "89bdcfdb28da13eaa09a40f5e3fd5667c3cf789cf43e237b8581d1cd814fee392ada66a79cbe77295950e996f485f887" + }, + { + "alg" : "SHA3-384", + "content" : "0d011b75ed22fe456ff683b420875636c4c05b3b837d8819f3f38fd33ec52b3ce2f854acfb7bebffc6659046af8fa204" + }, + { + "alg" : "SHA3-256", + "content" : "92d05019d2aec2c45f0464df5bf29a2e41c1af1ee3de05ec9d8ca82e0ee4f0b0" + }, + { + "alg" : "SHA3-512", + "content" : "4c5cbbe0dcaa9878e1dc6d3caa523c795a96280cb53843577164e5af458572cde0e82310cf5b52c1ea370c434d5631f02e06980d63126843d9b16e357a5f7483" + } + ], + "licenses" : [ + { + "license" : { + "id" : "BSD-3-Clause", + "url" : "https://opensource.org/licenses/BSD-3-Clause" + } + } + ], + "purl" : "pkg:maven/org.hamcrest/hamcrest@2.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/hamcrest/JavaHamcrest" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.hamcrest/hamcrest@2.2?type=jar" + }, + { + "group" : "org.junit.jupiter", + "name" : "junit-jupiter-api", + "version" : "5.10.1", + "description" : "Module \"junit-jupiter-api\" of JUnit 5.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "c6b8b04f2910f6cef6ac10846f43a92d" + }, + { + "alg" : "SHA-1", + "content" : "eb90c7d8bfaae8fdc97b225733fcb595ddd72843" + }, + { + "alg" : "SHA-256", + "content" : "60d5c398c32dc7039b99282514ad6064061d8417cf959a1f6bd2038cc907c913" + }, + { + "alg" : "SHA-512", + "content" : "b1fef44d4aa781bb119ab723c3c2a6f0d27efc4493a1fa26b603c7c7a8884c4d6274bccec6536f120d55f876f8d052aaf6cc003074c27cc704deb2c4bc08b6f0" + }, + { + "alg" : "SHA-384", + "content" : "0fd81f893be859a50766bfbf3bd74bd7d359c6d481b7fe3099e220402f585d3d46b6ad42a36b1d88eefbb6fd27a3cefa" + }, + { + "alg" : "SHA3-384", + "content" : "5e13ba92f757499ca52d719869d318cade9bde9c948ee9c68d753a21ec273f7b56ad68ff8cb281614efeef1d4c479db0" + }, + { + "alg" : "SHA3-256", + "content" : "997c9e0cc57d61a85a8eec568d0f014d47af5bf655602a2c3518b6530b089905" + }, + { + "alg" : "SHA3-512", + "content" : "e97c3e2c1faa1f77b174ef6ce7b24a2339e547f5976a4e40348653e84498e0c3bb96068447facef6df6b54d4af34b807f19b4d2bb1d31e26f97d6dae07843bf6" + } + ], + "licenses" : [ + { + "license" : { + "id" : "EPL-2.0" + } + } + ], + "purl" : "pkg:maven/org.junit.jupiter/junit-jupiter-api@5.10.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/junit-team/junit5" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.junit.jupiter/junit-jupiter-api@5.10.1?type=jar" + }, + { + "group" : "org.skyscreamer", + "name" : "jsonassert", + "version" : "1.5.1", + "description" : "A library to develop RESTful but flexible APIs", + "hashes" : [ + { + "alg" : "MD5", + "content" : "60a7d3d352b233487d735f4b86802717" + }, + { + "alg" : "SHA-1", + "content" : "6d842d0faf4cf6725c509a5e5347d319ee0431c3" + }, + { + "alg" : "SHA-256", + "content" : "1e9a7c443d0dd579906646d767f3701918a78cb88a93112f528305fc9095d261" + }, + { + "alg" : "SHA-512", + "content" : "51221bbeb30ed47840494d31128e605e29a96249f3e4b9c00985a865f8ed58b73e045772e3b0af74a35018a9dd004b5cc2182344b9154d9a50604ad1a073f2dd" + }, + { + "alg" : "SHA-384", + "content" : "941cec8d4ce1fab19f32b36f0afd2c7de27325659c5f85ab90948182098de4afe327b49cea57b946f18671af8037aefd" + }, + { + "alg" : "SHA3-384", + "content" : "3fb46460472c82901ec6fa5deab84eea18369e74aad920e3ee9e0fb8a859e8397a287428d0bf1c2b137368b6579c5c4b" + }, + { + "alg" : "SHA3-256", + "content" : "24b6c0f73ee51c19d5fdae62588dff9d0bf172da7e6ad1595e275920c8de829c" + }, + { + "alg" : "SHA3-512", + "content" : "686fb7b0ee0849bc78b6eeb74a941795252cec9a62ea153e6bd1e77d51fb6ee14f64970cb52cc13f581d21b166c6f1b28b8fbc4c7ae0c3b225df385a92635f0c" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.skyscreamer/jsonassert@1.5.1?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.skyscreamer/jsonassert@1.5.1?type=jar" + }, + { + "group" : "org.mockito", + "name" : "mockito-junit-jupiter", + "version" : "5.7.0", + "description" : "Mockito JUnit 5 support", + "hashes" : [ + { + "alg" : "MD5", + "content" : "ab44b412aa650651eedf323e945fe367" + }, + { + "alg" : "SHA-1", + "content" : "ac2d6a3431747a7986b8f4abef465f72bf3a21ae" + }, + { + "alg" : "SHA-256", + "content" : "e2416a260c3a45ba77d674cfe27d49428e57efe21a7b2ddeae733ebb5c5d85bf" + }, + { + "alg" : "SHA-512", + "content" : "39cccb119c0767f4e443567873af78d882c4a1e99c553ad39d4efae2698933de602d9c0046a70a05be552793569d4b43e75c2a798fd1f7f0a8c5ab34db8b9c94" + }, + { + "alg" : "SHA-384", + "content" : "f02eeae7fe867ff8580164b4d20d269efbad2a18ba2ffc8ba9744c603c589fb5155399361b14ab2a6549d605d26a4694" + }, + { + "alg" : "SHA3-384", + "content" : "6b95b5f5efcc97a2531c9c108e53fe5465ae0249d46988fe7fd47df7ad4d154de40a66471a996ae7abd75bd0c1f6c9b4" + }, + { + "alg" : "SHA3-256", + "content" : "30978340a8749b094a5b0f42dffbb91e72f7d7eaea6924efce13f47a44048fdf" + }, + { + "alg" : "SHA3-512", + "content" : "80601cb4de8850a0255b7c28cb7993be667a238d961fd281c7152b7ba40eec399240a2ab9d686cd1463872652876e88ef221d699acb61a2acf041c9f187053ab" + } + ], + "licenses" : [ + { + "license" : { + "id" : "MIT", + "url" : "https://opensource.org/licenses/MIT" + } + } + ], + "purl" : "pkg:maven/org.mockito/mockito-junit-jupiter@5.7.0?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "build-system", + "url" : "https://github.com/mockito/mockito/actions" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/mockito/mockito/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/mockito/mockito.git" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.mockito/mockito-junit-jupiter@5.7.0?type=jar" + }, + { + "group" : "org.apache.logging.log4j", + "name" : "log4j-api", + "version" : "2.21.1", + "description" : "The Apache Log4j API", + "hashes" : [ + { + "alg" : "MD5", + "content" : "b5e9bf76dd128b37666ecd9a252b50ec" + }, + { + "alg" : "SHA-1", + "content" : "74c65e87b9ce1694a01524e192d7be989ba70486" + }, + { + "alg" : "SHA-256", + "content" : "1db48e180881bef1deb502022006a025a248d8f6a26186789b0c7ce487c602d6" + }, + { + "alg" : "SHA-512", + "content" : "4cbf72fbea7009ec2fc363aae2ccfe11ea2023967d65be39335eedd1d8917b7402eeb2219efd5a1f11d03833dd1f57eecab428616b03124ef2266c6cca06ac56" + }, + { + "alg" : "SHA-384", + "content" : "edd8429f2f88476afbfa63314f7846d1341a4cfc58d3abe55b3cda236613feb6859f711e0ae60bd7821b74e488fb0666" + }, + { + "alg" : "SHA3-384", + "content" : "b67292ff0c7ca988a4b40b6ec14582ef579990d275a37944ac9572ecdfd4bf6e9fff2ab982b21d159a1135c21a32495f" + }, + { + "alg" : "SHA3-256", + "content" : "b2641c2db75d3c676e451a53b5f60dfaf030a84e0230747bd50d00414f8a27b3" + }, + { + "alg" : "SHA3-512", + "content" : "f1f4d9c48a9d088460e1ad3d71126b243069e522588cdc5534ac8f201ec0574287e8f1fba182f8925ee75b78726269487cc0160f7f8bd1aa21cc8e587fdb5c4a" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0", + "url" : "https://www.apache.org/licenses/LICENSE-2.0" + } + } + ], + "purl" : "pkg:maven/org.apache.logging.log4j/log4j-api@2.21.1?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.apache.logging.log4j/log4j-api@2.21.1?type=jar" + }, + { + "group" : "org.assertj", + "name" : "assertj-core", + "version" : "3.24.2", + "description" : "Rich and fluent assertions for testing in Java", + "hashes" : [ + { + "alg" : "MD5", + "content" : "b596a91049e6ce526bc5595c1bebea2c" + }, + { + "alg" : "SHA-1", + "content" : "ebbf338e33f893139459ce5df023115971c2786f" + }, + { + "alg" : "SHA-256", + "content" : "df3d0b348f1fe806bdddcb10fa4ae63c6679e9888d4bc7055f09848517976aa3" + }, + { + "alg" : "SHA-512", + "content" : "d8e3159effc7954258f2398e26c34eab6c243675408c7b5fcd7ed04a7b7dc06006514510ad15be9e7725f724cbf6e5c534cb22cbfb7c0aed71b81d4ed5755220" + }, + { + "alg" : "SHA-384", + "content" : "4f06196b5329e215282476d8e3aa5065092924bccb91da4eb0aa2e8fcd2509f249369654f0c17b59c38f11b878a305e3" + }, + { + "alg" : "SHA3-384", + "content" : "3029ae58aef975843e9205f130dcdd8f8e7da5ff1bfad62b7d918ffe52b74a3c34a859af13393abe122124a9132f3feb" + }, + { + "alg" : "SHA3-256", + "content" : "2db6965251a03be26f5baa83792a002444b4de34aaaefb0e6cf3cccf0a20939e" + }, + { + "alg" : "SHA3-512", + "content" : "fa3ffb87bc40c3f881fb477d41c8565cbc1ce46ead2030442674bb86a425c722b75fce5bb3c22425b21cc3122ac46e0f28b2eaba2bcf5d5ddcb31f47d967b890" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.assertj/assertj-core@3.24.2?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.assertj/assertj-core@3.24.2?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-starter-web", + "version" : "3.2.1", + "description" : "Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container", + "hashes" : [ + { + "alg" : "MD5", + "content" : "8a6aea9e1fbdbabbd00e35038739200f" + }, + { + "alg" : "SHA-1", + "content" : "e27e36d4222fd4d589e634e1c7f5f09f0316147c" + }, + { + "alg" : "SHA-256", + "content" : "2f14d3a4a0ae3ad634bcfa07117542001c1789c0bdce3504baee8f2bc45ef006" + }, + { + "alg" : "SHA-512", + "content" : "2fcfc8d9abfcd0518b6755737c6e520544600b3c26b42b60d1ab3fcfceb31582d5dbcd5d86a98ec312442d335e49f0db0ecf21d8e99089ef41d962ece42d97ae" + }, + { + "alg" : "SHA-384", + "content" : "e3c8cb02b18ea5b7aa2a7c9c97c62385fcaa8fc53f41d7bf0b98d262a10473e9674924ad287964f6e58fb9c5915da8d1" + }, + { + "alg" : "SHA3-384", + "content" : "713c9200480f14fd4bcd073d43ac7900771c9d36b4e72b50ddf80733670948ad57700ea37336de5078d16557e426de79" + }, + { + "alg" : "SHA3-256", + "content" : "3346906c7b4b455c00226fd9804a237d3a667523800e0c2083413fde4592b7c3" + }, + { + "alg" : "SHA3-512", + "content" : "99ba750d8e1c97636eb47122ce259b1bc9b91c51fecc50d13604f7ae7096a20f1fa38562d83786c1d4c3ba07ff94b286d869d671a5f0d00fd6c378f032332f63" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-starter-web@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-web@3.2.1?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-starter-test", + "version" : "3.2.1", + "description" : "Starter for testing Spring Boot applications with libraries including JUnit Jupiter, Hamcrest and Mockito", + "hashes" : [ + { + "alg" : "MD5", + "content" : "f808bed72032367a1170477e74e57f7e" + }, + { + "alg" : "SHA-1", + "content" : "e6a20062864e3a9a0bba0ac3b0c5a819453045b9" + }, + { + "alg" : "SHA-256", + "content" : "2e0a11d69fed912dd6f5a6b0f492ce1530e2ac932de9588d4b7df0ab548eea0a" + }, + { + "alg" : "SHA-512", + "content" : "83c1f7e7b404be7b9f603a386ca2d0c84c7e0b73190ffb19ef2b0dff5cbc1ebd57ce73be663ee01ed28f1c4f41d91db7f070d7b37a3f2ae6b9b6814dd930a089" + }, + { + "alg" : "SHA-384", + "content" : "3a5159cad10587b250f0a1f7cf6ebea9f2cbda539c008094fec1dff47eeced5b2119be3ad007eab0598445b9282164f4" + }, + { + "alg" : "SHA3-384", + "content" : "9303b808eed6e0425d5c7e968601960d9ff2e0c2fd840ffd041b01f0499b1f86ae05c50e968e925374a54b26e9298410" + }, + { + "alg" : "SHA3-256", + "content" : "a18f18bd0a077a38ea0b3aeae85730b9f104d65d4d48f88210f2954c45739eae" + }, + { + "alg" : "SHA3-512", + "content" : "e021bfc51b8d6b8cdc1b44cf5042778c208db09b349250e33630b28ace2ed97d52bd89750ab70e14b650578f379a7e6172838c83bbb2c974394132cb80381f27" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-starter-test@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-test@3.2.1?type=jar" + }, + { + "group" : "jakarta.activation", + "name" : "jakarta.activation-api", + "version" : "2.1.2", + "description" : "${project.name} ${spec.version} Specification", + "hashes" : [ + { + "alg" : "MD5", + "content" : "1af11450fafc7ee26c633d940286bc16" + }, + { + "alg" : "SHA-1", + "content" : "640c0d5aff45dbff1e1a1bc09673ff3a02b1ba12" + }, + { + "alg" : "SHA-256", + "content" : "f53f578dd0eb4170c195a4e215c59a38abfb4123dcb95dd902fef92876499fbb" + }, + { + "alg" : "SHA-512", + "content" : "383283f469aba01a274591e29f1aa398fefa273bca180162d9d11c87509ffb55cb2dde51783bd6cae6f2c4347e0ac7358cf11f4c85787d5d2857354b9e29d877" + }, + { + "alg" : "SHA-384", + "content" : "e34ac294c104cb67ac06f7fc60752e54a881c04f68271b758899739a5df5be2d2d0e707face2705b95fa5a26cedf9313" + }, + { + "alg" : "SHA3-384", + "content" : "ffd74b0335a4bfdd9a0c733c77ecdfa967d5280500c7d2f01e2be8499d39a9f0cd29c9063ae634223347bb00f4e60c33" + }, + { + "alg" : "SHA3-256", + "content" : "c97236eaebb15b8aefa034b23834eaeed848dacf119746c6d87832c47581e74d" + }, + { + "alg" : "SHA3-512", + "content" : "147dfa2bf46bb47c81462c36ac6612f9f807169ffb785e2bbd45538205c5713f33af4373f3324a2063350c2367baff37e9c2cf085c38c96870ad88c60a7fbea4" + } + ], + "licenses" : [ + { + "license" : { + "id" : "BSD-3-Clause" + } + } + ], + "purl" : "pkg:maven/jakarta.activation/jakarta.activation-api@2.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "issue-tracker", + "url" : "https://github.com/jakartaee/jaf-api/issues/" + }, + { + "type" : "vcs", + "url" : "https://github.com/jakartaee/jaf-api" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/jakarta.activation/jakarta.activation-api@2.1.2?type=jar" + }, + { + "group" : "io.micrometer", + "name" : "micrometer-core", + "version" : "1.12.1", + "description" : "Core module of Micrometer containing instrumentation API and implementation", + "hashes" : [ + { + "alg" : "MD5", + "content" : "30dcc7ea6a0e99663e5908bce7371206" + }, + { + "alg" : "SHA-1", + "content" : "b72e9a2f26355ecb8ababa0148a5c3c4ac648f14" + }, + { + "alg" : "SHA-256", + "content" : "97d0a5309e9c584f4dec6f549a383ae25d8727abff43cff8e0b90580ee797b67" + }, + { + "alg" : "SHA-512", + "content" : "2acd080a1b40cb5a1ca0b7266af829392e318291dab57e6239ca97d15112cc206992b78316f4c02400454124519a084341e4de55dd729c96805b3fb196707a64" + }, + { + "alg" : "SHA-384", + "content" : "9a3998a9a219fc049ace5731fde94944948332eccbe589dbc34456057a2df173ef17e3b0642233e513d3118bcfba565f" + }, + { + "alg" : "SHA3-384", + "content" : "22c97b3fb49d299ebc36674a6e32d9fd05726d88109ede3323e3e97e82100d1ed6d7010e86749a2b07ffe994fb3b7833" + }, + { + "alg" : "SHA3-256", + "content" : "3b272686c89e274b5944715db002871e072f0f8c7099228f6d6909656b6ba3f4" + }, + { + "alg" : "SHA3-512", + "content" : "b1d82086950a2e61ed3e016fa962af2e9c3b2d543c4c311d40d9f7fc402b9beb3e5d09261d336cb1634b186f723bf584874f3fb8a29c38198d5ddd2b386c4413" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/io.micrometer/micrometer-core@1.12.1?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/io.micrometer/micrometer-core@1.12.1?type=jar" + }, + { + "group" : "org.junit.jupiter", + "name" : "junit-jupiter-params", + "version" : "5.10.1", + "description" : "Module \"junit-jupiter-params\" of JUnit 5.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "5e8e17f6f2a5dedb42d9846a3352dd31" + }, + { + "alg" : "SHA-1", + "content" : "c8f15d4e99940c4564098af78c10809c00fdca06" + }, + { + "alg" : "SHA-256", + "content" : "c8cf62debcbb354deefe1ffd0671eff785514907567d22a615ff8a8de4522b21" + }, + { + "alg" : "SHA-512", + "content" : "dbd8a3bca0a03b6eef54de2b489685c8125e0c6f23cbdb633174b21e07cc7b97a24b55dcb5b60ec1a496683a918bfdf1ea0459950689e3755aa965ea9e106ee9" + }, + { + "alg" : "SHA-384", + "content" : "882b3106163d7c195867e08db9948a0997e1469a23c847bff523efa30a9b274c0588f8228fca98c78abf9b61709a7ff2" + }, + { + "alg" : "SHA3-384", + "content" : "6e4e9a7dbb32cc3f16f21a14fe036aa13488c5b94e3cb6cc53b417c4588b90b5ae118caa3eb9f4bc9c513d06e2c1f408" + }, + { + "alg" : "SHA3-256", + "content" : "171a08027b527e3be1ad66082405eacf4a55746dd983c46d9ff7ee5552276615" + }, + { + "alg" : "SHA3-512", + "content" : "c435b4a17208b67f6fa35ebe74872c3d2c3557b290437bb682ac86701402bbe17d0e53446c674bb94c7feaae4bbfa99d888c7bf7181707e27fe08ff7934c00f6" + } + ], + "licenses" : [ + { + "license" : { + "id" : "EPL-2.0" + } + } + ], + "purl" : "pkg:maven/org.junit.jupiter/junit-jupiter-params@5.10.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/junit-team/junit5" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.junit.jupiter/junit-jupiter-params@5.10.1?type=jar" + }, + { + "group" : "com.fasterxml.jackson.core", + "name" : "jackson-databind", + "version" : "2.15.3", + "description" : "General data-binding functionality for Jackson: works on core streaming API", + "hashes" : [ + { + "alg" : "MD5", + "content" : "5f453c55f127690fa8491ce347aa055c" + }, + { + "alg" : "SHA-1", + "content" : "a734bc2c47a9453c4efa772461a3aeb273c010d9" + }, + { + "alg" : "SHA-256", + "content" : "c3c53333a2172a80678bda1803e39cff45bec6ae3e9c7d4f44a81ec4e2ab18dc" + }, + { + "alg" : "SHA-512", + "content" : "490ccc99a9c28238fe28455bae08196b83df034cae8a1947d27ff89e500a5d812cf4be36c61942e647c62ad540d8eb4428f49855f0cc8db0ee9e7a5b12ba2454" + }, + { + "alg" : "SHA-384", + "content" : "b53f4a6fddbf677a8d02c65e9f0a96372140c68286d68740987fb462f946de878abaeea421d3e4716751f04d88c16ad1" + }, + { + "alg" : "SHA3-384", + "content" : "5a407605544e303abf8a212651bf5e5594fa313804a399bf03401f449c0baf26ef965def518b05c275b2f38f18457739" + }, + { + "alg" : "SHA3-256", + "content" : "d0880002ac261d181e663499627fcce5763f3a9120bb76e758adfb9939d17c98" + }, + { + "alg" : "SHA3-512", + "content" : "e97bfe0e9117dad82e0799cb2c105c4553c6aa5ce9abdefee4fd5b584876555309aafa9a19ca586e928e292e32f23452849a10da7364966e11e4f7afcc6aec78" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.3?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/FasterXML/jackson-databind" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.3?type=jar" + }, + { + "group" : "org.slf4j", + "name" : "jul-to-slf4j", + "version" : "2.0.9", + "description" : "JUL to SLF4J bridge", + "hashes" : [ + { + "alg" : "MD5", + "content" : "24f86e89ee3f71ea91f644150c507740" + }, + { + "alg" : "SHA-1", + "content" : "09ef7c70b248185845f013f49a33ff9ca65b7975" + }, + { + "alg" : "SHA-256", + "content" : "69b4e5f8d3bd3f6f54367d19f2c1ee95dd5877802f12d868282e218dd76b00bf" + }, + { + "alg" : "SHA-512", + "content" : "c1cdfbc0c867917d65ab58e039b01c5b119368aef82abcb406d91646da208a4bfad91831a5a425eacfa8253ccd5713a9d4325d45665288483929cce7a6a56eb7" + }, + { + "alg" : "SHA-384", + "content" : "a8d45375ec27c0833a441f28055ba2c07b601fb7a9bc54945672fc2f7b957d8ada5d574ab607ef3f9a279c32c0a7b0a5" + }, + { + "alg" : "SHA3-384", + "content" : "d65edaa8f6ad8bbea84617e414ede438ec4aafffa3734f2d38e6dd0a01c1f42f9397acaf6291a73489fb252d7369c71e" + }, + { + "alg" : "SHA3-256", + "content" : "69416188261a8af7cb686a6d68a809f4e7cab668f6b12d4456ce8fd9df7a1c25" + }, + { + "alg" : "SHA3-512", + "content" : "52d54c80e3934913a184efc091978201934b0ee47a6b4f9c8555a4d549becd26957e17592aff46dfdcfcbcb2313bfad09699ee84cfd7112ed2a00422c87399e8" + } + ], + "licenses" : [ + { + "license" : { + "id" : "MIT", + "url" : "https://opensource.org/licenses/MIT" + } + } + ], + "purl" : "pkg:maven/org.slf4j/jul-to-slf4j@2.0.9?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.slf4j/jul-to-slf4j@2.0.9?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot", + "version" : "3.2.1", + "description" : "Spring Boot", + "hashes" : [ + { + "alg" : "MD5", + "content" : "6f7384977eae04c804b1062df9217959" + }, + { + "alg" : "SHA-1", + "content" : "faa2ce019bee68a8d17529d0a08ebc427f927e13" + }, + { + "alg" : "SHA-256", + "content" : "6fde604399114e77b12519b3d117117c607cb73b89a88800856fb0e0cc82ea7a" + }, + { + "alg" : "SHA-512", + "content" : "8619959d143ef38f5c846591b8b10b0c50906a3301a5e9ed3e3df44124bdfbe3197cd4ecfb214c3250f40a0c1b11138b7a3f6865755445879f0685d2e88a6846" + }, + { + "alg" : "SHA-384", + "content" : "e237fdf6fdb8d21f2fc19fc15a370901c368266ae8d2b157f41b5eeed50b211a871fabc352dda10bb3aec60975d233f5" + }, + { + "alg" : "SHA3-384", + "content" : "cd6240fc102daf1efcd9fdd6532ce21297d5477e9bde3f5651cc9ec9505d526f63ea2284e484c2aee2a8e63841137839" + }, + { + "alg" : "SHA3-256", + "content" : "3959b52aebe7405a95f82d8990b8122cf21b89967f691dad851b85191973f9cb" + }, + { + "alg" : "SHA3-512", + "content" : "1b4ef33997158ddb97ccbcec7011cd55f0e019428d25410b01a83ca58c9420f2f8805be955cf704605145abe582522db0c8afb9698ae4efac141a3807a457ae5" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot@3.2.1?type=jar" + }, + { + "group" : "org.latencyutils", + "name" : "LatencyUtils", + "version" : "2.0.3", + "description" : "LatencyUtils is a package that provides latency recording and reporting utilities.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "2ad12e1ef7614cecfb0483fa9ac6da73" + }, + { + "alg" : "SHA-1", + "content" : "769c0b82cb2421c8256300e907298a9410a2a3d3" + }, + { + "alg" : "SHA-256", + "content" : "a32a9ffa06b2f4e01c5360f8f9df7bc5d9454a5d373cd8f361347fa5a57165ec" + }, + { + "alg" : "SHA-512", + "content" : "bb81a42498c65389366205f4e07cee336920e2f05cc0daae213f2784b1d0ce9a908b038daec20478f23eb00b2bf704f96c5b00f63c99615193ab2a3cc4a9f890" + }, + { + "alg" : "SHA-384", + "content" : "16ca4640dc9d848e6c6d15441897e1b5a9f27f34207b0bb456dd54d8f267b73b348092e548e78634144de44ba3515205" + }, + { + "alg" : "SHA3-384", + "content" : "406c2b5c6f64b0c090568e479b5e6136a04a4e77f8eea65d32b4e2b01deebcdf6a0a851240cdb740c25b5a5e61e6c179" + }, + { + "alg" : "SHA3-256", + "content" : "50ae828358301033542fd7c412e86ee318d5451f89a182e2a679aaf18099d26d" + }, + { + "alg" : "SHA3-512", + "content" : "456c337b9fb385579aae707409ed6a04d08e5fc87b1a46733dca617c22c625bf253dc4747e0cdbf5e7d8b48102d2938cb482b6b688a79aab645a7459c592258f" + } + ], + "licenses" : [ + { + "license" : { + "id" : "CC0-1.0" + } + } + ], + "purl" : "pkg:maven/org.latencyutils/LatencyUtils@2.0.3?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "issue-tracker", + "url" : "https://github.com/LatencyUtils/LatencyUtils/issues" + }, + { + "type" : "vcs", + "url" : "scm:git:git://github.com/LatencyUtils/LatencyUtils.git" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.latencyutils/LatencyUtils@2.0.3?type=jar" + }, + { + "group" : "org.apache.tomcat.embed", + "name" : "tomcat-embed-el", + "version" : "10.1.17", + "description" : "Core Tomcat implementation", + "hashes" : [ + { + "alg" : "MD5", + "content" : "f9171a84574782d1d68acd8b07177172" + }, + { + "alg" : "SHA-1", + "content" : "9ad7312421535d7d3aabe0f541e852baccb59726" + }, + { + "alg" : "SHA-256", + "content" : "bac12b9c993a9181ffc88ea8ba085491a482729e64ae105750a7475a7b85e549" + }, + { + "alg" : "SHA-512", + "content" : "77cf7be4536d7f1f4761fec33562134150c0ebc74d582160ff913c8be37b1502ed63e90bce81bc8617cfcd76c774903c2dca4209a972146f4c976f786456c596" + }, + { + "alg" : "SHA-384", + "content" : "62b14b49de8ee6efb41831ff172114af56a18379a797de732915ac356bce3e5582764253852c9831a3c3b6c1e52dea65" + }, + { + "alg" : "SHA3-384", + "content" : "05cb21cbf8b221332d7ad588cc6aa2087c60e8ce92c5ff2bddcd16465ef2a0198f74d4595dc3313d1acc68ea945c8672" + }, + { + "alg" : "SHA3-256", + "content" : "c18e9b240138c21a23b0bf2f502d1d667084c5a50d7b3340a4a08799a3175de9" + }, + { + "alg" : "SHA3-512", + "content" : "663d02ece35a989d8da1cdbdea002974f0115ae8c727dd71f0505f299c63f04c0e83b718e4c3e65412bea1c79d872e9ca7d9431c7deb63a312d3191d419620ab" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.apache.tomcat.embed/tomcat-embed-el@10.1.17?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.apache.tomcat.embed/tomcat-embed-el@10.1.17?type=jar" + }, + { + "publisher" : "Spring IO", + "group" : "org.springframework", + "name" : "spring-context", + "version" : "6.1.2", + "description" : "Spring Context", + "hashes" : [ + { + "alg" : "MD5", + "content" : "ca23d3013c2afc6d3b30b993f3c5cd69" + }, + { + "alg" : "SHA-1", + "content" : "15df19852991220556b4462a366269b8e15278eb" + }, + { + "alg" : "SHA-256", + "content" : "af22a435469956415bbee873de6c05995ef12f2d29622abf510a94581ea52de2" + }, + { + "alg" : "SHA-512", + "content" : "eca3cb14e8c0fb65d27bc21a8041aab3baea14f278fb546356fcec9874d0dcd10353fe697e94ebc35a78abb3387d5a41b67c1cbc9341eb05359c1b535147a9c9" + }, + { + "alg" : "SHA-384", + "content" : "374207d989f7f27ded5468f35867d0aace78927cdaf98c31b2b6345210fbbe960ae5e5143bb0308347b7ef386159fa04" + }, + { + "alg" : "SHA3-384", + "content" : "236c1d366734b231ef4a334da4220b311dd58b1707ae854b2a50ff89b6b348913458fecdab14d196128b695de6dc9832" + }, + { + "alg" : "SHA3-256", + "content" : "e1e1e87df37dbc064315d7afaa59480c830a0f445ed0df2ff5968931f96e9e86" + }, + { + "alg" : "SHA3-512", + "content" : "a600b2720ed8e5c6ecbb2a68b6a5fb5320811818e2128016b9888df705901a8d0f38dfa99b8d458724a85e769b4da2ce14d461133e085f8aab23f59e9e520c11" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework/spring-context@6.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io/projects/spring-framework" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-framework/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-framework" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework/spring-context@6.1.2?type=jar" + }, + { + "group" : "org.opentest4j", + "name" : "opentest4j", + "version" : "1.3.0", + "description" : "Open Test Alliance for the JVM", + "hashes" : [ + { + "alg" : "MD5", + "content" : "03c404f727531f3fd3b4c73997899327" + }, + { + "alg" : "SHA-1", + "content" : "152ea56b3a72f655d4fd677fc0ef2596c3dd5e6e" + }, + { + "alg" : "SHA-256", + "content" : "48e2df636cab6563ced64dcdff8abb2355627cb236ef0bf37598682ddf742f1b" + }, + { + "alg" : "SHA-512", + "content" : "78fc698a7871bb50305e3657893c10500595f043348d875f57bc39ca4a6a51eda3967b7c8c8a7ec3e8f85f2171bca4aa98823e912e416e87e81c6ba5b70a37c3" + }, + { + "alg" : "SHA-384", + "content" : "10398b6998c9202a0731e2e19ae1c3f9d8a83582c2663fe7bdda15794ee6fa816727dbd8f7c7164bd5395ee1cfe7c97e" + }, + { + "alg" : "SHA3-384", + "content" : "3abe706fd78509c25a402c7bbf6f9ddf71ffb5b35054864ba0fdf7902207115f888a0ba728fd71d2e87a9360d2498121" + }, + { + "alg" : "SHA3-256", + "content" : "d961907a1bfa1dcda329dca494ffbc251b31fabcaca5ab7095661a8ce3c1d654" + }, + { + "alg" : "SHA3-512", + "content" : "0ad661617bcac51bcd26f7ad4611c69b1fd9811b50dbf734e041a3243ab1f845e7796620e8a7c40c4a2df3946864598b1251396c7d9bd813203d82710788cce0" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.opentest4j/opentest4j@1.3.0?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/ota4j-team/opentest4j" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.opentest4j/opentest4j@1.3.0?type=jar" + }, + { + "publisher" : "Spring IO", + "group" : "org.springframework", + "name" : "spring-core", + "version" : "6.1.2", + "description" : "Spring Core", + "hashes" : [ + { + "alg" : "MD5", + "content" : "98bedebd5de314d344ed3a7dcad01c66" + }, + { + "alg" : "SHA-1", + "content" : "e43c71a9eaca454654621f7d272f15b53c68d583" + }, + { + "alg" : "SHA-256", + "content" : "8e3f7378e98c26500bdb5ecd6865778f57a22787eb2f11b9bd5fb8e438a0c631" + }, + { + "alg" : "SHA-512", + "content" : "9654f2d77899116d66dbf5808815c866da0bc7a965532da059c7819bde3928e8d3692f0dc97e06f94c44e5452b785b50eb364a1cb7e46385653ba0e2c7195306" + }, + { + "alg" : "SHA-384", + "content" : "3b63b4a26c5706ef2e379ff7bce89df983e7ae449a927905ce23ecf26e22bbcf8e91dc53cc75f4f7cd72bc09d7e7bb20" + }, + { + "alg" : "SHA3-384", + "content" : "ca29e88f0764a6a9279fc93d5cb9284a04c6ccca6a8a5beaa404079b90674286fc6458d14b0b0a727d31e00b8009e4f9" + }, + { + "alg" : "SHA3-256", + "content" : "861fc1147deae5a55165bd32c3fd4e18687afcc37876205c10bf1feede582ff9" + }, + { + "alg" : "SHA3-512", + "content" : "659a0d2e5ba153be219e1ebbafb28f9b48c44a2acd78d695e7479551a1c1641b7893d7df071a3cc7436de03735b0c8024b2f758bd0286711eae64ab005f6e929" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework/spring-core@6.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io/projects/spring-framework" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-framework/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-framework" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework/spring-core@6.1.2?type=jar" + }, + { + "group" : "com.jayway.jsonpath", + "name" : "json-path", + "version" : "2.8.0", + "description" : "A library to query and verify JSON", + "hashes" : [ + { + "alg" : "MD5", + "content" : "501b9f34e6a05c20dd74e6b40e066617" + }, + { + "alg" : "SHA-1", + "content" : "b4ab3b7a9e425655a0ca65487bbbd6d7ddb75160" + }, + { + "alg" : "SHA-256", + "content" : "9601707e95cd79fb98570a01ea8cfb857b5cde948744d6e0edf733c11002c95b" + }, + { + "alg" : "SHA-512", + "content" : "8d1521092a2acb13a2667774b8b81debc1f2a0e937007e27e5bd28bb222910774b64d6e269f33473f765c810c03a34e715d16065dc9a4be8d8d081436282ba7e" + }, + { + "alg" : "SHA-384", + "content" : "aeea493be7c23574a77df50a0652776b768d52e4238efd504b8ef3b142bbe6caf0dae8955b30c2173a54f70243d36a36" + }, + { + "alg" : "SHA3-384", + "content" : "c11c80614c007f350fa2fe758c0f4505e7ed7d25590622f133abc59ccffeb4e0b2abfd393b83e58dff4668307f28704f" + }, + { + "alg" : "SHA3-256", + "content" : "d7a7d1d7845dde343617ec009dd0d76e6bf012f182324e3b9d0f23c52bb7f67f" + }, + { + "alg" : "SHA3-512", + "content" : "da023255dfa2271a0b6b35b7d35980c3c502f3f63b3d515714f7dea54046f527bd6cbd903fec9492aad88ad03a1b85dc2b05fca4b34ded3c3b427c4cbfab02fe" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/com.jayway.jsonpath/json-path@2.8.0?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "scm:git:git://github.com/jayway/JsonPath.git" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/com.jayway.jsonpath/json-path@2.8.0?type=jar" + }, + { + "group" : "org.slf4j", + "name" : "slf4j-api", + "version" : "2.0.9", + "description" : "The slf4j API", + "hashes" : [ + { + "alg" : "MD5", + "content" : "45630e54b0f0ac2b3c80462515ad8fda" + }, + { + "alg" : "SHA-1", + "content" : "7cf2726fdcfbc8610f9a71fb3ed639871f315340" + }, + { + "alg" : "SHA-256", + "content" : "0818930dc8d7debb403204611691da58e49d42c50b6ffcfdce02dadb7c3c2b6c" + }, + { + "alg" : "SHA-512", + "content" : "069e6ddce79617e37d61758120c7e68348ee62f255781948937f7bec3058e46244026d7f6a11e90fbc15cd4288c4bb1acee4f242af521c721a9e68a05e64d526" + }, + { + "alg" : "SHA-384", + "content" : "fd6f7ad85d02ac63cd1a586c8bb158c1fc000495f512f097731ea9f749b5da2637615b821294962805ba312c738f40aa" + }, + { + "alg" : "SHA3-384", + "content" : "17cd61f59a162250b52a89c7c56eb60da253b776210500313c7b82744483ff84717946f969251fb4d76f9bb12a2458fe" + }, + { + "alg" : "SHA3-256", + "content" : "9dcb04582c64c79e788f9191195834ec75bb3457133d22a176a0ccb069b97103" + }, + { + "alg" : "SHA3-512", + "content" : "990faffa454598a3fa82affe30f1323db769d2e1fff20d9c7163ef6fd95ac7a0874c06a634207a2eaed9e5afbdee68b225138fc75018717ba97efe3ffe92c88a" + } + ], + "licenses" : [ + { + "license" : { + "id" : "MIT", + "url" : "https://opensource.org/licenses/MIT" + } + } + ], + "purl" : "pkg:maven/org.slf4j/slf4j-api@2.0.9?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.slf4j/slf4j-api@2.0.9?type=jar" + }, + { + "group" : "ch.qos.logback", + "name" : "logback-classic", + "version" : "1.4.14", + "description" : "logback-classic module", + "hashes" : [ + { + "alg" : "MD5", + "content" : "204b49a7fa041b2b2c455193079dc1d2" + }, + { + "alg" : "SHA-1", + "content" : "d98bc162275134cdf1518774da4a2a17ef6fb94d" + }, + { + "alg" : "SHA-256", + "content" : "8e832f7263ca606ae36dabb2d8b24c2f43d82cf634e81dad9d1640fa6ee3c596" + }, + { + "alg" : "SHA-512", + "content" : "77b535f2cf5a2fdb807017cb6fe456c40dcb11491e743ff86f99df2714a1b12bb9182ac193d37c8a6dd7eb2bf4c7d24390a6d551d02a280083673516eecdabc4" + }, + { + "alg" : "SHA-384", + "content" : "606400251082b8193a57bb20f1774ee2d6e439fab2ddb0207643fe9cee66cf61edba5e5c80d4b3bc9785a7bab910f8df" + }, + { + "alg" : "SHA3-384", + "content" : "d9d9b1412d2fea3eeb5d110a0e7d44c9bc13459fd2b2f5cbb30b95174081f0184758abe43b5e6b6197a716c3ba7b310f" + }, + { + "alg" : "SHA3-256", + "content" : "e1b0d59a9a91fd7878c92b3680cde8c34896823612a2f04715c05e977c09db82" + }, + { + "alg" : "SHA3-512", + "content" : "e0a39dacbb91b7d9f00bdf78829918079f6f2e749c28f31a359064bac9ac7eb65c87e581795946814460f787e33b8829a9cf0e933a0f87dd7d48f288d45f5064" + } + ], + "licenses" : [ + { + "license" : { + "id" : "EPL-1.0" + } + }, + { + "license" : { + "name" : "GNU Lesser General Public License", + "url" : "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" + } + } + ], + "purl" : "pkg:maven/ch.qos.logback/logback-classic@1.4.14?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/ch.qos.logback/logback-classic@1.4.14?type=jar" + }, + { + "publisher" : "Chemouni Uriel", + "group" : "net.minidev", + "name" : "accessors-smart", + "version" : "2.5.0", + "description" : "Java reflect give poor performance on getter setter an constructor calls, accessors-smart use ASM to speed up those calls.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "fc814b28882dd9f2552eda21add0698f" + }, + { + "alg" : "SHA-1", + "content" : "aca011492dfe9c26f4e0659028a4fe0970829dd8" + }, + { + "alg" : "SHA-256", + "content" : "12314fc6881d66a413fd66370787adba16e504fbf7e138690b0f3952e3fbd321" + }, + { + "alg" : "SHA-512", + "content" : "77b21fdd3401a0557d2d04a14c27563897afe9e001fc520398e22083bc18afee5e48dd9f5fc6561d0f327a30a9303bf5cc20f0a2ce741d80b3792e258276faac" + }, + { + "alg" : "SHA-384", + "content" : "7464bf3917d11712b235c7e1af339766d01cb4b41ec98941c3c69bc4ab9a4d0e6c832cbf01482425100dc8f1611ce3a0" + }, + { + "alg" : "SHA3-384", + "content" : "be26dc2bfc5fdc1a45e14f1c2fcfe224994e66d39049e235ea83c714fb90bb685d3f2209c0d550528e2cd9b2d9d95a6e" + }, + { + "alg" : "SHA3-256", + "content" : "6a914eb757ec313842f13c837eeb628e606323cc63dc24127e7a9804e2746d12" + }, + { + "alg" : "SHA3-512", + "content" : "edbddef0538aac87bf6af714e12c4078fd6ada069b6fd0e1e5c1038b060999764e06c28b3ca38b8d540d0f60c72f7321ddc22d2537156999bad5098c89b6975a" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/net.minidev/accessors-smart@2.5.0?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://urielch.github.io/" + }, + { + "type" : "distribution", + "url" : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" + }, + { + "type" : "vcs", + "url" : "https://github.com/netplex/json-smart-v2" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/net.minidev/accessors-smart@2.5.0?type=jar" + }, + { + "group" : "com.fasterxml.jackson.core", + "name" : "jackson-core", + "version" : "2.15.3", + "description" : "Core Jackson processing abstractions (aka Streaming API), implementation for JSON", + "hashes" : [ + { + "alg" : "MD5", + "content" : "c86c75392bf138d54d2a219bb1d0cbcd" + }, + { + "alg" : "SHA-1", + "content" : "60d600567c1862840397bf9ff5a92398edc5797b" + }, + { + "alg" : "SHA-256", + "content" : "51fab7aad51ed588482edc507fd542747936c5094d1ab76ed21ddb63b96b610d" + }, + { + "alg" : "SHA-512", + "content" : "112de40a31dc7d011f256f1d2fe0d9e2afc301a1f31974318f8d070c3e362b2ba96005167384244f630b915451db6694bd3cf6a9b793872351bc18f21c9de5e4" + }, + { + "alg" : "SHA-384", + "content" : "9daaf08467525e462234c53ddbf7287bcef15d8df7fbc64bcd558a91d11e8335b3a79368d194b126d3c8fb846800025b" + }, + { + "alg" : "SHA3-384", + "content" : "0b4fdc8d11fc060461e74e773fce2e64d1a98bed7db6edf51784bb1b801da4bae744a2958e81c2e24cb992fec892fb6c" + }, + { + "alg" : "SHA3-256", + "content" : "751ad4f10a78cb36fccbbe1dfe208816f17619edd5adeabc86b7509201e03c3d" + }, + { + "alg" : "SHA3-512", + "content" : "aa5807b7d92d150fada6a4ecdbfce998bbea825a09af8381127ba3736de029ae9923f54d770b2e5c3f5c85d9b4bcf21e6893a5a3089db2d02f1432b85dfa0fe7" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.3?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/FasterXML/jackson-core" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.3?type=jar" + }, + { + "group" : "org.xmlunit", + "name" : "xmlunit-core", + "version" : "2.9.1", + "description" : "XMLUnit for Java", + "hashes" : [ + { + "alg" : "MD5", + "content" : "011288450a3905a7d97e3957b69e713e" + }, + { + "alg" : "SHA-1", + "content" : "e5833662d9a1279a37da3ef6f62a1da29fcd68c4" + }, + { + "alg" : "SHA-256", + "content" : "7e70f23d4f75e05f0ee79f0f6b9e13b6cf51d34f36c5fc3a6b839429dde1efef" + }, + { + "alg" : "SHA-512", + "content" : "1d07dc1582a1930664ab3cffd1443e85c83fec138c663f3070a9d3b283f818157b2cdd1589595867281a96d3b444b18c22c1ee3249a75c857c6ee9682785e8a3" + }, + { + "alg" : "SHA-384", + "content" : "f54a506a08b66776d92d4379712ae9f7658cc89bd7b780eb629bd37143ff68e28cb2314539dc3c1ff13dc9cccba394f2" + }, + { + "alg" : "SHA3-384", + "content" : "7fd679371624f72417612491bac721a49f229744df3fc7455e5fd3983bd2de452a4eaabb707be7bac328f3beeea88d99" + }, + { + "alg" : "SHA3-256", + "content" : "c517aa9c543a4a3df361c30ba6609082a1dd5dc2abc351643ad5b733a1282773" + }, + { + "alg" : "SHA3-512", + "content" : "3797bade2087f791697f6736296381f8b158a2a93f50faeabcd96b4c9f48ad26fd78af56cc1036c449c35e624181961d54acdd7623b84c23c81c72d5d0fa57f1" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.xmlunit/xmlunit-core@2.9.1?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.xmlunit/xmlunit-core@2.9.1?type=jar" + }, + { + "publisher" : "OW2", + "group" : "org.ow2.asm", + "name" : "asm", + "version" : "9.3", + "description" : "ASM, a very small and fast Java bytecode manipulation framework", + "hashes" : [ + { + "alg" : "MD5", + "content" : "e1c3b96035117ab516ffe0de9bd696e0" + }, + { + "alg" : "SHA-1", + "content" : "8e6300ef51c1d801a7ed62d07cd221aca3a90640" + }, + { + "alg" : "SHA-256", + "content" : "1263369b59e29c943918de11d6d6152e2ec6085ce63e5710516f8c67d368e4bc" + }, + { + "alg" : "SHA-512", + "content" : "04362f50a2b66934c2635196bf8e6bd2adbe4435f312d1d97f4733c911e070f5693941a70f586928437043d01d58994325e63744e71886ae53a62c824927a4d4" + }, + { + "alg" : "SHA-384", + "content" : "304aa6673d587a68a06dd8601c6db0dc4d387f89a058b7600459522d94780e9e8d87a2778604fc41b81c43a57bf49ad6" + }, + { + "alg" : "SHA3-384", + "content" : "9744884ed03ced46ed36c68c7bb1f523678bcbb4f32ebeaa220157b8631e862d6573066dfc2092ed77dc7826ad17aef2" + }, + { + "alg" : "SHA3-256", + "content" : "2be2d22fdbafe87b7cdda0498fc4f45db8d77a720b63ec1f7ffe8351e173b77b" + }, + { + "alg" : "SHA3-512", + "content" : "a3ff403dd3eefbb7511d2360ab1ca3d1bf33b2f9d1c5738284be9d132eb6ad869f2d97e790ed0969132af30271e544d3725c02252267fe55e0339f89f3669ce1" + } + ], + "licenses" : [ + { + "license" : { + "id" : "BSD-3-Clause", + "url" : "https://opensource.org/licenses/BSD-3-Clause" + } + } + ], + "purl" : "pkg:maven/org.ow2.asm/asm@9.3?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "http://www.ow2.org/" + }, + { + "type" : "issue-tracker", + "url" : "https://gitlab.ow2.org/asm/asm/issues" + }, + { + "type" : "mailing-list", + "url" : "https://mail.ow2.org/wws/arc/asm/" + }, + { + "type" : "vcs", + "url" : "https://gitlab.ow2.org/asm/asm/" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.ow2.asm/asm@9.3?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-starter", + "version" : "3.2.1", + "description" : "Core starter, including auto-configuration support, logging and YAML", + "hashes" : [ + { + "alg" : "MD5", + "content" : "d9eb815815944bcdaeed5e63f32e5d7f" + }, + { + "alg" : "SHA-1", + "content" : "bc03d7075fb9d9d4877218db48d5dae3dd72a65d" + }, + { + "alg" : "SHA-256", + "content" : "a25f2f4172c34f46b73fff03293370c3daf231a1db2883ef8032aa471779fb8b" + }, + { + "alg" : "SHA-512", + "content" : "35cc80f9b10e81624324083a024c97e247e12f54762cfaadf40504903b0ebdc76d0226af1e4646bca445211b039913709ff48289dd57e27ecab18fd6e427d306" + }, + { + "alg" : "SHA-384", + "content" : "9acae9f3f77733a83d37641d3bd32d762225a08dcb20d61ff33a9038e8a4fe2dd39026bb08026cdb618437f68fc11382" + }, + { + "alg" : "SHA3-384", + "content" : "1e605937a46c8371423b7876d5dae4363f718f70200a1276056bd6466d03096aa580708c7abc76618a141a542df29b24" + }, + { + "alg" : "SHA3-256", + "content" : "331b3c120493fb5d9dd628beb8aa10382772a08d0a687103a2e87a4516fffde6" + }, + { + "alg" : "SHA3-512", + "content" : "9f2612fbecec4664979896868e4766b1f66aaebc914e46a07a7ef7e5ff76786e5a73ae9ca5f364d23ae41f8bea2fb44e5034014950423fdc3a438ae1dc275820" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-starter@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-starter@3.2.1?type=jar" + }, + { + "group" : "org.apache.tomcat.embed", + "name" : "tomcat-embed-core", + "version" : "10.1.17", + "description" : "Core Tomcat implementation", + "hashes" : [ + { + "alg" : "MD5", + "content" : "81d2d784780b1fe54275ab4f3d0c3830" + }, + { + "alg" : "SHA-1", + "content" : "5b9185ee002f9e194d2cb21ddcf8bc5f3d4a69da" + }, + { + "alg" : "SHA-256", + "content" : "5d70fa6ae0548f89fb4c070423ecc2db050cebf248b0d5f3f2294375a6762382" + }, + { + "alg" : "SHA-512", + "content" : "9fb1726f3a10f5e0bdd1cafcdc9532536679d04e5cdde9e54bdf18819ea2651bcaac0efddd6a8b5dbf3cfb8dfcd7ab0453f2ff3fa4e21a0f3796d4dd6d630433" + }, + { + "alg" : "SHA-384", + "content" : "e644a094c17574fc9334772913aeabd6de0be8eacb0718981dbd97ee197a21f43ff3efe2c073f8863a4ff111f4ccb303" + }, + { + "alg" : "SHA3-384", + "content" : "2e8d5d4b1e202e19529270adc7992e9d187ad34bdd62ab7633359f3394059cdade69c88dddd3879dea40487cb17702da" + }, + { + "alg" : "SHA3-256", + "content" : "25826af7f0a6fd192e83cd14481055b0c5477c325e51d17355d9ff97963380a0" + }, + { + "alg" : "SHA3-512", + "content" : "0b2513e578a484562ad47a8a1a4d1fe8253a9a276fac49ea9732877d976a2d1827037caa5a6401d5659c765317acb94127e62f99373a4efea63b44ab4a1824be" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@10.1.17?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@10.1.17?type=jar" + }, + { + "group" : "net.bytebuddy", + "name" : "byte-buddy-agent", + "version" : "1.14.10", + "description" : "The Byte Buddy agent offers convenience for attaching an agent to the local or a remote VM.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "389b6aca1ee862684592f6f041f81724" + }, + { + "alg" : "SHA-1", + "content" : "90ed94ac044ea8953b224304c762316e91fd6b31" + }, + { + "alg" : "SHA-256", + "content" : "67993a89d47ca58ff868802a4448ddd150e5fe4e5a5645ded990d7b4d557a6b9" + }, + { + "alg" : "SHA-512", + "content" : "7f1a1310b1a0f60d6ff07dee8d9b7e404e8fb9a25a5c0c186e00cafc834e5a026a7694fb65279367dabfa1789c1f16192d0ea794b7f511f0bb3414b8d519e9a5" + }, + { + "alg" : "SHA-384", + "content" : "ed1e1d594a7c2837311accf3f718cbc7c6e2034afcab13c63d72313ee1ffd18a53863f1ccd194b85b7e0ffed78bafc9c" + }, + { + "alg" : "SHA3-384", + "content" : "b3baeae67826ec4e4f71b2870220c362f153d2a126b04557302b5b8e24a58b9741bef7afa9c4e4f0fa1ea9371cbcb1df" + }, + { + "alg" : "SHA3-256", + "content" : "01ccb9e430868deef5b51124073643eaf6dd2c8c7e4d6e70b59042c9d28e3361" + }, + { + "alg" : "SHA3-512", + "content" : "b621fa443ade355b10cc45329a5e0f700942dd39e633a8f2343ece00446cd42f5c1217b041a67b3143df86397c363f8dcad226f1e70b8755126512a74f878262" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/net.bytebuddy/byte-buddy-agent@1.14.10?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/net.bytebuddy/byte-buddy-agent@1.14.10?type=jar" + }, + { + "publisher" : "Spring IO", + "group" : "org.springframework", + "name" : "spring-test", + "version" : "6.1.2", + "description" : "Spring TestContext Framework", + "hashes" : [ + { + "alg" : "MD5", + "content" : "fadfe62dd198a4acce4416acb28e2869" + }, + { + "alg" : "SHA-1", + "content" : "c393079051398e02c20d8b24e02822f365123719" + }, + { + "alg" : "SHA-256", + "content" : "2155779c3e461df55f3b093f0e6e4bda398664e3452efe599690bc9a3f1932f0" + }, + { + "alg" : "SHA-512", + "content" : "5e6e4f76edbf17a321302bf6257c09ed7893e32c50fb3cace37b2271f3c488d397c67b5315ef3019ee6d28544f52cf593e0475bf00927cd67f0c668d6b3909a3" + }, + { + "alg" : "SHA-384", + "content" : "151df7daac9a3e3e74732405bd4feb17ad9ff3e4de196e767f39da675d4480994ed8da13e3b1b27c7b4ee9ebc17feef8" + }, + { + "alg" : "SHA3-384", + "content" : "9069193468f2ae4c65c94d3950541efe37498a4e19245ddc67909181e83e14019f956baba54da0b9d2e8a262db13abd0" + }, + { + "alg" : "SHA3-256", + "content" : "8ccf71564f5ee7e6a578031c7c8530a5ddf136cc1dce483818ebd30d53c851df" + }, + { + "alg" : "SHA3-512", + "content" : "31049da217d1115b589780ffaa3ddfbf676cc58e70bd4cbc1f24c0cb2aea6b155539f8f9b3f6757f19719fed0a6102110f195b34cdd464b5e375132c25e7bb51" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework/spring-test@6.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io/projects/spring-framework" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-framework/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-framework" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework/spring-test@6.1.2?type=jar" + }, + { + "group" : "org.junit.jupiter", + "name" : "junit-jupiter", + "version" : "5.10.1", + "description" : "Module \"junit-jupiter\" of JUnit 5.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "32fd55a03f648868767c1bebedd198df" + }, + { + "alg" : "SHA-1", + "content" : "6e5c7dd668d6349cb99e52ab8321e73479a309bc" + }, + { + "alg" : "SHA-256", + "content" : "c1a386e901fae28e493185a47c8cea988fb1a37422b353a0f8b4df2e6c5d6037" + }, + { + "alg" : "SHA-512", + "content" : "c97a2f9eefa6f34441fc0c97744873040bbe49d335954edab43bab25876a33f4b3f11347459420569ef660449728aa093bbae5d42c0fa733a0b624706b57a65d" + }, + { + "alg" : "SHA-384", + "content" : "873dfccaf8366ce5b14dc0b5498205debecd90ecba20b1f1c924721764d546b5b9629dd57c486e5a5a2bc38954bf3824" + }, + { + "alg" : "SHA3-384", + "content" : "67f09e3174ae3fac6ddea13b56dcf078165e715cb18afd73d86bb980357e365cef6e62083231f09ae2accddfe62f5bcb" + }, + { + "alg" : "SHA3-256", + "content" : "1c2a60003b13025c959e7728b3f4469b67bad8649d2080c0871418fb52b1c078" + }, + { + "alg" : "SHA3-512", + "content" : "7c03cfaeabed9c57b26e083bcb0ca9a114c491216fc7e9652a39a5468579175e575ace315493610fdc7711c6557eff11933fbd28f5433c237d2277bee102c5a6" + } + ], + "licenses" : [ + { + "license" : { + "id" : "EPL-2.0" + } + } + ], + "purl" : "pkg:maven/org.junit.jupiter/junit-jupiter@5.10.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/junit-team/junit5" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.junit.jupiter/junit-jupiter@5.10.1?type=jar" + }, + { + "publisher" : "Chemouni Uriel", + "group" : "net.minidev", + "name" : "json-smart", + "version" : "2.5.0", + "description" : "JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate. It is based on a subset of the JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999. JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others. These properties make JSON an ideal data-interchange language.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "af9b7eda9c435acaf22e840991c7b10f" + }, + { + "alg" : "SHA-1", + "content" : "57a64f421b472849c40e77d2e7cce3a141b41e99" + }, + { + "alg" : "SHA-256", + "content" : "432b9e545848c4141b80717b26e367f83bf33f19250a228ce75da6e967da2bc7" + }, + { + "alg" : "SHA-512", + "content" : "56284bb3cee2bcc3684cdcc610115c7eacafdbd70aa852cb0209616b0503dfd448c5110b50e11a71b1c61a6e7ea27594ff63cc968230374555cc6f652d69d372" + }, + { + "alg" : "SHA-384", + "content" : "0fbbd6899d344c3158007f2f033165284323f1ecdfa49e17730d9d2bed8b3d77bbdc209a72a388e9e15a5bed9d9c8eef" + }, + { + "alg" : "SHA3-384", + "content" : "0f18f178117f8c640e7e1ac2ed4c2b28e331f658f40eac2f5974e891f7130b760e4f057859a537caaa046ba9c086a24a" + }, + { + "alg" : "SHA3-256", + "content" : "4c91eaa12f7c0ee08264ad95d016cfa41af08c963055b7f9076771da402e93e0" + }, + { + "alg" : "SHA3-512", + "content" : "0c5fad6395cf3fd25c04fd1e2c915351da4849475b463e017b760ef97800addb170d11f89791dd29ab867e343c35fd1f3ea7935622ba728d789c9f2e7fd1da51" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/net.minidev/json-smart@2.5.0?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://urielch.github.io/" + }, + { + "type" : "distribution", + "url" : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" + }, + { + "type" : "vcs", + "url" : "https://github.com/netplex/json-smart-v2" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/net.minidev/json-smart@2.5.0?type=jar" + }, + { + "publisher" : "Spring IO", + "group" : "org.springframework", + "name" : "spring-expression", + "version" : "6.1.2", + "description" : "Spring Expression Language (SpEL)", + "hashes" : [ + { + "alg" : "MD5", + "content" : "2f56216dc7ee08cbeafa54ccf18cad35" + }, + { + "alg" : "SHA-1", + "content" : "98786397734b27b7c8843a6b01a7fa34d40d6806" + }, + { + "alg" : "SHA-256", + "content" : "0fef5fb19f375a8632d2a117f4b3aed059b959e9693e90c3b7f57b7cad2f9e0b" + }, + { + "alg" : "SHA-512", + "content" : "a28e984d9ff1d4078d57f139ff28065ffba7f325c891c74c0774cd3ccfe50a9462cd93483c28c8ca4674b581ab723687c37c5c88e7cb080823d5629fa684e7f8" + }, + { + "alg" : "SHA-384", + "content" : "a84fb64144a67b56ce322fc9f4948a9491f6f5876d198eb57c99f38540971a0779a2949b93cc5f32662f97a83823ea87" + }, + { + "alg" : "SHA3-384", + "content" : "b099ce06de6a5543e52a2d43c97c4ed6567e82263db29849ff09cf37bf48e3e9974308698c2f272187508e242f756576" + }, + { + "alg" : "SHA3-256", + "content" : "efa3768de47e3b1ff9257f8367a528e38b3eec9c972eb7ba3dd8f60da626fb17" + }, + { + "alg" : "SHA3-512", + "content" : "95d7011482520e797a25f9d9b8db1b1bf6c24b3ddb3ca4b70fe5a1a58ed04ea870f86f8393f884dad8b893a6fc53ad8da1b21fdc01d9169564c3dc0229824b27" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework/spring-expression@6.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io/projects/spring-framework" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-framework/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-framework" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework/spring-expression@6.1.2?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-starter-actuator", + "version" : "3.2.1", + "description" : "Starter for using Spring Boot's Actuator which provides production ready features to help you monitor and manage your application", + "hashes" : [ + { + "alg" : "MD5", + "content" : "59713236dc4fc4b1562a3ea9788bde1e" + }, + { + "alg" : "SHA-1", + "content" : "ca17ff67e80a230f04d40d73321d623b769e361d" + }, + { + "alg" : "SHA-256", + "content" : "31c28021755feab49cc9310a8353382b3ca35d0adf02926b83e4c44ea4942898" + }, + { + "alg" : "SHA-512", + "content" : "ed618c7f1e3337c90919551ad4f14996bb2a78f773ba00c1e02d5a991d1c578e940d9b73f5e01045115c7b5d3f096f8de6720ba0d28992a586ef834948f17766" + }, + { + "alg" : "SHA-384", + "content" : "45956cbd019f099f96f36391c98fd23ea32698035f90f6e4e4df0d9a43dc03ef6db2954c2871da76a038511280591b43" + }, + { + "alg" : "SHA3-384", + "content" : "3a08b673deb39ab5db9561281245b76e9f57410601e5ce4040cefedb02e2a19abb45a98d2de170fbbac7b7f0b93eceb3" + }, + { + "alg" : "SHA3-256", + "content" : "12151432b32e26bab903572023ea022757a31177e4a6315d8fcd15bbbf34731c" + }, + { + "alg" : "SHA3-512", + "content" : "911f109b63d07f20de51f8a2de8799e32fdff05a52def36d408cb1da72a3bb63ff0878f850a7ad1cc9e85393f24ac58c6b8dd4068f11d9e70bc1e130974db00f" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-starter-actuator@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-actuator@3.2.1?type=jar" + }, + { + "publisher" : "Spring IO", + "group" : "org.springframework", + "name" : "spring-beans", + "version" : "6.1.2", + "description" : "Spring Beans", + "hashes" : [ + { + "alg" : "MD5", + "content" : "5ee147f2234968eeab4b469af4d3b5f1" + }, + { + "alg" : "SHA-1", + "content" : "abf52f2254975a3b1e95b2b63fb8b01d891cdc51" + }, + { + "alg" : "SHA-256", + "content" : "742baa41c1b0282ef01b3d542dc1b1de71db2578bd9ddd9a7d57fb191234b194" + }, + { + "alg" : "SHA-512", + "content" : "efd0eb5a073c899515ae144a4fcb4fc97cc53cbd4236d0e6a30df8fa8873fcd9bc509bc3fa88d1bff86a94dc3dbc5106374d0117f64ec8df9e6affe8f98aaa07" + }, + { + "alg" : "SHA-384", + "content" : "6214558d1024fa3b5545079268b0b2fbeda93768a0665d617612ddf4e42e11b770c38c05cb86e3ae558025afa67beea5" + }, + { + "alg" : "SHA3-384", + "content" : "8170ccea30165f25c533e27c0de38b590ca72f285cfc365c60e97745e78532213d6c93bdbea56f561dd180297a8c5ab4" + }, + { + "alg" : "SHA3-256", + "content" : "2761e0814e167de13ed08ce748880006407eda2fa744a347f57684c2bc9bb6fe" + }, + { + "alg" : "SHA3-512", + "content" : "ecdeb4cd558af513ed381942f35bd2d8dfa9b0db446dbc8c5326656ade960682283c71fcaae5578ca431f705f1a86041b0764bd453f30e738be65c4f0bbf37d1" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework/spring-beans@6.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io/projects/spring-framework" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-framework/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-framework" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework/spring-beans@6.1.2?type=jar" + } + ], + "dependencies" : [ + { + "ref" : "pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.15.3?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/ch.qos.logback/logback-classic@1.4.14?type=jar", + "dependsOn" : [ + "pkg:maven/ch.qos.logback/logback-core@1.4.14?type=jar", + "pkg:maven/org.slf4j/slf4j-api@2.0.9?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-test-autoconfigure@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot-test@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot@3.2.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/io.micrometer/micrometer-jakarta9@1.12.1?type=jar", + "dependsOn" : [ + "pkg:maven/io.micrometer/micrometer-core@1.12.1?type=jar", + "pkg:maven/io.micrometer/micrometer-observation@1.12.1?type=jar", + "pkg:maven/io.micrometer/micrometer-commons@1.12.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/com.fasterxml.jackson.module/jackson-module-parameter-names@2.15.3?type=jar", + "dependsOn" : [ + "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.3?type=jar", + "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.3?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-test@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot-starter@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-test-autoconfigure@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-test@3.2.1?type=jar", + "pkg:maven/com.jayway.jsonpath/json-path@2.8.0?type=jar", + "pkg:maven/jakarta.xml.bind/jakarta.xml.bind-api@4.0.1?type=jar", + "pkg:maven/net.minidev/json-smart@2.5.0?type=jar", + "pkg:maven/org.assertj/assertj-core@3.24.2?type=jar", + "pkg:maven/org.awaitility/awaitility@4.2.0?type=jar", + "pkg:maven/org.hamcrest/hamcrest@2.2?type=jar", + "pkg:maven/org.junit.jupiter/junit-jupiter@5.10.1?type=jar", + "pkg:maven/org.mockito/mockito-junit-jupiter@5.7.0?type=jar", + "pkg:maven/org.mockito/mockito-core@5.7.0?type=jar", + "pkg:maven/org.skyscreamer/jsonassert@1.5.1?type=jar", + "pkg:maven/org.springframework/spring-test@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar", + "pkg:maven/org.xmlunit/xmlunit-core@2.9.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.mockito/mockito-core@5.7.0?type=jar", + "dependsOn" : [ + "pkg:maven/net.bytebuddy/byte-buddy@1.14.10?type=jar", + "pkg:maven/net.bytebuddy/byte-buddy-agent@1.14.10?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-actuator-autoconfigure@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-actuator@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot@3.2.1?type=jar", + "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310@2.15.3?type=jar", + "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.3?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.yaml/snakeyaml@2.2?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/jakarta.activation/jakarta.activation-api@2.1.2?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-web@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot-starter-json@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-starter@3.2.1?type=jar", + "pkg:maven/org.springframework/spring-webmvc@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-web@6.1.2?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-starter-tomcat@3.2.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-test@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot@3.2.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework/spring-beans@6.1.2?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/net.minidev/accessors-smart@2.5.0?type=jar", + "dependsOn" : [ + "pkg:maven/org.ow2.asm/asm@9.3?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.xmlunit/xmlunit-core@2.9.1?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/io.micrometer/micrometer-core@1.12.1?type=jar", + "dependsOn" : [ + "pkg:maven/io.micrometer/micrometer-observation@1.12.1?type=jar", + "pkg:maven/io.micrometer/micrometer-commons@1.12.1?type=jar", + "pkg:maven/org.hdrhistogram/HdrHistogram@2.1.12?type=jar", + "pkg:maven/org.latencyutils/LatencyUtils@2.0.3?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.hdrhistogram/HdrHistogram@2.1.12?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.opentest4j/opentest4j@1.3.0?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/net.minidev/json-smart@2.5.0?type=jar", + "dependsOn" : [ + "pkg:maven/net.minidev/accessors-smart@2.5.0?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.apiguardian/apiguardian-api@1.1.2?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.junit.platform/junit-platform-commons@1.10.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.apiguardian/apiguardian-api@1.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework/spring-expression@6.1.2?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.apache.tomcat.embed/tomcat-embed-websocket@10.1.17?type=jar", + "dependsOn" : [ + "pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@10.1.17?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.apache.logging.log4j/log4j-to-slf4j@2.21.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.slf4j/slf4j-api@2.0.9?type=jar", + "pkg:maven/org.apache.logging.log4j/log4j-api@2.21.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-logging@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/ch.qos.logback/logback-classic@1.4.14?type=jar", + "pkg:maven/org.apache.logging.log4j/log4j-to-slf4j@2.21.1?type=jar", + "pkg:maven/org.slf4j/jul-to-slf4j@2.0.9?type=jar" + ] + }, + { + "ref" : "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.3?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.skyscreamer/jsonassert@1.5.1?type=jar", + "dependsOn" : [ + "pkg:maven/com.vaadin.external.google/android-json@0.0.20131108.vaadin1?type=jar" + ] + }, + { + "ref" : "pkg:maven/ch.qos.logback/logback-core@1.4.14?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jdk8@2.15.3?type=jar", + "dependsOn" : [ + "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.3?type=jar", + "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.3?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.slf4j/slf4j-api@2.0.9?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.springframework/spring-webmvc@6.1.2?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework/spring-web@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-context@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-aop@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-beans@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-expression@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@10.1.17?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.assertj/assertj-core@3.24.2?type=jar", + "dependsOn" : [ + "pkg:maven/net.bytebuddy/byte-buddy@1.14.10?type=jar" + ] + }, + { + "ref" : "pkg:maven/com.jayway.jsonpath/json-path@2.8.0?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.apache.tomcat.embed/tomcat-embed-el@10.1.17?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.springframework/spring-core@6.1.2?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework/spring-jcl@6.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/io.micrometer/micrometer-commons@1.12.1?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot@3.2.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.junit.jupiter/junit-jupiter-params@5.10.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.junit.jupiter/junit-jupiter-api@5.10.1?type=jar", + "pkg:maven/org.apiguardian/apiguardian-api@1.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.hamcrest/hamcrest@2.2?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.springframework/spring-context@6.1.2?type=jar", + "dependsOn" : [ + "pkg:maven/io.micrometer/micrometer-observation@1.12.1?type=jar", + "pkg:maven/org.springframework/spring-aop@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-beans@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-expression@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.slf4j/jul-to-slf4j@2.0.9?type=jar", + "dependsOn" : [ + "pkg:maven/org.slf4j/slf4j-api@2.0.9?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.apache.logging.log4j/log4j-api@2.21.1?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/net.bytebuddy/byte-buddy-agent@1.14.10?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.springframework/spring-web@6.1.2?type=jar", + "dependsOn" : [ + "pkg:maven/io.micrometer/micrometer-observation@1.12.1?type=jar", + "pkg:maven/org.springframework/spring-beans@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.junit.jupiter/junit-jupiter@5.10.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.junit.jupiter/junit-jupiter-params@5.10.1?type=jar", + "pkg:maven/org.junit.jupiter/junit-jupiter-api@5.10.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-tomcat@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/jakarta.annotation/jakarta.annotation-api@2.1.1?type=jar", + "pkg:maven/org.apache.tomcat.embed/tomcat-embed-websocket@10.1.17?type=jar", + "pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@10.1.17?type=jar", + "pkg:maven/org.apache.tomcat.embed/tomcat-embed-el@10.1.17?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.awaitility/awaitility@4.2.0?type=jar", + "dependsOn" : [ + "pkg:maven/org.hamcrest/hamcrest@2.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-actuator@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot@3.2.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework/spring-context@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/io.micrometer/micrometer-observation@1.12.1?type=jar", + "dependsOn" : [ + "pkg:maven/io.micrometer/micrometer-commons@1.12.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework/spring-jcl@6.1.2?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-json@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot-starter@3.2.1?type=jar", + "pkg:maven/org.springframework/spring-web@6.1.2?type=jar", + "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jdk8@2.15.3?type=jar", + "pkg:maven/com.fasterxml.jackson.module/jackson-module-parameter-names@2.15.3?type=jar", + "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310@2.15.3?type=jar", + "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.3?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.mockito/mockito-junit-jupiter@5.7.0?type=jar", + "dependsOn" : [ + "pkg:maven/org.mockito/mockito-core@5.7.0?type=jar" + ] + }, + { + "ref" : "pkg:maven/net.bytebuddy/byte-buddy@1.14.10?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.junit.jupiter/junit-jupiter-api@5.10.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.junit.platform/junit-platform-commons@1.10.1?type=jar", + "pkg:maven/org.opentest4j/opentest4j@1.3.0?type=jar", + "pkg:maven/org.apiguardian/apiguardian-api@1.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310@2.15.3?type=jar", + "dependsOn" : [ + "pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.15.3?type=jar", + "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.3?type=jar", + "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.3?type=jar" + ] + }, + { + "ref" : "pkg:maven/jakarta.xml.bind/jakarta.xml.bind-api@4.0.1?type=jar", + "dependsOn" : [ + "pkg:maven/jakarta.activation/jakarta.activation-api@2.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-starter@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-starter-logging@3.2.1?type=jar", + "pkg:maven/jakarta.annotation/jakarta.annotation-api@2.1.1?type=jar", + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar", + "pkg:maven/org.yaml/snakeyaml@2.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.latencyutils/LatencyUtils@2.0.3?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/com.vaadin.external.google/android-json@0.0.20131108.vaadin1?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.springframework/spring-test@6.1.2?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework/spring-aop@6.1.2?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework/spring-beans@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/jakarta.annotation/jakarta.annotation-api@2.1.1?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-actuator@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot-starter@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-actuator-autoconfigure@3.2.1?type=jar", + "pkg:maven/io.micrometer/micrometer-jakarta9@1.12.1?type=jar", + "pkg:maven/io.micrometer/micrometer-observation@1.12.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.3?type=jar", + "dependsOn" : [ + "pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.15.3?type=jar", + "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.3?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.example/cyclonedx@0.0.1-SNAPSHOT?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot-starter-actuator@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-starter-web@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-starter-test@3.2.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.ow2.asm/asm@9.3?type=jar", + "dependsOn" : [ ] + } + ] +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-actuator/README.adoc b/spring-boot-project/spring-boot-actuator/README.adoc index 3adcc5550bad..b20309431e69 100644 --- a/spring-boot-project/spring-boot-actuator/README.adoc +++ b/spring-boot-project/spring-boot-actuator/README.adoc @@ -7,31 +7,37 @@ gathering can be automatically applied to your application. The https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#production-ready[user guide] covers the features in more detail. + + == Enabling the Actuator + The recommended way to enable the features is to add a dependency to the `spring-boot-starter-actuator` '`Starter`'. To add the actuator to a Maven-based project, add the following '`Starter`' dependency: -[source,xml,indent=0] +[source,xml] ---- - - - org.springframework.boot - spring-boot-starter-actuator - - + + + org.springframework.boot + spring-boot-starter-actuator + + ---- For Gradle, use the following declaration: -[indent=0] +[source] ---- - dependencies { - implementation 'org.springframework.boot:spring-boot-starter-actuator' - } +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-actuator' +} ---- + + == Features + * **Endpoints** Actuator endpoints allow you to monitor and interact with your application. Spring Boot includes a number of built-in endpoints and you can also add your own. For example the `health` endpoint provides basic application health diff --git a/spring-boot-project/spring-boot-actuator/build.gradle b/spring-boot-project/spring-boot-actuator/build.gradle index c4e393059178..a5460910c480 100644 --- a/spring-boot-project/spring-boot-actuator/build.gradle +++ b/spring-boot-project/spring-boot-actuator/build.gradle @@ -3,6 +3,7 @@ plugins { id "org.springframework.boot.conventions" id "org.springframework.boot.configuration-properties" id "org.springframework.boot.optional-dependencies" + id "org.springframework.boot.docker-test" id "org.springframework.boot.deployed" } @@ -10,8 +11,19 @@ description = "Spring Boot Actuator" dependencies { api(project(":spring-boot-project:spring-boot")) + + dockerTestImplementation(project(":spring-boot-project:spring-boot-autoconfigure")) + dockerTestImplementation(project(":spring-boot-project:spring-boot-test")) + dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker")) + dockerTestImplementation("org.assertj:assertj-core") + dockerTestImplementation("org.junit.jupiter:junit-jupiter") + dockerTestImplementation("org.springframework:spring-test") + dockerTestImplementation("org.testcontainers:junit-jupiter") + dockerTestImplementation("org.testcontainers:mongodb") + dockerTestImplementation("org.testcontainers:neo4j") + dockerTestImplementation("org.testcontainers:testcontainers") - optional("com.datastax.oss:java-driver-core") { + optional("org.apache.cassandra:java-driver-core") { exclude group: "org.slf4j", module: "jcl-over-slf4j" } optional("com.fasterxml.jackson.core:jackson-databind") @@ -22,9 +34,11 @@ dependencies { optional("com.zaxxer:HikariCP") optional("io.lettuce:lettuce-core") optional("io.micrometer:micrometer-observation") - optional("io.micrometer:micrometer-core") + optional("io.micrometer:micrometer-jakarta9") optional("io.micrometer:micrometer-tracing") optional("io.micrometer:micrometer-registry-prometheus") + optional("io.micrometer:micrometer-registry-prometheus-simpleclient") + optional("io.prometheus:prometheus-metrics-exposition-formats") optional("io.prometheus:simpleclient_pushgateway") { exclude(group: "javax.xml.bind", module: "jaxb-api") } @@ -98,9 +112,6 @@ dependencies { testImplementation("org.skyscreamer:jsonassert") testImplementation("org.springframework:spring-test") testImplementation("com.squareup.okhttp3:mockwebserver") - testImplementation("org.testcontainers:junit-jupiter") - testImplementation("org.testcontainers:neo4j") - testImplementation("org.testcontainers:testcontainers") testRuntimeOnly("ch.qos.logback:logback-classic") testRuntimeOnly("io.projectreactor.netty:reactor-netty-http") diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/RedisCacheMetricsTests.java b/spring-boot-project/spring-boot-actuator/src/dockerTest/java/org/springframework/boot/actuate/metrics/cache/RedisCacheMetricsTests.java similarity index 95% rename from spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/RedisCacheMetricsTests.java rename to spring-boot-project/spring-boot-actuator/src/dockerTest/java/org/springframework/boot/actuate/metrics/cache/RedisCacheMetricsTests.java index 235b38994e30..0202312ba45a 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/RedisCacheMetricsTests.java +++ b/spring-boot-project/spring-boot-actuator/src/dockerTest/java/org/springframework/boot/actuate/metrics/cache/RedisCacheMetricsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,8 @@ import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ContextConsumer; -import org.springframework.boot.testsupport.testcontainers.RedisContainer; +import org.springframework.boot.testsupport.container.RedisContainer; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCache; @@ -49,7 +50,7 @@ class RedisCacheMetricsTests { @Container - static final RedisContainer redis = new RedisContainer(); + static final RedisContainer redis = TestImage.container(RedisContainer.class); private static final Tags TAGS = Tags.of("app", "test").and("cache", "test"); diff --git a/spring-boot-project/spring-boot-actuator/src/dockerTest/java/org/springframework/boot/actuate/mongo/MongoHealthIndicatorIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/dockerTest/java/org/springframework/boot/actuate/mongo/MongoHealthIndicatorIntegrationTests.java new file mode 100644 index 000000000000..40b2d8ad6e8d --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/dockerTest/java/org/springframework/boot/actuate/mongo/MongoHealthIndicatorIntegrationTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.mongo; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.MongoClientSettings.Builder; +import com.mongodb.ServerApi; +import com.mongodb.ServerApiVersion; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.boot.actuate.data.mongo.MongoHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.data.mongodb.core.MongoTemplate; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link MongoHealthIndicator}. + * + * @author Andy Wilkinson + */ +@Testcontainers(disabledWithoutDocker = true) +class MongoHealthIndicatorIntegrationTests { + + @Container + static MongoDBContainer mongo = TestImage.container(MongoDBContainer.class); + + @Test + void standardApi() { + Health health = mongoHealth(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + } + + @Test + void strictV1Api() { + Health health = mongoHealth(ServerApi.builder().strict(true).version(ServerApiVersion.V1).build()); + assertThat(health.getStatus()).isEqualTo(Status.UP); + } + + private Health mongoHealth() { + return mongoHealth(null); + } + + private Health mongoHealth(ServerApi serverApi) { + Builder settingsBuilder = MongoClientSettings.builder() + .applyConnectionString(new ConnectionString(mongo.getConnectionString())); + if (serverApi != null) { + settingsBuilder.serverApi(serverApi); + } + MongoClientSettings settings = settingsBuilder.build(); + MongoClient mongoClient = MongoClients.create(settings); + MongoHealthIndicator healthIndicator = new MongoHealthIndicator(new MongoTemplate(mongoClient, "db")); + return healthIndicator.getHealth(true); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/dockerTest/java/org/springframework/boot/actuate/mongo/MongoReactiveHealthIndicatorIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/dockerTest/java/org/springframework/boot/actuate/mongo/MongoReactiveHealthIndicatorIntegrationTests.java new file mode 100644 index 000000000000..7c15a4df1a42 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/dockerTest/java/org/springframework/boot/actuate/mongo/MongoReactiveHealthIndicatorIntegrationTests.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.mongo; + +import java.time.Duration; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.MongoClientSettings.Builder; +import com.mongodb.ServerApi; +import com.mongodb.ServerApiVersion; +import com.mongodb.reactivestreams.client.MongoClient; +import com.mongodb.reactivestreams.client.MongoClients; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.boot.actuate.data.mongo.MongoReactiveHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.data.mongodb.core.ReactiveMongoTemplate; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link MongoReactiveHealthIndicator}. + * + * @author Andy Wilkinson + */ +@Testcontainers(disabledWithoutDocker = true) +class MongoReactiveHealthIndicatorIntegrationTests { + + @Container + static MongoDBContainer mongo = TestImage.container(MongoDBContainer.class); + + @Test + void standardApi() { + Health health = mongoHealth(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + } + + @Test + void strictV1Api() { + Health health = mongoHealth(ServerApi.builder().strict(true).version(ServerApiVersion.V1).build()); + assertThat(health.getStatus()).isEqualTo(Status.UP); + } + + private Health mongoHealth() { + return mongoHealth(null); + } + + private Health mongoHealth(ServerApi serverApi) { + Builder settingsBuilder = MongoClientSettings.builder() + .applyConnectionString(new ConnectionString(mongo.getConnectionString())); + if (serverApi != null) { + settingsBuilder.serverApi(serverApi); + } + MongoClientSettings settings = settingsBuilder.build(); + MongoClient mongoClient = MongoClients.create(settings); + MongoReactiveHealthIndicator healthIndicator = new MongoReactiveHealthIndicator( + new ReactiveMongoTemplate(mongoClient, "db")); + return healthIndicator.getHealth(true).block(Duration.ofSeconds(30)); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jReactiveHealthIndicatorIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/dockerTest/java/org/springframework/boot/actuate/neo4j/Neo4jReactiveHealthIndicatorIntegrationTests.java similarity index 89% rename from spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jReactiveHealthIndicatorIntegrationTests.java rename to spring-boot-project/spring-boot-actuator/src/dockerTest/java/org/springframework/boot/actuate/neo4j/Neo4jReactiveHealthIndicatorIntegrationTests.java index 1220e35c8c11..6b8080697166 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jReactiveHealthIndicatorIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/dockerTest/java/org/springframework/boot/actuate/neo4j/Neo4jReactiveHealthIndicatorIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.test.context.DynamicPropertyRegistry; @@ -49,9 +49,7 @@ class Neo4jReactiveHealthIndicatorIntegrationTests { // gh-33428 @Container - private static final Neo4jContainer neo4jServer = new Neo4jContainer<>(DockerImageNames.neo4j()) - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + private static final Neo4jContainer neo4jServer = TestImage.container(Neo4jContainer.class); @DynamicPropertySource static void neo4jProperties(DynamicPropertyRegistry registry) { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/data/mongo/MongoHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/data/mongo/MongoHealthIndicator.java index 75fba5119f5c..307fa646c8c2 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/data/mongo/MongoHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/data/mongo/MongoHealthIndicator.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,7 @@ public MongoHealthIndicator(MongoTemplate mongoTemplate) { @Override protected void doHealthCheck(Health.Builder builder) throws Exception { - Document result = this.mongoTemplate.executeCommand("{ isMaster: 1 }"); + Document result = this.mongoTemplate.executeCommand("{ hello: 1 }"); builder.up().withDetail("maxWireVersion", result.getInteger("maxWireVersion")); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/data/mongo/MongoReactiveHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/data/mongo/MongoReactiveHealthIndicator.java index aaa96e93a5cf..39a3b6bb1428 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/data/mongo/MongoReactiveHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/data/mongo/MongoReactiveHealthIndicator.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,7 @@ public MongoReactiveHealthIndicator(ReactiveMongoTemplate reactiveMongoTemplate) @Override protected Mono doHealthCheck(Health.Builder builder) { - Mono buildInfo = this.reactiveMongoTemplate.executeCommand("{ isMaster: 1 }"); + Mono buildInfo = this.reactiveMongoTemplate.executeCommand("{ hello: 1 }"); return buildInfo.map((document) -> up(builder, document)); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/SanitizableData.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/SanitizableData.java index df399bae449d..eaaa15c54d03 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/SanitizableData.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/SanitizableData.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,7 +76,7 @@ public Object getValue() { } /** - * Return a new {@link SanitizableData} instance with sanatized value. + * Return a new {@link SanitizableData} instance with sanitized value. * @return a new sanitizable data instance. * @since 3.1.0 */ diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/DiscoveredOperationMethod.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/DiscoveredOperationMethod.java index 4eb94e4066d0..4cf89333a905 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/DiscoveredOperationMethod.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/DiscoveredOperationMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,11 +44,11 @@ public DiscoveredOperationMethod(Method method, OperationType operationType, Assert.notNull(annotationAttributes, "AnnotationAttributes must not be null"); List producesMediaTypes = new ArrayList<>(); producesMediaTypes.addAll(Arrays.asList(annotationAttributes.getStringArray("produces"))); - producesMediaTypes.addAll(getProducesFromProducable(annotationAttributes)); + producesMediaTypes.addAll(getProducesFromProducible(annotationAttributes)); this.producesMediaTypes = Collections.unmodifiableList(producesMediaTypes); } - private & Producible> List getProducesFromProducable( + private & Producible> List getProducesFromProducible( AnnotationAttributes annotationAttributes) { Class type = getProducesFrom(annotationAttributes); if (type == Producible.class) { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/reflect/OperationMethodParameter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/reflect/OperationMethodParameter.java index a4fa71ebe628..6b3e038859b0 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/reflect/OperationMethodParameter.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/reflect/OperationMethodParameter.java @@ -68,7 +68,10 @@ public boolean isMandatory() { if (!ObjectUtils.isEmpty(this.parameter.getAnnotationsByType(Nullable.class))) { return false; } - return (jsr305Present) ? new Jsr305().isMandatory(this.parameter) : true; + if (jsr305Present) { + return new Jsr305().isMandatory(this.parameter); + } + return true; } @Override diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointLinksResolver.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointLinksResolver.java index cba80cde53d6..69b42a5ed9ba 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointLinksResolver.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointLinksResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,7 +55,9 @@ public EndpointLinksResolver(Collection> endpoint public EndpointLinksResolver(Collection> endpoints, String basePath) { this.endpoints = endpoints; if (logger.isInfoEnabled()) { - logger.info("Exposing " + endpoints.size() + " endpoint(s) beneath base path '" + basePath + "'"); + String suffix = (endpoints.size() == 1) ? "" : "s"; + logger + .info("Exposing " + endpoints.size() + " endpoint" + suffix + " beneath base path '" + basePath + "'"); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointServlet.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointServlet.java index f7949e5fe93f..6f7b9e83ae16 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointServlet.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointServlet.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,9 @@ * @author Phillip Webb * @author Julio José Gómez Díaz * @since 2.0.0 + * @deprecated since 3.3.0 in favor of {@code @Endpoint} and {@code @WebEndpoint} */ +@Deprecated(since = "3.3.0", forRemoval = true) public final class EndpointServlet { private final Servlet servlet; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ExposableServletEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ExposableServletEndpoint.java index bdef98a341a9..7303daac6122 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ExposableServletEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ExposableServletEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,10 @@ * * @author Phillip Webb * @since 2.0.0 + * @deprecated since 3.3.0 in favor of {@code @Endpoint} and {@code @WebEndpoint} */ +@Deprecated(since = "3.3.0", forRemoval = true) +@SuppressWarnings("removal") public interface ExposableServletEndpoint extends ExposableEndpoint, PathMappedEndpoint { /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrar.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrar.java index a8c37b11d9d0..b7d0a9e44f5e 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrar.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,10 @@ * @author Phillip Webb * @author Madhura Bhave * @since 2.0.0 + * @deprecated since 3.3.0 in favor of {@code @Endpoint} and {@code @WebEndpoint} support */ +@Deprecated(since = "3.3.0", forRemoval = true) +@SuppressWarnings("removal") public class ServletEndpointRegistrar implements ServletContextInitializer { private static final Log logger = LogFactory.getLog(ServletEndpointRegistrar.class); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpoint.java index dee548086ef9..334cb816018e 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,12 +47,14 @@ * @since 2.0.0 * @see WebEndpoint * @see RestControllerEndpoint + * @deprecated since 3.3.0 in favor of {@code @Endpoint} and {@code @WebEndpoint} */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Endpoint @FilteredEndpoint(ControllerEndpointFilter.class) +@Deprecated(since = "3.3.0", forRemoval = true) public @interface ControllerEndpoint { /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscoverer.java index e8803cc26eba..a6c9cf3a0893 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,6 @@ import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; import org.springframework.boot.actuate.endpoint.web.PathMapper; -import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointDiscoverer.ControllerEndpointDiscovererRuntimeHints; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.core.annotation.MergedAnnotations; @@ -43,8 +42,11 @@ * * @author Phillip Webb * @since 2.0.0 + * @deprecated since 3.3.0 in favor of {@code @Endpoint} and {@code @WebEndpoint} support */ -@ImportRuntimeHints(ControllerEndpointDiscovererRuntimeHints.class) +@ImportRuntimeHints(ControllerEndpointDiscoverer.ControllerEndpointDiscovererRuntimeHints.class) +@Deprecated(since = "3.3.0", forRemoval = true) +@SuppressWarnings("removal") public class ControllerEndpointDiscoverer extends EndpointDiscoverer implements ControllerEndpointsSupplier { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointFilter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointFilter.java index 244865c44c2c..883f70c4093e 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointFilter.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") class ControllerEndpointFilter extends DiscovererEndpointFilter { ControllerEndpointFilter() { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredServletEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredServletEndpoint.java index f8418c08ca35..9a9e67aaf590 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredServletEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredServletEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") class DiscoveredServletEndpoint extends AbstractDiscoveredEndpoint implements ExposableServletEndpoint { private final String rootPath; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/RestControllerEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/RestControllerEndpoint.java index 0aec498c5436..26dd043b0800 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/RestControllerEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/RestControllerEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,6 +48,7 @@ * @since 2.0.0 * @see WebEndpoint * @see ControllerEndpoint + * @deprecated since 3.3.0 in favor of {@code @Endpoint} and {@code @WebEndpoint} */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @@ -55,6 +56,7 @@ @Endpoint @FilteredEndpoint(ControllerEndpointFilter.class) @ResponseBody +@Deprecated(since = "3.3.0", forRemoval = true) public @interface RestControllerEndpoint { /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpoint.java index 570dc552c4aa..5a2dc80e96c4 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,12 +40,14 @@ * * @author Phillip Webb * @since 2.0.0 + * @deprecated since 3.3.0 in favor of {@code @Endpoint} and {@code @WebEndpoint} */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Endpoint @FilteredEndpoint(ServletEndpointFilter.class) +@Deprecated(since = "3.3.0", forRemoval = true) public @interface ServletEndpoint { /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscoverer.java index 7e9746b19a86..9d7b6606d0c9 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,6 @@ import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint; import org.springframework.boot.actuate.endpoint.web.PathMapper; -import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointDiscoverer.ServletEndpointDiscovererRuntimeHints; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.core.annotation.MergedAnnotations; @@ -43,8 +42,11 @@ * * @author Phillip Webb * @since 2.0.0 + * @deprecated since 3.3.0 in favor of {@code @Endpoint} and {@code @WebEndpoint} */ -@ImportRuntimeHints(ServletEndpointDiscovererRuntimeHints.class) +@ImportRuntimeHints(ServletEndpointDiscoverer.ServletEndpointDiscovererRuntimeHints.class) +@Deprecated(since = "3.3.0", forRemoval = true) +@SuppressWarnings("removal") public class ServletEndpointDiscoverer extends EndpointDiscoverer implements ServletEndpointsSupplier { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointFilter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointFilter.java index 0eb0b22137b7..18626ae11929 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointFilter.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") class ServletEndpointFilter extends DiscovererEndpointFilter { ServletEndpointFilter() { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointsSupplier.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointsSupplier.java index ee56699e586e..a51b9d706a9d 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointsSupplier.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointsSupplier.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,8 +24,11 @@ * * @author Phillip Webb * @since 2.0.0 + * @deprecated since 3.3.0 in favor of {@code @Endpoint} and {@code @WebEndpoint} */ @FunctionalInterface +@Deprecated(since = "3.3.0", forRemoval = true) +@SuppressWarnings("removal") public interface ServletEndpointsSupplier extends EndpointsSupplier { } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java index 1358f6f0636e..348c9a654650 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -101,7 +101,8 @@ private EnvironmentDescriptor getEnvironmentDescriptor(Predicate propert propertyNamePredicate, showUnsanitized)); } }); - return new EnvironmentDescriptor(Arrays.asList(this.environment.getActiveProfiles()), propertySources); + return new EnvironmentDescriptor(Arrays.asList(this.environment.getActiveProfiles()), + Arrays.asList(this.environment.getDefaultProfiles()), propertySources); } @ReadOperation @@ -114,7 +115,7 @@ EnvironmentEntryDescriptor getEnvironmentEntryDescriptor(String propertyName, bo Map descriptors = getPropertySourceDescriptors(propertyName, showUnsanitized); PropertySummaryDescriptor summary = getPropertySummaryDescriptor(descriptors); return new EnvironmentEntryDescriptor(summary, Arrays.asList(this.environment.getActiveProfiles()), - toPropertySourceDescriptors(descriptors)); + Arrays.asList(this.environment.getDefaultProfiles()), toPropertySourceDescriptors(descriptors)); } private List toPropertySourceDescriptors( @@ -209,10 +210,14 @@ public static final class EnvironmentDescriptor implements OperationResponseBody private final List activeProfiles; + private final List defaultProfiles; + private final List propertySources; - private EnvironmentDescriptor(List activeProfiles, List propertySources) { + private EnvironmentDescriptor(List activeProfiles, List defaultProfiles, + List propertySources) { this.activeProfiles = activeProfiles; + this.defaultProfiles = defaultProfiles; this.propertySources = propertySources; } @@ -220,6 +225,10 @@ public List getActiveProfiles() { return this.activeProfiles; } + public List getDefaultProfiles() { + return this.defaultProfiles; + } + public List getPropertySources() { return this.propertySources; } @@ -236,12 +245,15 @@ public static final class EnvironmentEntryDescriptor { private final List activeProfiles; + private final List defaultProfiles; + private final List propertySources; EnvironmentEntryDescriptor(PropertySummaryDescriptor property, List activeProfiles, - List propertySources) { + List defaultProfiles, List propertySources) { this.property = property; this.activeProfiles = activeProfiles; + this.defaultProfiles = defaultProfiles; this.propertySources = propertySources; } @@ -253,6 +265,10 @@ public List getActiveProfiles() { return this.activeProfiles; } + public List getDefaultProfiles() { + return this.defaultProfiles; + } + public List getPropertySources() { return this.propertySources; } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java index 690c75356e52..b0a063414fcc 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -145,7 +145,7 @@ public static Builder up() { * @param ex the exception * @return a new {@link Builder} instance */ - public static Builder down(Exception ex) { + public static Builder down(Throwable ex) { return down().withException(ex); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicator.java deleted file mode 100644 index 4896ff6094e2..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicator.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.influx; - -import org.influxdb.InfluxDB; -import org.influxdb.dto.Pong; - -import org.springframework.boot.actuate.health.AbstractHealthIndicator; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.util.Assert; - -/** - * {@link HealthIndicator} for InfluxDB. - * - * @author Eddú Meléndez - * @since 2.0.0 - */ -public class InfluxDbHealthIndicator extends AbstractHealthIndicator { - - private final InfluxDB influxDb; - - public InfluxDbHealthIndicator(InfluxDB influxDb) { - super("InfluxDB health check failed"); - Assert.notNull(influxDb, "InfluxDB must not be null"); - this.influxDb = influxDb; - } - - @Override - protected void doHealthCheck(Health.Builder builder) { - Pong pong = this.influxDb.ping(); - builder.up().withDetail("version", pong.getVersion()); - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/influx/package-info.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/influx/package-info.java deleted file mode 100644 index a67ac0decaff..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/influx/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Actuator support for InfluxDB. - */ -package org.springframework.boot.actuate.influx; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/ProcessInfoContributor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/ProcessInfoContributor.java new file mode 100644 index 000000000000..1a1cd3d7540b --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/ProcessInfoContributor.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.info; + +import org.springframework.aot.hint.BindingReflectionHintsRegistrar; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.boot.actuate.info.Info.Builder; +import org.springframework.boot.actuate.info.ProcessInfoContributor.ProcessInfoContributorRuntimeHints; +import org.springframework.boot.info.ProcessInfo; +import org.springframework.context.annotation.ImportRuntimeHints; + +/** + * An {@link InfoContributor} that exposes {@link ProcessInfo}. + * + * @author Jonatan Ivanov + * @since 3.3.0 + */ +@ImportRuntimeHints(ProcessInfoContributorRuntimeHints.class) +public class ProcessInfoContributor implements InfoContributor { + + private final ProcessInfo processInfo; + + public ProcessInfoContributor() { + this.processInfo = new ProcessInfo(); + } + + @Override + public void contribute(Builder builder) { + builder.withDetail("process", this.processInfo); + } + + static class ProcessInfoContributorRuntimeHints implements RuntimeHintsRegistrar { + + private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + this.bindingRegistrar.registerReflectionHints(hints.reflection(), ProcessInfo.class); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicator.java index b7f86dee2128..c82f42177950 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicator.java @@ -101,7 +101,7 @@ protected void doHealthCheck(Health.Builder builder) throws Exception { } } - private void doDataSourceHealthCheck(Health.Builder builder) throws Exception { + private void doDataSourceHealthCheck(Health.Builder builder) { builder.up().withDetail("database", getProduct()); String validationQuery = this.query; if (StringUtils.hasText(validationQuery)) { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusOutputFormat.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusOutputFormat.java new file mode 100644 index 000000000000..582a34cf1250 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusOutputFormat.java @@ -0,0 +1,98 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.metrics.export.prometheus; + +import java.io.IOException; +import java.io.OutputStream; + +import io.prometheus.metrics.expositionformats.ExpositionFormats; +import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; +import io.prometheus.metrics.expositionformats.PrometheusProtobufWriter; +import io.prometheus.metrics.expositionformats.PrometheusTextFormatWriter; +import io.prometheus.metrics.model.snapshots.MetricSnapshots; + +import org.springframework.boot.actuate.endpoint.Producible; +import org.springframework.util.MimeType; +import org.springframework.util.MimeTypeUtils; + +/** + * A {@link Producible} enum for supported Prometheus formats. + * + * @author Andy Wilkinson + * @since 3.3.0 + */ +public enum PrometheusOutputFormat implements Producible { + + /** + * Prometheus text version 0.0.4. + */ + CONTENT_TYPE_004(PrometheusTextFormatWriter.CONTENT_TYPE) { + + @Override + void write(ExpositionFormats expositionFormats, OutputStream outputStream, MetricSnapshots snapshots) + throws IOException { + expositionFormats.getPrometheusTextFormatWriter().write(outputStream, snapshots); + } + + @Override + public boolean isDefault() { + return true; + } + + }, + + /** + * OpenMetrics text version 1.0.0. + */ + CONTENT_TYPE_OPENMETRICS_100(OpenMetricsTextFormatWriter.CONTENT_TYPE) { + + @Override + void write(ExpositionFormats expositionFormats, OutputStream outputStream, MetricSnapshots snapshots) + throws IOException { + expositionFormats.getOpenMetricsTextFormatWriter().write(outputStream, snapshots); + } + + }, + + /** + * Prometheus metrics protobuf. + */ + CONTENT_TYPE_PROTOBUF(PrometheusProtobufWriter.CONTENT_TYPE) { + + @Override + void write(ExpositionFormats expositionFormats, OutputStream outputStream, MetricSnapshots snapshots) + throws IOException { + expositionFormats.getPrometheusProtobufWriter().write(outputStream, snapshots); + } + + }; + + private final MimeType mimeType; + + PrometheusOutputFormat(String mimeType) { + this.mimeType = MimeTypeUtils.parseMimeType(mimeType); + } + + @Override + public MimeType getProducedMimeType() { + return this.mimeType; + } + + abstract void write(ExpositionFormats expositionFormats, OutputStream outputStream, MetricSnapshots snapshots) + throws IOException; + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManager.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManager.java index f0a3453e70e1..dc3ebe4d31ac 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManager.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -141,7 +141,7 @@ private void shutdown(ShutdownOperation shutdownOperation) { } this.scheduled.cancel(false); switch (shutdownOperation) { - case PUSH, POST -> post(); + case POST -> post(); case PUT -> put(); case DELETE -> delete(); } @@ -162,13 +162,6 @@ public enum ShutdownOperation { */ POST, - /** - * Perform a POST before shutdown. - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of {@link #POST}. - */ - @Deprecated(since = "3.0.0", forRemoval = true) - PUSH, - /** * Perform a PUT before shutdown. */ diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusScrapeEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusScrapeEndpoint.java index 9f0a1db9bd72..151a00fb50cb 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusScrapeEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusScrapeEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,16 @@ package org.springframework.boot.actuate.metrics.export.prometheus; +import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; -import java.util.Enumeration; +import java.util.Properties; import java.util.Set; -import io.prometheus.client.Collector.MetricFamilySamples; -import io.prometheus.client.CollectorRegistry; +import io.prometheus.metrics.config.PrometheusProperties; +import io.prometheus.metrics.config.PrometheusPropertiesLoader; +import io.prometheus.metrics.expositionformats.ExpositionFormats; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.MetricSnapshots; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; @@ -37,6 +39,7 @@ * * @author Jon Schneider * @author Johnny Lim + * @author Moritz Halbritter * @since 2.0.0 */ @WebEndpoint(id = "prometheus") @@ -44,30 +47,48 @@ public class PrometheusScrapeEndpoint { private static final int METRICS_SCRAPE_CHARS_EXTRA = 1024; - private final CollectorRegistry collectorRegistry; + private final PrometheusRegistry prometheusRegistry; + + private final ExpositionFormats expositionFormats; private volatile int nextMetricsScrapeSize = 16; - public PrometheusScrapeEndpoint(CollectorRegistry collectorRegistry) { - this.collectorRegistry = collectorRegistry; + /** + * Creates a new {@link PrometheusScrapeEndpoint}. + * @param prometheusRegistry the Prometheus registry to use + * @deprecated since 3.3.1 for removal in 3.5.0 in favor of + * {@link #PrometheusScrapeEndpoint(PrometheusRegistry, Properties)} + */ + @Deprecated(since = "3.3.1", forRemoval = true) + public PrometheusScrapeEndpoint(PrometheusRegistry prometheusRegistry) { + this(prometheusRegistry, null); } - @ReadOperation(producesFrom = TextOutputFormat.class) - public WebEndpointResponse scrape(TextOutputFormat format, @Nullable Set includedNames) { - try { - Writer writer = new StringWriter(this.nextMetricsScrapeSize); - Enumeration samples = (includedNames != null) - ? this.collectorRegistry.filteredMetricFamilySamples(includedNames) - : this.collectorRegistry.metricFamilySamples(); - format.write(writer, samples); - - String scrapePage = writer.toString(); - this.nextMetricsScrapeSize = scrapePage.length() + METRICS_SCRAPE_CHARS_EXTRA; + /** + * Creates a new {@link PrometheusScrapeEndpoint}. + * @param prometheusRegistry the Prometheus registry to use + * @param exporterProperties the properties used to configure Prometheus' + * {@link ExpositionFormats} + */ + public PrometheusScrapeEndpoint(PrometheusRegistry prometheusRegistry, Properties exporterProperties) { + this.prometheusRegistry = prometheusRegistry; + PrometheusProperties prometheusProperties = (exporterProperties != null) + ? PrometheusPropertiesLoader.load(exporterProperties) : PrometheusPropertiesLoader.load(); + this.expositionFormats = ExpositionFormats.init(prometheusProperties.getExporterProperties()); + } - return new WebEndpointResponse<>(scrapePage, format); + @ReadOperation(producesFrom = PrometheusOutputFormat.class) + public WebEndpointResponse scrape(PrometheusOutputFormat format, @Nullable Set includedNames) { + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(this.nextMetricsScrapeSize); + MetricSnapshots metricSnapshots = (includedNames != null) + ? this.prometheusRegistry.scrape(includedNames::contains) : this.prometheusRegistry.scrape(); + format.write(this.expositionFormats, outputStream, metricSnapshots); + byte[] content = outputStream.toByteArray(); + this.nextMetricsScrapeSize = content.length + METRICS_SCRAPE_CHARS_EXTRA; + return new WebEndpointResponse<>(content, format); } catch (IOException ex) { - // This actually never happens since StringWriter doesn't throw an IOException throw new IllegalStateException("Writing metrics failed", ex); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusSimpleclientScrapeEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusSimpleclientScrapeEndpoint.java new file mode 100644 index 000000000000..7e81405cdd26 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusSimpleclientScrapeEndpoint.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.metrics.export.prometheus; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Enumeration; +import java.util.Set; + +import io.prometheus.client.Collector.MetricFamilySamples; +import io.prometheus.client.CollectorRegistry; + +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; +import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint; +import org.springframework.lang.Nullable; + +/** + * {@link Endpoint @Endpoint} that uses the Prometheus simpleclient to output metrics in a + * format that can be scraped by the Prometheus server. + * + * @author Jon Schneider + * @author Johnny Lim + * @since 2.0.0 + * @deprecated since 3.3.0 for removal in 3.5.0 in favor of + * {@link PrometheusScrapeEndpoint} + */ +@Deprecated(since = "3.3.0", forRemoval = true) +@WebEndpoint(id = "prometheus") +public class PrometheusSimpleclientScrapeEndpoint { + + private static final int METRICS_SCRAPE_CHARS_EXTRA = 1024; + + private final CollectorRegistry collectorRegistry; + + private volatile int nextMetricsScrapeSize = 16; + + public PrometheusSimpleclientScrapeEndpoint(CollectorRegistry collectorRegistry) { + this.collectorRegistry = collectorRegistry; + } + + @SuppressWarnings("removal") + @ReadOperation(producesFrom = TextOutputFormat.class) + public WebEndpointResponse scrape(TextOutputFormat format, @Nullable Set includedNames) { + try { + Writer writer = new StringWriter(this.nextMetricsScrapeSize); + Enumeration samples = (includedNames != null) + ? this.collectorRegistry.filteredMetricFamilySamples(includedNames) + : this.collectorRegistry.metricFamilySamples(); + format.write(writer, samples); + String scrapePage = writer.toString(); + this.nextMetricsScrapeSize = scrapePage.length() + METRICS_SCRAPE_CHARS_EXTRA; + return new WebEndpointResponse<>(scrapePage, format); + } + catch (IOException ex) { + // This actually never happens since StringWriter doesn't throw an IOException + throw new IllegalStateException("Writing metrics failed", ex); + } + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/TextOutputFormat.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/TextOutputFormat.java index 54b16b7110c9..659893d8f298 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/TextOutputFormat.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/TextOutputFormat.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,9 @@ * * @author Andy Wilkinson * @since 2.5.0 + * @deprecated since 3.3.0 for removal in 3.5.0 in favor of {@link PrometheusOutputFormat} */ +@Deprecated(since = "3.3.0", forRemoval = true) public enum TextOutputFormat implements Producible { /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/DataSourcePoolMetrics.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/DataSourcePoolMetrics.java index caf29f30ff68..f22023a1ef24 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/DataSourcePoolMetrics.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/DataSourcePoolMetrics.java @@ -110,12 +110,8 @@ Function getValueFunction(Function this.metadataProvider.getDataSourcePoolMetadata(dataSource)); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/DefaultRestTemplateExchangeTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/DefaultRestTemplateExchangeTagsProvider.java deleted file mode 100644 index ace3689e6862..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/DefaultRestTemplateExchangeTagsProvider.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.client; - -import java.util.Arrays; - -import io.micrometer.core.instrument.Tag; - -import org.springframework.http.HttpRequest; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.util.StringUtils; - -/** - * Default implementation of {@link RestTemplateExchangeTagsProvider}. - * - * @author Jon Schneider - * @author Nishant Raut - * @since 2.0.0 - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of - * {@link org.springframework.http.client.observation.DefaultClientRequestObservationConvention} - */ -@Deprecated(since = "3.0.0", forRemoval = true) -@SuppressWarnings("removal") -public class DefaultRestTemplateExchangeTagsProvider implements RestTemplateExchangeTagsProvider { - - @Override - public Iterable getTags(String urlTemplate, HttpRequest request, ClientHttpResponse response) { - Tag uriTag = (StringUtils.hasText(urlTemplate) ? RestTemplateExchangeTags.uri(urlTemplate) - : RestTemplateExchangeTags.uri(request)); - return Arrays.asList(RestTemplateExchangeTags.method(request), uriTag, - RestTemplateExchangeTags.status(response), RestTemplateExchangeTags.clientName(request), - RestTemplateExchangeTags.outcome(response)); - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/ObservationRestClientCustomizer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/ObservationRestClientCustomizer.java new file mode 100644 index 000000000000..904e94260340 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/ObservationRestClientCustomizer.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.metrics.web.client; + +import io.micrometer.observation.ObservationRegistry; + +import org.springframework.boot.web.client.RestClientCustomizer; +import org.springframework.http.client.observation.ClientRequestObservationConvention; +import org.springframework.util.Assert; +import org.springframework.web.client.RestClient.Builder; + +/** + * {@link RestClientCustomizer} that configures the {@link Builder RestClient builder} to + * record request observations. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +public class ObservationRestClientCustomizer implements RestClientCustomizer { + + private final ObservationRegistry observationRegistry; + + private final ClientRequestObservationConvention observationConvention; + + /** + * Create a new {@link ObservationRestClientCustomizer}. + * @param observationRegistry the observation registry + * @param observationConvention the observation convention + */ + public ObservationRestClientCustomizer(ObservationRegistry observationRegistry, + ClientRequestObservationConvention observationConvention) { + Assert.notNull(observationConvention, "ObservationConvention must not be null"); + Assert.notNull(observationRegistry, "ObservationRegistry must not be null"); + this.observationRegistry = observationRegistry; + this.observationConvention = observationConvention; + } + + @Override + public void customize(Builder restClientBuilder) { + restClientBuilder.observationRegistry(this.observationRegistry); + restClientBuilder.observationConvention(this.observationConvention); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTags.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTags.java deleted file mode 100644 index 5f17ee3d4f44..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTags.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.client; - -import java.io.IOException; -import java.net.URI; -import java.util.regex.Pattern; - -import io.micrometer.core.instrument.Tag; - -import org.springframework.boot.actuate.metrics.http.Outcome; -import org.springframework.http.HttpRequest; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.http.client.observation.DefaultClientRequestObservationConvention; -import org.springframework.util.StringUtils; -import org.springframework.web.client.RestTemplate; - -/** - * Factory methods for creating {@link Tag Tags} related to a request-response exchange - * performed by a {@link RestTemplate}. - * - * @author Andy Wilkinson - * @author Jon Schneider - * @author Nishant Raut - * @author Brian Clozel - * @since 2.0.0 - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of - * {@link DefaultClientRequestObservationConvention} - */ -@Deprecated(since = "3.0.0", forRemoval = true) -public final class RestTemplateExchangeTags { - - private static final Pattern STRIP_URI_PATTERN = Pattern.compile("^https?://[^/]+/"); - - private RestTemplateExchangeTags() { - } - - /** - * Creates a {@code method} {@code Tag} for the {@link HttpRequest#getMethod() method} - * of the given {@code request}. - * @param request the request - * @return the method tag - */ - public static Tag method(HttpRequest request) { - return Tag.of("method", request.getMethod().name()); - } - - /** - * Creates a {@code uri} {@code Tag} for the URI of the given {@code request}. - * @param request the request - * @return the uri tag - */ - public static Tag uri(HttpRequest request) { - return Tag.of("uri", ensureLeadingSlash(stripUri(request.getURI().toString()))); - } - - /** - * Creates a {@code uri} {@code Tag} from the given {@code uriTemplate}. - * @param uriTemplate the template - * @return the uri tag - */ - public static Tag uri(String uriTemplate) { - String uri = (StringUtils.hasText(uriTemplate) ? uriTemplate : "none"); - return Tag.of("uri", ensureLeadingSlash(stripUri(uri))); - } - - private static String stripUri(String uri) { - return STRIP_URI_PATTERN.matcher(uri).replaceAll(""); - } - - private static String ensureLeadingSlash(String url) { - return (url == null || url.startsWith("/")) ? url : "/" + url; - } - - /** - * Creates a {@code status} {@code Tag} derived from the - * {@link ClientHttpResponse#getStatusCode() status} of the given {@code response}. - * @param response the response - * @return the status tag - */ - public static Tag status(ClientHttpResponse response) { - return Tag.of("status", getStatusMessage(response)); - } - - private static String getStatusMessage(ClientHttpResponse response) { - try { - if (response == null) { - return "CLIENT_ERROR"; - } - return String.valueOf(response.getStatusCode().value()); - } - catch (IOException ex) { - return "IO_ERROR"; - } - } - - /** - * Create a {@code client.name} {@code Tag} derived from the {@link URI#getHost host} - * of the {@link HttpRequest#getURI() URI} of the given {@code request}. - * @param request the request - * @return the client.name tag - */ - public static Tag clientName(HttpRequest request) { - String host = request.getURI().getHost(); - if (host == null) { - host = "none"; - } - return Tag.of("client.name", host); - } - - /** - * Creates an {@code outcome} {@code Tag} derived from the - * {@link ClientHttpResponse#getStatusCode() status} of the given {@code response}. - * @param response the response - * @return the outcome tag - * @since 2.2.0 - */ - public static Tag outcome(ClientHttpResponse response) { - try { - if (response != null) { - return Outcome.forStatus(response.getStatusCode().value()).asTag(); - } - } - catch (IOException ex) { - // Continue - } - return Outcome.UNKNOWN.asTag(); - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTagsProvider.java deleted file mode 100644 index ea3c05360ce3..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTagsProvider.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.client; - -import io.micrometer.core.instrument.Tag; - -import org.springframework.http.HttpRequest; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.http.client.observation.ClientRequestObservationConvention; -import org.springframework.web.client.RestTemplate; - -/** - * Provides {@link Tag Tags} for an exchange performed by a {@link RestTemplate}. - * - * @author Jon Schneider - * @author Andy Wilkinson - * @since 2.0.0 - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of - * {@link ClientRequestObservationConvention} - */ -@FunctionalInterface -@Deprecated(since = "3.0.0", forRemoval = true) -public interface RestTemplateExchangeTagsProvider { - - /** - * Provides the tags to be associated with metrics that are recorded for the given - * {@code request} and {@code response} exchange. - * @param urlTemplate the source URl template, if available - * @param request the request - * @param response the response (may be {@code null} if the exchange failed) - * @return the tags - */ - Iterable getTags(String urlTemplate, HttpRequest request, ClientHttpResponse response); - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProvider.java deleted file mode 100644 index aeae3222d5ab..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProvider.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.reactive.client; - -import java.util.Arrays; - -import io.micrometer.core.instrument.Tag; - -import org.springframework.web.reactive.function.client.ClientRequest; -import org.springframework.web.reactive.function.client.ClientResponse; - -/** - * Default implementation of {@link WebClientExchangeTagsProvider}. - * - * @author Brian Clozel - * @author Nishant Raut - * @since 2.1.0 - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of - * {@link org.springframework.web.reactive.function.client.ClientRequestObservationConvention} - */ -@Deprecated(since = "3.0.0", forRemoval = true) -@SuppressWarnings("removal") -public class DefaultWebClientExchangeTagsProvider implements WebClientExchangeTagsProvider { - - @Override - public Iterable tags(ClientRequest request, ClientResponse response, Throwable throwable) { - Tag method = WebClientExchangeTags.method(request); - Tag uri = WebClientExchangeTags.uri(request); - Tag clientName = WebClientExchangeTags.clientName(request); - Tag status = WebClientExchangeTags.status(response, throwable); - Tag outcome = WebClientExchangeTags.outcome(response); - return Arrays.asList(method, uri, clientName, status, outcome); - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTags.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTags.java deleted file mode 100644 index c916188b6846..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTags.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.reactive.client; - -import java.io.IOException; -import java.util.regex.Pattern; - -import io.micrometer.core.instrument.Tag; - -import org.springframework.boot.actuate.metrics.http.Outcome; -import org.springframework.http.client.reactive.ClientHttpRequest; -import org.springframework.web.reactive.function.client.ClientRequest; -import org.springframework.web.reactive.function.client.ClientResponse; -import org.springframework.web.reactive.function.client.WebClient; - -/** - * Factory methods for creating {@link Tag Tags} related to a request-response exchange - * performed by a {@link WebClient}. - * - * @author Brian Clozel - * @author Nishant Raut - * @since 2.1.0 - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of - * {@link org.springframework.web.reactive.function.client.DefaultClientRequestObservationConvention} - */ -@Deprecated(since = "3.0.0", forRemoval = true) -public final class WebClientExchangeTags { - - private static final String URI_TEMPLATE_ATTRIBUTE = WebClient.class.getName() + ".uriTemplate"; - - private static final Tag IO_ERROR = Tag.of("status", "IO_ERROR"); - - private static final Tag CLIENT_ERROR = Tag.of("status", "CLIENT_ERROR"); - - private static final Pattern PATTERN_BEFORE_PATH = Pattern.compile("^https?://[^/]+/"); - - private static final Tag CLIENT_NAME_NONE = Tag.of("client.name", "none"); - - private WebClientExchangeTags() { - } - - /** - * Creates a {@code method} {@code Tag} for the {@link ClientHttpRequest#getMethod() - * method} of the given {@code request}. - * @param request the request - * @return the method tag - */ - public static Tag method(ClientRequest request) { - return Tag.of("method", request.method().name()); - } - - /** - * Creates a {@code uri} {@code Tag} for the URI path of the given {@code request}. - * @param request the request - * @return the uri tag - */ - public static Tag uri(ClientRequest request) { - String uri = (String) request.attribute(URI_TEMPLATE_ATTRIBUTE).orElseGet(() -> request.url().toString()); - return Tag.of("uri", extractPath(uri)); - } - - private static String extractPath(String url) { - String path = PATTERN_BEFORE_PATH.matcher(url).replaceFirst(""); - return (path.startsWith("/") ? path : "/" + path); - } - - /** - * Creates a {@code status} {@code Tag} derived from the - * {@link ClientResponse#statusCode()} of the given {@code response} if available, the - * thrown exception otherwise, or considers the request as Cancelled as a last resort. - * @param response the response - * @param throwable the exception - * @return the status tag - * @since 2.3.0 - */ - public static Tag status(ClientResponse response, Throwable throwable) { - if (response != null) { - return Tag.of("status", String.valueOf(response.statusCode().value())); - } - if (throwable != null) { - return (throwable instanceof IOException) ? IO_ERROR : CLIENT_ERROR; - } - return CLIENT_ERROR; - } - - /** - * Create a {@code client.name} {@code Tag} derived from the - * {@link java.net.URI#getHost host} of the {@link ClientRequest#url() URL} of the - * given {@code request}. - * @param request the request - * @return the client.name tag - */ - public static Tag clientName(ClientRequest request) { - String host = request.url().getHost(); - if (host == null) { - return CLIENT_NAME_NONE; - } - return Tag.of("client.name", host); - } - - /** - * Creates an {@code outcome} {@code Tag} derived from the - * {@link ClientResponse#statusCode() status} of the given {@code response}. - * @param response the response - * @return the outcome tag - * @since 2.2.0 - */ - public static Tag outcome(ClientResponse response) { - Outcome outcome = (response != null) ? Outcome.forStatus(response.statusCode().value()) : Outcome.UNKNOWN; - return outcome.asTag(); - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTagsProvider.java deleted file mode 100644 index 7d522e48d2b3..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTagsProvider.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.reactive.client; - -import io.micrometer.core.instrument.Tag; - -import org.springframework.web.reactive.function.client.ClientRequest; -import org.springframework.web.reactive.function.client.ClientResponse; - -/** - * {@link Tag Tags} provider for an exchange performed by a - * {@link org.springframework.web.reactive.function.client.WebClient}. - * - * @author Brian Clozel - * @since 2.1.0 - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of - * {@link org.springframework.web.reactive.function.client.ClientRequestObservationConvention} - */ -@FunctionalInterface -@Deprecated(since = "3.0.0", forRemoval = true) -public interface WebClientExchangeTagsProvider { - - /** - * Provide tags to be associated with metrics for the client exchange. - * @param request the client request - * @param response the server response (may be {@code null}) - * @param throwable the exception (may be {@code null}) - * @return tags to associate with metrics for the request and response exchange - */ - Iterable tags(ClientRequest request, ClientResponse response, Throwable throwable); - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProvider.java deleted file mode 100644 index ef319dc065ad..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProvider.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.reactive.server; - -import java.util.Collections; -import java.util.List; - -import io.micrometer.core.instrument.Tag; -import io.micrometer.core.instrument.Tags; - -import org.springframework.web.server.ServerWebExchange; - -/** - * Default implementation of {@link WebFluxTagsProvider}. - * - * @author Jon Schneider - * @author Andy Wilkinson - * @since 2.0.0 - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of - * {@link org.springframework.http.server.reactive.observation.ServerRequestObservationConvention} - */ -@Deprecated(since = "3.0.0", forRemoval = true) -@SuppressWarnings("removal") -public class DefaultWebFluxTagsProvider implements WebFluxTagsProvider { - - private final boolean ignoreTrailingSlash; - - private final List contributors; - - public DefaultWebFluxTagsProvider() { - this(false); - } - - /** - * Creates a new {@link DefaultWebFluxTagsProvider} that will provide tags from the - * given {@code contributors} in addition to its own. - * @param contributors the contributors that will provide additional tags - * @since 2.3.0 - */ - public DefaultWebFluxTagsProvider(List contributors) { - this(false, contributors); - } - - public DefaultWebFluxTagsProvider(boolean ignoreTrailingSlash) { - this(ignoreTrailingSlash, Collections.emptyList()); - } - - /** - * Creates a new {@link DefaultWebFluxTagsProvider} that will provide tags from the - * given {@code contributors} in addition to its own. - * @param ignoreTrailingSlash whether trailing slashes should be ignored when - * determining the {@code uri} tag. - * @param contributors the contributors that will provide additional tags - * @since 2.3.0 - */ - public DefaultWebFluxTagsProvider(boolean ignoreTrailingSlash, List contributors) { - this.ignoreTrailingSlash = ignoreTrailingSlash; - this.contributors = contributors; - } - - @Override - public Iterable httpRequestTags(ServerWebExchange exchange, Throwable exception) { - Tags tags = Tags.empty(); - tags = tags.and(WebFluxTags.method(exchange)); - tags = tags.and(WebFluxTags.uri(exchange, this.ignoreTrailingSlash)); - tags = tags.and(WebFluxTags.exception(exception)); - tags = tags.and(WebFluxTags.status(exchange)); - tags = tags.and(WebFluxTags.outcome(exchange, exception)); - for (WebFluxTagsContributor contributor : this.contributors) { - tags = tags.and(contributor.httpRequestTags(exchange, exception)); - } - return tags; - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTags.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTags.java deleted file mode 100644 index ecada43258a4..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTags.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.reactive.server; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.regex.Pattern; - -import io.micrometer.core.instrument.Tag; - -import org.springframework.boot.actuate.metrics.http.Outcome; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.util.StringUtils; -import org.springframework.web.reactive.HandlerMapping; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.pattern.PathPattern; - -/** - * Factory methods for {@link Tag Tags} associated with a request-response exchange that - * is handled by WebFlux. - * - * @author Jon Schneider - * @author Andy Wilkinson - * @author Michael McFadyen - * @author Brian Clozel - * @since 2.0.0 - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of - * {@link org.springframework.http.server.reactive.observation.ServerRequestObservationConvention} - */ -@Deprecated(since = "3.0.0", forRemoval = true) -public final class WebFluxTags { - - private static final Tag URI_NOT_FOUND = Tag.of("uri", "NOT_FOUND"); - - private static final Tag URI_REDIRECTION = Tag.of("uri", "REDIRECTION"); - - private static final Tag URI_ROOT = Tag.of("uri", "root"); - - private static final Tag URI_UNKNOWN = Tag.of("uri", "UNKNOWN"); - - private static final Tag EXCEPTION_NONE = Tag.of("exception", "None"); - - private static final Pattern FORWARD_SLASHES_PATTERN = Pattern.compile("//+"); - - private static final Set DISCONNECTED_CLIENT_EXCEPTIONS = new HashSet<>( - Arrays.asList("AbortedException", "ClientAbortException", "EOFException", "EofException")); - - private WebFluxTags() { - } - - /** - * Creates a {@code method} tag based on the - * {@link org.springframework.http.server.reactive.ServerHttpRequest#getMethod() - * method} of the {@link ServerWebExchange#getRequest()} request of the given - * {@code exchange}. - * @param exchange the exchange - * @return the method tag whose value is a capitalized method (e.g. GET). - */ - public static Tag method(ServerWebExchange exchange) { - return Tag.of("method", exchange.getRequest().getMethod().name()); - } - - /** - * Creates a {@code status} tag based on the response status of the given - * {@code exchange}. - * @param exchange the exchange - * @return the status tag derived from the response status - */ - public static Tag status(ServerWebExchange exchange) { - HttpStatusCode status = exchange.getResponse().getStatusCode(); - if (status == null) { - status = HttpStatus.OK; - } - return Tag.of("status", String.valueOf(status.value())); - } - - /** - * Creates a {@code uri} tag based on the URI of the given {@code exchange}. Uses the - * {@link HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE} best matching pattern if - * available. Falling back to {@code REDIRECTION} for 3xx responses, {@code NOT_FOUND} - * for 404 responses, {@code root} for requests with no path info, and {@code UNKNOWN} - * for all other requests. - * @param exchange the exchange - * @return the uri tag derived from the exchange - */ - public static Tag uri(ServerWebExchange exchange) { - return uri(exchange, false); - } - - /** - * Creates a {@code uri} tag based on the URI of the given {@code exchange}. Uses the - * {@link HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE} best matching pattern if - * available. Falling back to {@code REDIRECTION} for 3xx responses, {@code NOT_FOUND} - * for 404 responses, {@code root} for requests with no path info, and {@code UNKNOWN} - * for all other requests. - * @param exchange the exchange - * @param ignoreTrailingSlash whether to ignore the trailing slash - * @return the uri tag derived from the exchange - */ - public static Tag uri(ServerWebExchange exchange, boolean ignoreTrailingSlash) { - PathPattern pathPattern = exchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); - if (pathPattern != null) { - String patternString = pathPattern.getPatternString(); - if (ignoreTrailingSlash && patternString.length() > 1) { - patternString = removeTrailingSlash(patternString); - } - if (patternString.isEmpty()) { - return URI_ROOT; - } - return Tag.of("uri", patternString); - } - HttpStatusCode status = exchange.getResponse().getStatusCode(); - if (status != null) { - if (status.is3xxRedirection()) { - return URI_REDIRECTION; - } - if (status == HttpStatus.NOT_FOUND) { - return URI_NOT_FOUND; - } - } - String path = getPathInfo(exchange); - if (path.isEmpty()) { - return URI_ROOT; - } - return URI_UNKNOWN; - } - - private static String getPathInfo(ServerWebExchange exchange) { - String path = exchange.getRequest().getPath().value(); - String uri = StringUtils.hasText(path) ? path : "/"; - String singleSlashes = FORWARD_SLASHES_PATTERN.matcher(uri).replaceAll("/"); - return removeTrailingSlash(singleSlashes); - } - - private static String removeTrailingSlash(String text) { - if (!StringUtils.hasLength(text)) { - return text; - } - return text.endsWith("/") ? text.substring(0, text.length() - 1) : text; - } - - /** - * Creates an {@code exception} tag based on the {@link Class#getSimpleName() simple - * name} of the class of the given {@code exception}. - * @param exception the exception, may be {@code null} - * @return the exception tag derived from the exception - */ - public static Tag exception(Throwable exception) { - if (exception != null) { - String simpleName = exception.getClass().getSimpleName(); - return Tag.of("exception", StringUtils.hasText(simpleName) ? simpleName : exception.getClass().getName()); - } - return EXCEPTION_NONE; - } - - /** - * Creates an {@code outcome} tag based on the response status of the given - * {@code exchange} and the exception thrown during request processing. - * @param exchange the exchange - * @param exception the termination signal sent by the publisher - * @return the outcome tag derived from the response status - * @since 2.5.0 - */ - public static Tag outcome(ServerWebExchange exchange, Throwable exception) { - if (exception != null) { - if (DISCONNECTED_CLIENT_EXCEPTIONS.contains(exception.getClass().getSimpleName())) { - return Outcome.UNKNOWN.asTag(); - } - } - HttpStatusCode statusCode = exchange.getResponse().getStatusCode(); - Outcome outcome = (statusCode != null) ? Outcome.forStatus(statusCode.value()) : Outcome.SUCCESS; - return outcome.asTag(); - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsContributor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsContributor.java deleted file mode 100644 index 6bd9958dfca3..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsContributor.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.reactive.server; - -import io.micrometer.core.instrument.Tag; - -import org.springframework.web.server.ServerWebExchange; - -/** - * A contributor of {@link Tag Tags} for WebFlux-based request handling. Typically used by - * a {@link WebFluxTagsProvider} to provide tags in addition to its defaults. - * - * @author Andy Wilkinson - * @since 2.3.0 - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of - * {@link org.springframework.http.server.reactive.observation.ServerRequestObservationConvention} - */ -@FunctionalInterface -@Deprecated(since = "3.0.0", forRemoval = true) -public interface WebFluxTagsContributor { - - /** - * Provides tags to be associated with metrics for the given {@code exchange}. - * @param exchange the exchange - * @param ex the current exception (may be {@code null}) - * @return tags to associate with metrics for the request and response exchange - */ - Iterable httpRequestTags(ServerWebExchange exchange, Throwable ex); - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsProvider.java deleted file mode 100644 index 081c9598cc89..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsProvider.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.reactive.server; - -import io.micrometer.core.instrument.Tag; - -import org.springframework.web.server.ServerWebExchange; - -/** - * Provides {@link Tag Tags} for WebFlux-based request handling. - * - * @author Jon Schneider - * @author Andy Wilkinson - * @since 2.0.0 - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of - * {@link org.springframework.http.server.reactive.observation.ServerRequestObservationConvention} - */ -@FunctionalInterface -@Deprecated(since = "3.0.0", forRemoval = true) -public interface WebFluxTagsProvider { - - /** - * Provides tags to be associated with metrics for the given {@code exchange}. - * @param exchange the exchange - * @param ex the current exception (may be {@code null}) - * @return tags to associate with metrics for the request and response exchange - */ - Iterable httpRequestTags(ServerWebExchange exchange, Throwable ex); - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/package-info.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/package-info.java deleted file mode 100644 index 1167af5ca302..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Actuator support for WebFlux metrics. - */ -package org.springframework.boot.actuate.metrics.web.reactive.server; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/DefaultWebMvcTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/DefaultWebMvcTagsProvider.java deleted file mode 100644 index ac50670ca326..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/DefaultWebMvcTagsProvider.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.servlet; - -import java.util.Collections; -import java.util.List; - -import io.micrometer.core.instrument.Tag; -import io.micrometer.core.instrument.Tags; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -/** - * Default implementation of {@link WebMvcTagsProvider}. - * - * @author Jon Schneider - * @since 2.0.0 - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of - * {@link org.springframework.http.server.observation.ServerRequestObservationConvention} - */ -@Deprecated(since = "3.0.0", forRemoval = true) -@SuppressWarnings("removal") -public class DefaultWebMvcTagsProvider implements WebMvcTagsProvider { - - private final boolean ignoreTrailingSlash; - - private final List contributors; - - public DefaultWebMvcTagsProvider() { - this(false); - } - - /** - * Creates a new {@link DefaultWebMvcTagsProvider} that will provide tags from the - * given {@code contributors} in addition to its own. - * @param contributors the contributors that will provide additional tags - * @since 2.3.0 - */ - public DefaultWebMvcTagsProvider(List contributors) { - this(false, contributors); - } - - public DefaultWebMvcTagsProvider(boolean ignoreTrailingSlash) { - this(ignoreTrailingSlash, Collections.emptyList()); - } - - /** - * Creates a new {@link DefaultWebMvcTagsProvider} that will provide tags from the - * given {@code contributors} in addition to its own. - * @param ignoreTrailingSlash whether trailing slashes should be ignored when - * determining the {@code uri} tag. - * @param contributors the contributors that will provide additional tags - * @since 2.3.0 - */ - public DefaultWebMvcTagsProvider(boolean ignoreTrailingSlash, List contributors) { - this.ignoreTrailingSlash = ignoreTrailingSlash; - this.contributors = contributors; - } - - @Override - public Iterable getTags(HttpServletRequest request, HttpServletResponse response, Object handler, - Throwable exception) { - Tags tags = Tags.of(WebMvcTags.method(request), WebMvcTags.uri(request, response, this.ignoreTrailingSlash), - WebMvcTags.exception(exception), WebMvcTags.status(response), WebMvcTags.outcome(response)); - for (WebMvcTagsContributor contributor : this.contributors) { - tags = tags.and(contributor.getTags(request, response, handler, exception)); - } - return tags; - } - - @Override - public Iterable getLongRequestTags(HttpServletRequest request, Object handler) { - Tags tags = Tags.of(WebMvcTags.method(request), WebMvcTags.uri(request, null, this.ignoreTrailingSlash)); - for (WebMvcTagsContributor contributor : this.contributors) { - tags = tags.and(contributor.getLongRequestTags(request, handler)); - } - return tags; - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTags.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTags.java deleted file mode 100644 index db5c1f0e49c5..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTags.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.servlet; - -import java.util.regex.Pattern; - -import io.micrometer.core.instrument.Tag; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.boot.actuate.metrics.http.Outcome; -import org.springframework.http.HttpStatus; -import org.springframework.util.StringUtils; -import org.springframework.web.servlet.HandlerMapping; -import org.springframework.web.util.pattern.PathPattern; - -/** - * Factory methods for {@link Tag Tags} associated with a request-response exchange that - * is handled by Spring MVC. - * - * @author Jon Schneider - * @author Andy Wilkinson - * @author Brian Clozel - * @author Michael McFadyen - * @since 2.0.0 - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of - * {@link org.springframework.http.server.observation.ServerRequestObservationConvention} - */ -@Deprecated(since = "3.0.0", forRemoval = true) -public final class WebMvcTags { - - private static final String DATA_REST_PATH_PATTERN_ATTRIBUTE = "org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping.EFFECTIVE_REPOSITORY_RESOURCE_LOOKUP_PATH"; - - private static final Tag URI_NOT_FOUND = Tag.of("uri", "NOT_FOUND"); - - private static final Tag URI_REDIRECTION = Tag.of("uri", "REDIRECTION"); - - private static final Tag URI_ROOT = Tag.of("uri", "root"); - - private static final Tag URI_UNKNOWN = Tag.of("uri", "UNKNOWN"); - - private static final Tag EXCEPTION_NONE = Tag.of("exception", "None"); - - private static final Tag STATUS_UNKNOWN = Tag.of("status", "UNKNOWN"); - - private static final Tag METHOD_UNKNOWN = Tag.of("method", "UNKNOWN"); - - private static final Pattern TRAILING_SLASH_PATTERN = Pattern.compile("/$"); - - private static final Pattern MULTIPLE_SLASH_PATTERN = Pattern.compile("//+"); - - private WebMvcTags() { - } - - /** - * Creates a {@code method} tag based on the {@link HttpServletRequest#getMethod() - * method} of the given {@code request}. - * @param request the request - * @return the method tag whose value is a capitalized method (e.g. GET). - */ - public static Tag method(HttpServletRequest request) { - return (request != null) ? Tag.of("method", request.getMethod()) : METHOD_UNKNOWN; - } - - /** - * Creates a {@code status} tag based on the status of the given {@code response}. - * @param response the HTTP response - * @return the status tag derived from the status of the response - */ - public static Tag status(HttpServletResponse response) { - return (response != null) ? Tag.of("status", Integer.toString(response.getStatus())) : STATUS_UNKNOWN; - } - - /** - * Creates a {@code uri} tag based on the URI of the given {@code request}. Uses the - * {@link HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE} best matching pattern if - * available. Falling back to {@code REDIRECTION} for 3xx responses, {@code NOT_FOUND} - * for 404 responses, {@code root} for requests with no path info, and {@code UNKNOWN} - * for all other requests. - * @param request the request - * @param response the response - * @return the uri tag derived from the request - */ - public static Tag uri(HttpServletRequest request, HttpServletResponse response) { - return uri(request, response, false); - } - - /** - * Creates a {@code uri} tag based on the URI of the given {@code request}. Uses the - * {@link HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE} best matching pattern if - * available. Falling back to {@code REDIRECTION} for 3xx responses, {@code NOT_FOUND} - * for 404 responses, {@code root} for requests with no path info, and {@code UNKNOWN} - * for all other requests. - * @param request the request - * @param response the response - * @param ignoreTrailingSlash whether to ignore the trailing slash - * @return the uri tag derived from the request - */ - public static Tag uri(HttpServletRequest request, HttpServletResponse response, boolean ignoreTrailingSlash) { - if (request != null) { - String pattern = getMatchingPattern(request); - if (pattern != null) { - if (ignoreTrailingSlash && pattern.length() > 1) { - pattern = TRAILING_SLASH_PATTERN.matcher(pattern).replaceAll(""); - } - if (pattern.isEmpty()) { - return URI_ROOT; - } - return Tag.of("uri", pattern); - } - if (response != null) { - HttpStatus status = extractStatus(response); - if (status != null) { - if (status.is3xxRedirection()) { - return URI_REDIRECTION; - } - if (status == HttpStatus.NOT_FOUND) { - return URI_NOT_FOUND; - } - } - } - String pathInfo = getPathInfo(request); - if (pathInfo.isEmpty()) { - return URI_ROOT; - } - } - return URI_UNKNOWN; - } - - private static HttpStatus extractStatus(HttpServletResponse response) { - try { - return HttpStatus.valueOf(response.getStatus()); - } - catch (IllegalArgumentException ex) { - return null; - } - } - - private static String getMatchingPattern(HttpServletRequest request) { - PathPattern dataRestPathPattern = (PathPattern) request.getAttribute(DATA_REST_PATH_PATTERN_ATTRIBUTE); - if (dataRestPathPattern != null) { - return dataRestPathPattern.getPatternString(); - } - return (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); - } - - private static String getPathInfo(HttpServletRequest request) { - String pathInfo = request.getPathInfo(); - String uri = StringUtils.hasText(pathInfo) ? pathInfo : "/"; - uri = MULTIPLE_SLASH_PATTERN.matcher(uri).replaceAll("/"); - return TRAILING_SLASH_PATTERN.matcher(uri).replaceAll(""); - } - - /** - * Creates an {@code exception} tag based on the {@link Class#getSimpleName() simple - * name} of the class of the given {@code exception}. - * @param exception the exception, may be {@code null} - * @return the exception tag derived from the exception - */ - public static Tag exception(Throwable exception) { - if (exception != null) { - String simpleName = exception.getClass().getSimpleName(); - return Tag.of("exception", StringUtils.hasText(simpleName) ? simpleName : exception.getClass().getName()); - } - return EXCEPTION_NONE; - } - - /** - * Creates an {@code outcome} tag based on the status of the given {@code response}. - * @param response the HTTP response - * @return the outcome tag derived from the status of the response - * @since 2.1.0 - */ - public static Tag outcome(HttpServletResponse response) { - Outcome outcome = (response != null) ? Outcome.forStatus(response.getStatus()) : Outcome.UNKNOWN; - return outcome.asTag(); - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTagsContributor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTagsContributor.java deleted file mode 100644 index f27b2115af67..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTagsContributor.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.servlet; - -import io.micrometer.core.instrument.LongTaskTimer; -import io.micrometer.core.instrument.Tag; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -/** - * A contributor of {@link Tag Tags} for Spring MVC-based request handling. Typically used - * by a {@link WebMvcTagsProvider} to provide tags in addition to its defaults. - * - * @author Andy Wilkinson - * @since 2.3.0 - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of - * {@link org.springframework.http.server.observation.ServerRequestObservationConvention} - */ -@Deprecated(since = "3.0.0", forRemoval = true) -public interface WebMvcTagsContributor { - - /** - * Provides tags to be associated with metrics for the given {@code request} and - * {@code response} exchange. - * @param request the request - * @param response the response - * @param handler the handler for the request or {@code null} if the handler is - * unknown - * @param exception the current exception, if any - * @return tags to associate with metrics for the request and response exchange - */ - Iterable getTags(HttpServletRequest request, HttpServletResponse response, Object handler, - Throwable exception); - - /** - * Provides tags to be used by {@link LongTaskTimer long task timers}. - * @param request the HTTP request - * @param handler the handler for the request or {@code null} if the handler is - * unknown - * @return tags to associate with metrics recorded for the request - */ - Iterable getLongRequestTags(HttpServletRequest request, Object handler); - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTagsProvider.java deleted file mode 100644 index 09206f727b1b..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTagsProvider.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.servlet; - -import io.micrometer.core.instrument.LongTaskTimer; -import io.micrometer.core.instrument.Tag; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -/** - * Provides {@link Tag Tags} for Spring MVC-based request handling. - * - * @author Jon Schneider - * @author Andy Wilkinson - * @since 2.0.0 - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of - * {@link org.springframework.http.server.observation.ServerRequestObservationConvention} - */ -@Deprecated(since = "3.0.0", forRemoval = true) -public interface WebMvcTagsProvider { - - /** - * Provides tags to be associated with metrics for the given {@code request} and - * {@code response} exchange. - * @param request the request - * @param response the response - * @param handler the handler for the request or {@code null} if the handler is - * unknown - * @param exception the current exception, if any - * @return tags to associate with metrics for the request and response exchange - */ - Iterable getTags(HttpServletRequest request, HttpServletResponse response, Object handler, - Throwable exception); - - /** - * Provides tags to be used by {@link LongTaskTimer long task timers}. - * @param request the HTTP request - * @param handler the handler for the request or {@code null} if the handler is - * unknown - * @return tags to associate with metrics recorded for the request - */ - Iterable getLongRequestTags(HttpServletRequest request, Object handler); - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/package-info.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/package-info.java deleted file mode 100644 index 22bbf429f87a..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Actuator support for Spring MVC metrics. - */ -package org.springframework.boot.actuate.metrics.web.servlet; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/quartz/QuartzEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/quartz/QuartzEndpoint.java index 0e0b64cd325d..e6cdde6920d2 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/quartz/QuartzEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/quartz/QuartzEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,6 +46,7 @@ import org.quartz.Trigger.TriggerState; import org.quartz.TriggerKey; import org.quartz.impl.matchers.GroupMatcher; +import org.quartz.utils.Key; import org.springframework.boot.actuate.endpoint.OperationResponseBody; import org.springframework.boot.actuate.endpoint.SanitizableData; @@ -100,7 +101,7 @@ public QuartzGroupsDescriptor quartzJobGroups() throws SchedulerException { for (String groupName : this.scheduler.getJobGroupNames()) { List jobs = this.scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName)) .stream() - .map((key) -> key.getName()) + .map(Key::getName) .toList(); result.put(groupName, Collections.singletonMap("jobs", jobs)); } @@ -121,7 +122,7 @@ public QuartzGroupsDescriptor quartzTriggerGroups() throws SchedulerException { groupDetails.put("triggers", this.scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals(groupName)) .stream() - .map((key) -> key.getName()) + .map(Key::getName) .toList()); result.put(groupName, groupDetails); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/sbom/SbomEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/sbom/SbomEndpoint.java new file mode 100644 index 000000000000..503e667f72ac --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/sbom/SbomEndpoint.java @@ -0,0 +1,164 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.sbom; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.boot.actuate.endpoint.OperationResponseBody; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.annotation.Selector; +import org.springframework.boot.actuate.sbom.SbomEndpoint.SbomEndpointRuntimeHints; +import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.StringUtils; + +/** + * {@link Endpoint @Endpoint} to expose an SBOM. + * + * @author Moritz Halbritter + * @since 3.3.0 + */ +@Endpoint(id = "sbom") +@ImportRuntimeHints(SbomEndpointRuntimeHints.class) +public class SbomEndpoint { + + private static final List DEFAULT_APPLICATION_SBOM_LOCATIONS = List.of("classpath:META-INF/sbom/bom.json", + "classpath:META-INF/sbom/application.cdx.json"); + + static final String APPLICATION_SBOM_ID = "application"; + + private final SbomProperties properties; + + private final ResourceLoader resourceLoader; + + private final Map sboms; + + public SbomEndpoint(SbomProperties properties, ResourceLoader resourceLoader) { + this.properties = properties; + this.resourceLoader = resourceLoader; + this.sboms = Collections.unmodifiableMap(getSboms()); + } + + private Map getSboms() { + Map result = new HashMap<>(); + addKnownSboms(result); + addAdditionalSboms(result); + return result; + } + + private void addAdditionalSboms(Map result) { + this.properties.getAdditional().forEach((id, sbom) -> { + Resource resource = loadResource(sbom.getLocation()); + if (resource != null) { + if (result.putIfAbsent(id, resource) != null) { + throw new IllegalStateException("Duplicate SBOM registration with id '%s'".formatted(id)); + } + } + }); + } + + private void addKnownSboms(Map result) { + Resource applicationSbom = getApplicationSbom(); + if (applicationSbom != null) { + result.put(APPLICATION_SBOM_ID, applicationSbom); + } + } + + @ReadOperation + Sboms sboms() { + return new Sboms(new TreeSet<>(this.sboms.keySet())); + } + + @ReadOperation + Resource sbom(@Selector String id) { + return this.sboms.get(id); + } + + private Resource getApplicationSbom() { + if (StringUtils.hasLength(this.properties.getApplication().getLocation())) { + return loadResource(this.properties.getApplication().getLocation()); + } + for (String location : DEFAULT_APPLICATION_SBOM_LOCATIONS) { + Resource resource = this.resourceLoader.getResource(location); + if (resource.exists()) { + return resource; + } + } + return null; + } + + private Resource loadResource(String location) { + if (location == null) { + return null; + } + Location parsedLocation = Location.of(location); + Resource resource = this.resourceLoader.getResource(parsedLocation.location()); + if (resource.exists()) { + return resource; + } + if (parsedLocation.optional()) { + return null; + } + throw new IllegalStateException("Resource '%s' doesn't exist and it's not marked optional".formatted(location)); + } + + record Sboms(Collection ids) implements OperationResponseBody { + } + + private record Location(String location, boolean optional) { + + private static final String OPTIONAL_PREFIX = "optional:"; + + static Location of(String location) { + boolean optional = isOptional(location); + return new Location(optional ? stripOptionalPrefix(location) : location, optional); + } + + private static boolean isOptional(String location) { + return location.startsWith(OPTIONAL_PREFIX); + } + + private static String stripOptionalPrefix(String location) { + return location.substring(OPTIONAL_PREFIX.length()); + } + } + + static class SbomEndpointRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + for (String defaultLocation : DEFAULT_APPLICATION_SBOM_LOCATIONS) { + hints.resources().registerPattern(stripClasspathPrefix(defaultLocation)); + } + } + + private String stripClasspathPrefix(String location) { + return location.substring("classpath:".length()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/sbom/SbomEndpointWebExtension.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/sbom/SbomEndpointWebExtension.java new file mode 100644 index 000000000000..422895ec3fae --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/sbom/SbomEndpointWebExtension.java @@ -0,0 +1,132 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.sbom; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.annotation.Selector; +import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; +import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension; +import org.springframework.boot.actuate.sbom.SbomProperties.Sbom; +import org.springframework.core.io.Resource; +import org.springframework.util.MimeType; + +/** + * {@link EndpointWebExtension @EndpointWebExtension} for the {@link SbomEndpoint}. + * + * @author Moritz Halbritter + * @since 3.3.0 + */ +@EndpointWebExtension(endpoint = SbomEndpoint.class) +public class SbomEndpointWebExtension { + + private final SbomEndpoint sbomEndpoint; + + private final SbomProperties properties; + + private final Map detectedMediaTypeCache = new ConcurrentHashMap<>(); + + public SbomEndpointWebExtension(SbomEndpoint sbomEndpoint, SbomProperties properties) { + this.sbomEndpoint = sbomEndpoint; + this.properties = properties; + } + + @ReadOperation + WebEndpointResponse sbom(@Selector String id) { + Resource resource = this.sbomEndpoint.sbom(id); + if (resource == null) { + return new WebEndpointResponse<>(WebEndpointResponse.STATUS_NOT_FOUND); + } + MimeType type = getMediaType(id, resource); + return (type != null) ? new WebEndpointResponse<>(resource, type) : new WebEndpointResponse<>(resource); + } + + private MimeType getMediaType(String id, Resource resource) { + if (SbomEndpoint.APPLICATION_SBOM_ID.equals(id) && this.properties.getApplication().getMediaType() != null) { + return this.properties.getApplication().getMediaType(); + } + Sbom sbomProperties = this.properties.getAdditional().get(id); + if (sbomProperties != null && sbomProperties.getMediaType() != null) { + return sbomProperties.getMediaType(); + } + return this.detectedMediaTypeCache.computeIfAbsent(id, (ignored) -> { + try { + return detectSbomType(resource); + } + catch (IOException ex) { + throw new UncheckedIOException("Failed to detect type of resource '%s'".formatted(resource), ex); + } + }).getMediaType(); + } + + private SbomType detectSbomType(Resource resource) throws IOException { + String content = resource.getContentAsString(StandardCharsets.UTF_8); + for (SbomType candidate : SbomType.values()) { + if (candidate.matches(content)) { + return candidate; + } + } + return SbomType.UNKNOWN; + } + + enum SbomType { + + CYCLONE_DX(MimeType.valueOf("application/vnd.cyclonedx+json")) { + @Override + boolean matches(String content) { + return content.replaceAll("\\s", "").contains("\"bomFormat\":\"CycloneDX\""); + } + }, + SPDX(MimeType.valueOf("application/spdx+json")) { + @Override + boolean matches(String content) { + return content.contains("\"spdxVersion\""); + } + }, + SYFT(MimeType.valueOf("application/vnd.syft+json")) { + @Override + boolean matches(String content) { + return content.contains("\"FoundBy\"") || content.contains("\"foundBy\""); + } + }, + UNKNOWN(null) { + @Override + boolean matches(String content) { + return false; + } + }; + + private final MimeType mediaType; + + SbomType(MimeType mediaType) { + this.mediaType = mediaType; + } + + MimeType getMediaType() { + return this.mediaType; + } + + abstract boolean matches(String content); + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/sbom/SbomProperties.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/sbom/SbomProperties.java new file mode 100644 index 000000000000..5d883a83de70 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/sbom/SbomProperties.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.sbom; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.util.MimeType; + +/** + * Configuration properties for the SBOM endpoint. + * + * @author Moritz Halbritter + * @since 3.3.0 + */ +@ConfigurationProperties(prefix = "management.endpoint.sbom") +public class SbomProperties { + + /** + * Application SBOM configuration. + */ + private final Sbom application = new Sbom(); + + /** + * Additional SBOMs. + */ + private Map additional = new HashMap<>(); + + public Sbom getApplication() { + return this.application; + } + + public Map getAdditional() { + return this.additional; + } + + public void setAdditional(Map additional) { + this.additional = additional; + } + + public static class Sbom { + + /** + * Location to the SBOM. If null, the location will be auto-detected. + */ + private String location; + + /** + * Media type of the SBOM. If null, the media type will be auto-detected. + */ + private MimeType mediaType; + + public String getLocation() { + return this.location; + } + + public void setLocation(String location) { + this.location = location; + } + + public MimeType getMediaType() { + return this.mediaType; + } + + public void setMediaType(MimeType mediaType) { + this.mediaType = mediaType; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/sbom/package-info.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/sbom/package-info.java new file mode 100644 index 000000000000..48977e87fab2 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/sbom/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Actuator support for SBOMs. + */ +package org.springframework.boot.actuate.sbom; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpoint.java index 7b666c8b05c0..98eff14a5d45 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.springframework.boot.actuate.scheduling; -import java.lang.reflect.Method; import java.time.Duration; import java.util.Collection; import java.util.Collections; @@ -46,7 +45,6 @@ import org.springframework.scheduling.config.TriggerTask; import org.springframework.scheduling.support.CronTrigger; import org.springframework.scheduling.support.PeriodicTrigger; -import org.springframework.scheduling.support.ScheduledMethodRunnable; /** * {@link Endpoint @Endpoint} to expose information about an application's scheduled @@ -284,13 +282,7 @@ public static final class RunnableDescriptor { private final String target; private RunnableDescriptor(Runnable runnable) { - if (runnable instanceof ScheduledMethodRunnable scheduledMethodRunnable) { - Method method = scheduledMethodRunnable.getMethod(); - this.target = method.getDeclaringClass().getName() + "." + method.getName(); - } - else { - this.target = runnable.getClass().getName(); - } + this.target = runnable.toString(); } public String getTarget() { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AuthenticationAuditListener.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AuthenticationAuditListener.java index f35bd905935a..13beb48e3064 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AuthenticationAuditListener.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AuthenticationAuditListener.java @@ -24,6 +24,7 @@ import org.springframework.security.authentication.event.AbstractAuthenticationEvent; import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent; import org.springframework.security.authentication.event.AuthenticationSuccessEvent; +import org.springframework.security.authentication.event.LogoutSuccessEvent; import org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent; import org.springframework.util.ClassUtils; @@ -51,6 +52,13 @@ public class AuthenticationAuditListener extends AbstractAuthenticationAuditList */ public static final String AUTHENTICATION_SWITCH = "AUTHENTICATION_SWITCH"; + /** + * Logout success event type. + * + * @since 3.4.0 + */ + public static final String LOGOUT_SUCCESS = "LOGOUT_SUCCESS"; + private static final String WEB_LISTENER_CHECK_CLASS = "org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent"; private final WebAuditListener webListener = maybeCreateWebListener(); @@ -73,6 +81,9 @@ else if (this.webListener != null && this.webListener.accepts(event)) { else if (event instanceof AuthenticationSuccessEvent successEvent) { onAuthenticationSuccessEvent(successEvent); } + else if (event instanceof LogoutSuccessEvent logoutSuccessEvent) { + onLogoutSuccessEvent(logoutSuccessEvent); + } } private void onAuthenticationFailureEvent(AbstractAuthenticationFailureEvent event) { @@ -93,6 +104,14 @@ private void onAuthenticationSuccessEvent(AuthenticationSuccessEvent event) { publish(new AuditEvent(event.getAuthentication().getName(), AUTHENTICATION_SUCCESS, data)); } + private void onLogoutSuccessEvent(LogoutSuccessEvent event) { + Map data = new LinkedHashMap<>(); + if (event.getAuthentication().getDetails() != null) { + data.put("details", event.getAuthentication().getDetails()); + } + publish(new AuditEvent(event.getAuthentication().getName(), LOGOUT_SUCCESS, data)); + } + private static final class WebAuditListener { void process(AuthenticationAuditListener listener, AbstractAuthenticationEvent input) { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpoint.java new file mode 100644 index 000000000000..9ee15d69fc02 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpoint.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.session; + +import reactor.core.publisher.Mono; + +import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.annotation.Selector; +import org.springframework.boot.actuate.session.SessionsDescriptor.SessionDescriptor; +import org.springframework.session.ReactiveFindByIndexNameSessionRepository; +import org.springframework.session.ReactiveSessionRepository; +import org.springframework.session.Session; +import org.springframework.util.Assert; + +/** + * {@link Endpoint @Endpoint} to expose information about HTTP {@link Session}s on a + * reactive stack. + * + * @author Vedran Pavic + * @author Moritz Halbritter + * @since 3.3.0 + */ +@Endpoint(id = "sessions") +public class ReactiveSessionsEndpoint { + + private final ReactiveSessionRepository sessionRepository; + + private final ReactiveFindByIndexNameSessionRepository indexedSessionRepository; + + /** + * Create a new {@link ReactiveSessionsEndpoint} instance. + * @param sessionRepository the session repository + * @param indexedSessionRepository the indexed session repository + */ + public ReactiveSessionsEndpoint(ReactiveSessionRepository sessionRepository, + ReactiveFindByIndexNameSessionRepository indexedSessionRepository) { + Assert.notNull(sessionRepository, "ReactiveSessionRepository must not be null"); + this.sessionRepository = sessionRepository; + this.indexedSessionRepository = indexedSessionRepository; + } + + @ReadOperation + public Mono sessionsForUsername(String username) { + if (this.indexedSessionRepository == null) { + return Mono.empty(); + } + return this.indexedSessionRepository.findByPrincipalName(username).map(SessionsDescriptor::new); + } + + @ReadOperation + public Mono getSession(@Selector String sessionId) { + return this.sessionRepository.findById(sessionId).map(SessionDescriptor::new); + } + + @DeleteOperation + public Mono deleteSession(@Selector String sessionId) { + return this.sessionRepository.deleteById(sessionId); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionsDescriptor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionsDescriptor.java new file mode 100644 index 000000000000..24e12de097b8 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionsDescriptor.java @@ -0,0 +1,98 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.session; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.boot.actuate.endpoint.OperationResponseBody; +import org.springframework.session.Session; + +/** + * Description of user's {@link Session sessions}. + * + * @author Moritz Halbritter + * @since 3.3.0 + */ +public final class SessionsDescriptor implements OperationResponseBody { + + private final List sessions; + + public SessionsDescriptor(Map sessions) { + this.sessions = sessions.values().stream().map(SessionDescriptor::new).toList(); + } + + public List getSessions() { + return this.sessions; + } + + /** + * A description of user's {@link Session session} exposed by {@code sessions} + * endpoint. Primarily intended for serialization to JSON. + */ + public static final class SessionDescriptor { + + private final String id; + + private final Set attributeNames; + + private final Instant creationTime; + + private final Instant lastAccessedTime; + + private final long maxInactiveInterval; + + private final boolean expired; + + SessionDescriptor(Session session) { + this.id = session.getId(); + this.attributeNames = session.getAttributeNames(); + this.creationTime = session.getCreationTime(); + this.lastAccessedTime = session.getLastAccessedTime(); + this.maxInactiveInterval = session.getMaxInactiveInterval().getSeconds(); + this.expired = session.isExpired(); + } + + public String getId() { + return this.id; + } + + public Set getAttributeNames() { + return this.attributeNames; + } + + public Instant getCreationTime() { + return this.creationTime; + } + + public Instant getLastAccessedTime() { + return this.lastAccessedTime; + } + + public long getMaxInactiveInterval() { + return this.maxInactiveInterval; + } + + public boolean isExpired() { + return this.expired; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionsEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionsEndpoint.java index b333d8e23c2b..1b89c83a1132 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionsEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionsEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,21 +16,21 @@ package org.springframework.boot.actuate.session; -import java.time.Instant; -import java.util.List; import java.util.Map; -import java.util.Set; -import org.springframework.boot.actuate.endpoint.OperationResponseBody; import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.Selector; +import org.springframework.boot.actuate.session.SessionsDescriptor.SessionDescriptor; import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.Session; +import org.springframework.session.SessionRepository; +import org.springframework.util.Assert; /** - * {@link Endpoint @Endpoint} to expose a user's {@link Session}s. + * {@link Endpoint @Endpoint} to expose information about HTTP {@link Session}s on a + * Servlet stack. * * @author Vedran Pavic * @since 2.0.0 @@ -38,19 +38,40 @@ @Endpoint(id = "sessions") public class SessionsEndpoint { - private final FindByIndexNameSessionRepository sessionRepository; + private final SessionRepository sessionRepository; + + private final FindByIndexNameSessionRepository indexedSessionRepository; /** * Create a new {@link SessionsEndpoint} instance. * @param sessionRepository the session repository + * @deprecated since 3.3.0 for removal in 3.5.0 in favor of + * {@link #SessionsEndpoint(SessionRepository, FindByIndexNameSessionRepository)} */ + @Deprecated(since = "3.3.0", forRemoval = true) public SessionsEndpoint(FindByIndexNameSessionRepository sessionRepository) { + this(sessionRepository, sessionRepository); + } + + /** + * Create a new {@link SessionsEndpoint} instance. + * @param sessionRepository the session repository + * @param indexedSessionRepository the indexed session repository + * @since 3.3.0 + */ + public SessionsEndpoint(SessionRepository sessionRepository, + FindByIndexNameSessionRepository indexedSessionRepository) { + Assert.notNull(sessionRepository, "SessionRepository must not be null"); this.sessionRepository = sessionRepository; + this.indexedSessionRepository = indexedSessionRepository; } @ReadOperation public SessionsDescriptor sessionsForUsername(String username) { - Map sessions = this.sessionRepository.findByPrincipalName(username); + if (this.indexedSessionRepository == null) { + return null; + } + Map sessions = this.indexedSessionRepository.findByPrincipalName(username); return new SessionsDescriptor(sessions); } @@ -68,73 +89,4 @@ public void deleteSession(@Selector String sessionId) { this.sessionRepository.deleteById(sessionId); } - /** - * Description of user's {@link Session sessions}. - */ - public static final class SessionsDescriptor implements OperationResponseBody { - - private final List sessions; - - public SessionsDescriptor(Map sessions) { - this.sessions = sessions.values().stream().map(SessionDescriptor::new).toList(); - } - - public List getSessions() { - return this.sessions; - } - - } - - /** - * Description of user's {@link Session session}. - */ - public static final class SessionDescriptor implements OperationResponseBody { - - private final String id; - - private final Set attributeNames; - - private final Instant creationTime; - - private final Instant lastAccessedTime; - - private final long maxInactiveInterval; - - private final boolean expired; - - public SessionDescriptor(Session session) { - this.id = session.getId(); - this.attributeNames = session.getAttributeNames(); - this.creationTime = session.getCreationTime(); - this.lastAccessedTime = session.getLastAccessedTime(); - this.maxInactiveInterval = session.getMaxInactiveInterval().getSeconds(); - this.expired = session.isExpired(); - } - - public String getId() { - return this.id; - } - - public Set getAttributeNames() { - return this.attributeNames; - } - - public Instant getCreationTime() { - return this.creationTime; - } - - public Instant getLastAccessedTime() { - return this.lastAccessedTime; - } - - public long getMaxInactiveInterval() { - return this.maxInactiveInterval; - } - - public boolean isExpired() { - return this.expired; - } - - } - } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/ShutdownEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/ShutdownEndpointTests.java index 45b37c3e6fb3..018dd3a9059c 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/ShutdownEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/ShutdownEndpointTests.java @@ -60,7 +60,7 @@ void shutdown() { Thread.currentThread().setContextClassLoader(previousTccl); } assertThat(result.getMessage()).startsWith("Shutting down"); - assertThat(((ConfigurableApplicationContext) context).isActive()).isTrue(); + assertThat(context.isActive()).isTrue(); assertThat(config.latch.await(10, TimeUnit.SECONDS)).isTrue(); assertThat(config.threadContextClassLoader).isEqualTo(getClass().getClassLoader()); }); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/convert/ConversionServiceParameterValueMapperTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/convert/ConversionServiceParameterValueMapperTests.java index 3ba4df37a969..c2c1bc8d6ca2 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/convert/ConversionServiceParameterValueMapperTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/convert/ConversionServiceParameterValueMapperTests.java @@ -30,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; @@ -55,7 +56,7 @@ void mapParameterShouldDelegateToConversionService() { void mapParameterWhenConversionServiceFailsShouldThrowParameterMappingException() { ConversionService conversionService = mock(ConversionService.class); RuntimeException error = new RuntimeException(); - given(conversionService.convert(any(), any())).willThrow(error); + given(conversionService.convert(any(Object.class), eq(Integer.class))).willThrow(error); ConversionServiceParameterValueMapper mapper = new ConversionServiceParameterValueMapper(conversionService); assertThatExceptionOfType(ParameterMappingException.class) .isThrownBy(() -> mapper.mapParameterValue(new TestOperationParameter(Integer.class), "123")) diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanTests.java index 5fa8a81dd3b4..442bd367c948 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import javax.management.MBeanInfo; import javax.management.ReflectionException; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -165,7 +166,7 @@ void invokeWhenFluxResultShouldCollectToMonoListAndBlockOnMono() throws MBeanExc new TestJmxOperation((arguments) -> Flux.just("flux", "result"))); EndpointMBean bean = new EndpointMBean(this.responseMapper, null, endpoint); Object result = bean.invoke("testOperation", NO_PARAMS, NO_SIGNATURE); - assertThat(result).asList().containsExactly("flux", "result"); + assertThat(result).asInstanceOf(InstanceOfAssertFactories.LIST).containsExactly("flux", "result"); } @Test diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointLinksResolverTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointLinksResolverTests.java index 29a7b0038410..334193049c37 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointLinksResolverTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointLinksResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -75,6 +75,7 @@ void resolvedLinksContainsALinkForEachWebEndpointOperation() { } @Test + @SuppressWarnings("removal") void resolvedLinksContainsALinkForServletEndpoint() { ExposableServletEndpoint servletEndpoint = mock(ExposableServletEndpoint.class); given(servletEndpoint.getEndpointId()).willReturn(EndpointId.of("alpha")); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointServletTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointServletTests.java index 56a819fc51a8..b4e074ad5a9a 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointServletTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointServletTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ * @author Phillip Webb * @author Stephane Nicoll */ +@SuppressWarnings("removal") class EndpointServletTests { @Test diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrarTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrarTests.java index c0900bef2c02..8959534c8234 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrarTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrarTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,6 +48,7 @@ * @author Stephane Nicoll */ @ExtendWith(MockitoExtension.class) +@SuppressWarnings("removal") class ServletEndpointRegistrarTests { @Mock diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscovererTests.java index bf29e3bde1bd..f75cb971b2a5 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscovererTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,6 @@ import org.springframework.boot.actuate.endpoint.annotation.DiscoveredEndpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; -import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointDiscoverer.ControllerEndpointDiscovererRuntimeHints; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; @@ -50,6 +49,7 @@ * @author Stephane Nicoll * @author Moritz Halbritter */ +@SuppressWarnings("removal") class ControllerEndpointDiscovererTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); @@ -132,7 +132,8 @@ void getEndpointWhenEndpointHasOperationsShouldThrowException() { @Test void shouldRegisterHints() { RuntimeHints runtimeHints = new RuntimeHints(); - new ControllerEndpointDiscovererRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + new ControllerEndpointDiscoverer.ControllerEndpointDiscovererRuntimeHints().registerHints(runtimeHints, + getClass().getClassLoader()); assertThat(RuntimeHintsPredicates.reflection() .onType(ControllerEndpointFilter.class) .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(runtimeHints); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscovererTests.java index e4d9ec56a7e0..3e91f8cd7396 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscovererTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,6 @@ import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.web.EndpointServlet; import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint; -import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointDiscoverer.ServletEndpointDiscovererRuntimeHints; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; @@ -58,6 +57,7 @@ * @author Stephane Nicoll * @author Moritz Halbritter */ +@SuppressWarnings("removal") class ServletEndpointDiscovererTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); @@ -135,7 +135,8 @@ void getEndpointWhenEndpointSuppliesNullShouldThrowException() { @Test void shouldRegisterHints() { RuntimeHints runtimeHints = new RuntimeHints(); - new ServletEndpointDiscovererRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + new ServletEndpointDiscoverer.ServletEndpointDiscovererRuntimeHints().registerHints(runtimeHints, + getClass().getClassLoader()); assertThat(RuntimeHintsPredicates.reflection() .onType(ServletEndpointFilter.class) .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(runtimeHints); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/ControllerEndpointHandlerMappingIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/ControllerEndpointHandlerMappingIntegrationTests.java index 03e5a94dfac6..a54671819ff9 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/ControllerEndpointHandlerMappingIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/ControllerEndpointHandlerMappingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,6 +58,7 @@ * @author Phillip Webb * @author Stephane Nicoll */ +@SuppressWarnings("removal") class ControllerEndpointHandlerMappingIntegrationTests { private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner( diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/ControllerEndpointHandlerMappingTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/ControllerEndpointHandlerMappingTests.java index e307b8d1bc67..19a8349195f1 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/ControllerEndpointHandlerMappingTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/ControllerEndpointHandlerMappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,6 +46,7 @@ * @author Phillip Webb * @author Stephane Nicoll */ +@SuppressWarnings("removal") class ControllerEndpointHandlerMappingTests { private final StaticApplicationContext context = new StaticApplicationContext(); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/ControllerEndpointHandlerMappingIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/ControllerEndpointHandlerMappingIntegrationTests.java index 75b587779452..58aa9df52037 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/ControllerEndpointHandlerMappingIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/ControllerEndpointHandlerMappingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,6 +57,7 @@ * @author Phillip Webb * @author Stephane Nicoll */ +@SuppressWarnings("removal") class ControllerEndpointHandlerMappingIntegrationTests { private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner( diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/ControllerEndpointHandlerMappingTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/ControllerEndpointHandlerMappingTests.java index 9a74d68b3319..8f3f7db88e3e 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/ControllerEndpointHandlerMappingTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/ControllerEndpointHandlerMappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,6 +43,7 @@ * @author Phillip Webb * @author Stephane Nicoll */ +@SuppressWarnings("removal") class ControllerEndpointHandlerMappingTests { private final StaticApplicationContext context = new StaticApplicationContext(); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/DefaultWebMvcTagsProviderTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/DefaultWebMvcTagsProviderTests.java deleted file mode 100644 index 4bdcb94ce427..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/DefaultWebMvcTagsProviderTests.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.endpoint.web.servlet; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -import io.micrometer.core.instrument.Tag; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider; -import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsContributor; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.web.servlet.HandlerMapping; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link DefaultWebMvcTagsProvider}. - * - * @author Andy Wilkinson - */ -@SuppressWarnings("removal") -@Deprecated(since = "3.0.0", forRemoval = true) -class DefaultWebMvcTagsProviderTests { - - @Test - void whenTagsAreProvidedThenDefaultTagsArePresent() { - Map tags = asMap(new DefaultWebMvcTagsProvider().getTags(null, null, null, null)); - assertThat(tags).containsOnlyKeys("exception", "method", "outcome", "status", "uri"); - } - - @Test - void givenSomeContributorsWhenTagsAreProvidedThenDefaultTagsAndContributedTagsArePresent() { - Map tags = asMap( - new DefaultWebMvcTagsProvider(Arrays.asList(new TestWebMvcTagsContributor("alpha"), - new TestWebMvcTagsContributor("bravo", "charlie"))) - .getTags(null, null, null, null)); - assertThat(tags).containsOnlyKeys("exception", "method", "outcome", "status", "uri", "alpha", "bravo", - "charlie"); - } - - @Test - void whenLongRequestTagsAreProvidedThenDefaultTagsArePresent() { - Map tags = asMap(new DefaultWebMvcTagsProvider().getLongRequestTags(null, null)); - assertThat(tags).containsOnlyKeys("method", "uri"); - } - - @Test - void givenSomeContributorsWhenLongRequestTagsAreProvidedThenDefaultTagsAndContributedTagsArePresent() { - Map tags = asMap( - new DefaultWebMvcTagsProvider(Arrays.asList(new TestWebMvcTagsContributor("alpha"), - new TestWebMvcTagsContributor("bravo", "charlie"))) - .getLongRequestTags(null, null)); - assertThat(tags).containsOnlyKeys("method", "uri", "alpha", "bravo", "charlie"); - } - - @Test - void trailingSlashIsIncludedByDefault() { - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/the/uri/"); - request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "{one}/{two}/"); - Map tags = asMap(new DefaultWebMvcTagsProvider().getTags(request, null, null, null)); - assertThat(tags.get("uri").getValue()).isEqualTo("{one}/{two}/"); - } - - @Test - void trailingSlashCanBeIgnored() { - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/the/uri/"); - request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "{one}/{two}/"); - Map tags = asMap(new DefaultWebMvcTagsProvider(true).getTags(request, null, null, null)); - assertThat(tags.get("uri").getValue()).isEqualTo("{one}/{two}"); - } - - private Map asMap(Iterable tags) { - return StreamSupport.stream(tags.spliterator(), false) - .collect(Collectors.toMap(Tag::getKey, Function.identity())); - } - - private static final class TestWebMvcTagsContributor implements WebMvcTagsContributor { - - private final List tagNames; - - private TestWebMvcTagsContributor(String... tagNames) { - this.tagNames = Arrays.asList(tagNames); - } - - @Override - public Iterable getTags(HttpServletRequest request, HttpServletResponse response, Object handler, - Throwable exception) { - return this.tagNames.stream().map((name) -> Tag.of(name, "value")).toList(); - } - - @Override - public Iterable getLongRequestTags(HttpServletRequest request, Object handler) { - return this.tagNames.stream().map((name) -> Tag.of(name, "value")).toList(); - } - - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/WebMvcTagsTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/WebMvcTagsTests.java deleted file mode 100644 index 7097ee9dfd79..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/WebMvcTagsTests.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.endpoint.web.servlet; - -import io.micrometer.core.instrument.Tag; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTags; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.web.servlet.HandlerMapping; -import org.springframework.web.util.pattern.PathPatternParser; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link WebMvcTags}. - * - * @author Andy Wilkinson - * @author Brian Clozel - * @author Michael McFadyen - */ -@SuppressWarnings("removal") -@Deprecated(since = "3.0.0", forRemoval = true) -class WebMvcTagsTests { - - private final MockHttpServletRequest request = new MockHttpServletRequest(); - - private final MockHttpServletResponse response = new MockHttpServletResponse(); - - @Test - void uriTagIsDataRestsEffectiveRepositoryLookupPathWhenAvailable() { - this.request.setAttribute( - "org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping.EFFECTIVE_REPOSITORY_RESOURCE_LOOKUP_PATH", - new PathPatternParser().parse("/api/cities")); - this.request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "/api/{repository}"); - Tag tag = WebMvcTags.uri(this.request, this.response); - assertThat(tag.getValue()).isEqualTo("/api/cities"); - } - - @Test - void uriTagValueIsBestMatchingPatternWhenAvailable() { - this.request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "/spring/"); - this.response.setStatus(301); - Tag tag = WebMvcTags.uri(this.request, this.response); - assertThat(tag.getValue()).isEqualTo("/spring/"); - } - - @Test - void uriTagValueIsRootWhenBestMatchingPatternIsEmpty() { - this.request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, ""); - this.response.setStatus(301); - Tag tag = WebMvcTags.uri(this.request, this.response); - assertThat(tag.getValue()).isEqualTo("root"); - } - - @Test - void uriTagValueWithBestMatchingPatternAndIgnoreTrailingSlashRemoveTrailingSlash() { - this.request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "/spring/"); - Tag tag = WebMvcTags.uri(this.request, this.response, true); - assertThat(tag.getValue()).isEqualTo("/spring"); - } - - @Test - void uriTagValueWithBestMatchingPatternAndIgnoreTrailingSlashKeepSingleSlash() { - this.request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "/"); - Tag tag = WebMvcTags.uri(this.request, this.response, true); - assertThat(tag.getValue()).isEqualTo("/"); - } - - @Test - void uriTagValueIsRootWhenRequestHasNoPatternOrPathInfo() { - assertThat(WebMvcTags.uri(this.request, null).getValue()).isEqualTo("root"); - } - - @Test - void uriTagValueIsRootWhenRequestHasNoPatternAndSlashPathInfo() { - this.request.setPathInfo("/"); - assertThat(WebMvcTags.uri(this.request, null).getValue()).isEqualTo("root"); - } - - @Test - void uriTagValueIsUnknownWhenRequestHasNoPatternAndNonRootPathInfo() { - this.request.setPathInfo("/example"); - assertThat(WebMvcTags.uri(this.request, null).getValue()).isEqualTo("UNKNOWN"); - } - - @Test - void uriTagValueIsRedirectionWhenResponseStatusIs3xx() { - this.response.setStatus(301); - Tag tag = WebMvcTags.uri(this.request, this.response); - assertThat(tag.getValue()).isEqualTo("REDIRECTION"); - } - - @Test - void uriTagValueIsNotFoundWhenResponseStatusIs404() { - this.response.setStatus(404); - Tag tag = WebMvcTags.uri(this.request, this.response); - assertThat(tag.getValue()).isEqualTo("NOT_FOUND"); - } - - @Test - void uriTagToleratesCustomResponseStatus() { - this.response.setStatus(601); - Tag tag = WebMvcTags.uri(this.request, this.response); - assertThat(tag.getValue()).isEqualTo("root"); - } - - @Test - void uriTagIsUnknownWhenRequestIsNull() { - Tag tag = WebMvcTags.uri(null, null); - assertThat(tag.getValue()).isEqualTo("UNKNOWN"); - } - - @Test - void outcomeTagIsUnknownWhenResponseIsNull() { - Tag tag = WebMvcTags.outcome(null); - assertThat(tag.getValue()).isEqualTo("UNKNOWN"); - } - - @Test - void outcomeTagIsInformationalWhenResponseIs1xx() { - this.response.setStatus(100); - Tag tag = WebMvcTags.outcome(this.response); - assertThat(tag.getValue()).isEqualTo("INFORMATIONAL"); - } - - @Test - void outcomeTagIsSuccessWhenResponseIs2xx() { - this.response.setStatus(200); - Tag tag = WebMvcTags.outcome(this.response); - assertThat(tag.getValue()).isEqualTo("SUCCESS"); - } - - @Test - void outcomeTagIsRedirectionWhenResponseIs3xx() { - this.response.setStatus(301); - Tag tag = WebMvcTags.outcome(this.response); - assertThat(tag.getValue()).isEqualTo("REDIRECTION"); - } - - @Test - void outcomeTagIsClientErrorWhenResponseIs4xx() { - this.response.setStatus(400); - Tag tag = WebMvcTags.outcome(this.response); - assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR"); - } - - @Test - void outcomeTagIsClientErrorWhenResponseIsNonStandardInClientSeries() { - this.response.setStatus(490); - Tag tag = WebMvcTags.outcome(this.response); - assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR"); - } - - @Test - void outcomeTagIsServerErrorWhenResponseIs5xx() { - this.response.setStatus(500); - Tag tag = WebMvcTags.outcome(this.response); - assertThat(tag.getValue()).isEqualTo("SERVER_ERROR"); - } - - @Test - void outcomeTagIsUnknownWhenResponseStatusIsInUnknownSeries() { - this.response.setStatus(701); - Tag tag = WebMvcTags.outcome(this.response); - assertThat(tag.getValue()).isEqualTo("UNKNOWN"); - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebEndpointTest.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebEndpointTest.java index 395fd4de9ad4..86ffbea6cb08 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebEndpointTest.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebEndpointTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,13 +20,18 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.List; +import java.util.function.Function; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTestInvocationContextProvider.WebEndpointsInvocationContext; +import org.springframework.context.ConfigurableApplicationContext; + /** - * Signals that a test should be performed against all web endpoint implementations - * (Jersey, Web MVC, and WebFlux) + * Signals that a test should be run against one or more of the web endpoint + * infrastructure implementations (Jersey, Web MVC, and WebFlux) * * @author Andy Wilkinson */ @@ -36,4 +41,42 @@ @ExtendWith(WebEndpointTestInvocationContextProvider.class) public @interface WebEndpointTest { + /** + * The infrastructure against which the test should run. + * @return the infrastructure to run the tests against + */ + Infrastructure[] infrastructure() default { Infrastructure.JERSEY, Infrastructure.MVC, Infrastructure.WEBFLUX }; + + enum Infrastructure { + + /** + * Actuator running on the Jersey-based infrastructure. + */ + JERSEY("Jersey", WebEndpointTestInvocationContextProvider::createJerseyContext), + + /** + * Actuator running on the WebMVC-based infrastructure. + */ + MVC("WebMvc", WebEndpointTestInvocationContextProvider::createWebMvcContext), + + /** + * Actuator running on the WebFlux-based infrastructure. + */ + WEBFLUX("WebFlux", WebEndpointTestInvocationContextProvider::createWebFluxContext); + + private final String name; + + private final Function>, ConfigurableApplicationContext> contextFactory; + + Infrastructure(String name, Function>, ConfigurableApplicationContext> contextFactory) { + this.name = name; + this.contextFactory = contextFactory; + } + + WebEndpointsInvocationContext createInvocationContext() { + return new WebEndpointsInvocationContext(this.name, this.contextFactory); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebEndpointTestInvocationContextProvider.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebEndpointTestInvocationContextProvider.java index debfdb60787b..c0ac708f205e 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebEndpointTestInvocationContextProvider.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebEndpointTestInvocationContextProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; +import org.junit.platform.commons.util.AnnotationUtils; import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper; import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; @@ -45,6 +46,7 @@ import org.springframework.boot.actuate.endpoint.web.jersey.JerseyEndpointResourceFactory; import org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping; import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping; +import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest.Infrastructure; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; @@ -91,16 +93,14 @@ public boolean supportsTestTemplate(ExtensionContext context) { @Override public Stream provideTestTemplateInvocationContexts( ExtensionContext extensionContext) { - return Stream.of( - new WebEndpointsInvocationContext("Jersey", - WebEndpointTestInvocationContextProvider::createJerseyContext), - new WebEndpointsInvocationContext("WebMvc", - WebEndpointTestInvocationContextProvider::createWebMvcContext), - new WebEndpointsInvocationContext("WebFlux", - WebEndpointTestInvocationContextProvider::createWebFluxContext)); + WebEndpointTest webEndpointTest = AnnotationUtils + .findAnnotation(extensionContext.getRequiredTestMethod(), WebEndpointTest.class) + .orElseThrow(() -> new IllegalStateException("Unable to find WebEndpointTest annotation on %s" + .formatted(extensionContext.getRequiredTestMethod()))); + return Stream.of(webEndpointTest.infrastructure()).distinct().map(Infrastructure::createInvocationContext); } - private static ConfigurableApplicationContext createJerseyContext(List> classes) { + static ConfigurableApplicationContext createJerseyContext(List> classes) { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(); classes.add(JerseyEndpointConfiguration.class); context.register(ClassUtils.toClassArray(classes)); @@ -108,7 +108,7 @@ private static ConfigurableApplicationContext createJerseyContext(List> return context; } - private static ConfigurableApplicationContext createWebMvcContext(List> classes) { + static ConfigurableApplicationContext createWebMvcContext(List> classes) { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(); classes.add(WebMvcEndpointConfiguration.class); context.register(ClassUtils.toClassArray(classes)); @@ -116,7 +116,7 @@ private static ConfigurableApplicationContext createWebMvcContext(List> return context; } - private static ConfigurableApplicationContext createWebFluxContext(List> classes) { + static ConfigurableApplicationContext createWebFluxContext(List> classes) { AnnotationConfigReactiveWebServerApplicationContext context = new AnnotationConfigReactiveWebServerApplicationContext(); classes.add(WebFluxEndpointConfiguration.class); context.register(ClassUtils.toClassArray(classes)); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointWebExtensionTests.java index febf1ee346fd..f8e8ca87c6c6 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointWebExtensionTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointWebExtensionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -86,7 +86,8 @@ void whenShowValuesIsWhenAuthorizedAndSecurityContextIsNotAuthorized() { private void verifyPrefixed(SecurityContext securityContext, boolean showUnsanitized) { given(this.delegate.getEnvironmentEntryDescriptor("test", showUnsanitized)) - .willReturn(new EnvironmentEntryDescriptor(null, Collections.emptyList(), Collections.emptyList())); + .willReturn(new EnvironmentEntryDescriptor(null, Collections.emptyList(), Collections.emptyList(), + Collections.emptyList())); this.webExtension.environmentEntry(securityContext, "test"); then(this.delegate).should().getEnvironmentEntryDescriptor("test", showUnsanitized); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicatorTests.java deleted file mode 100644 index 367b1ec99113..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicatorTests.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.influx; - -import java.io.IOException; - -import org.influxdb.InfluxDB; -import org.influxdb.InfluxDBException; -import org.influxdb.dto.Pong; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.Status; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.then; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link InfluxDbHealthIndicator}. - * - * @author Eddú Meléndez - */ -class InfluxDbHealthIndicatorTests { - - @Test - void influxDbIsUp() { - Pong pong = mock(Pong.class); - given(pong.getVersion()).willReturn("0.9"); - InfluxDB influxDb = mock(InfluxDB.class); - given(influxDb.ping()).willReturn(pong); - InfluxDbHealthIndicator healthIndicator = new InfluxDbHealthIndicator(influxDb); - Health health = healthIndicator.health(); - assertThat(health.getStatus()).isEqualTo(Status.UP); - assertThat(health.getDetails()).containsEntry("version", "0.9"); - then(influxDb).should().ping(); - } - - @Test - void influxDbIsDown() { - InfluxDB influxDb = mock(InfluxDB.class); - given(influxDb.ping()).willThrow(new InfluxDBException(new IOException("Connection failed"))); - InfluxDbHealthIndicator healthIndicator = new InfluxDbHealthIndicator(influxDb); - Health health = healthIndicator.health(); - assertThat(health.getStatus()).isEqualTo(Status.DOWN); - assertThat((String) health.getDetails().get("error")).contains("Connection failed"); - then(influxDb).should().ping(); - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/ProcessInfoContributorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/ProcessInfoContributorTests.java new file mode 100644 index 000000000000..faceb15528ed --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/ProcessInfoContributorTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.info; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.info.ProcessInfoContributor.ProcessInfoContributorRuntimeHints; +import org.springframework.boot.info.ProcessInfo; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ProcessInfoContributor}. + * + * @author Jonatan Ivanov + */ +class ProcessInfoContributorTests { + + @Test + void processInfoShouldBeAdded() { + ProcessInfoContributor processInfoContributor = new ProcessInfoContributor(); + Info.Builder builder = new Info.Builder(); + processInfoContributor.contribute(builder); + Info info = builder.build(); + assertThat(info.get("process")).isInstanceOf(ProcessInfo.class); + } + + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new ProcessInfoContributorRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection() + .onType(ProcessInfo.class) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS)) + .accepts(runtimeHints); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/mail/MailHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/mail/MailHealthIndicatorTests.java index 88fda8cead08..9abfbedd88da 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/mail/MailHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/mail/MailHealthIndicatorTests.java @@ -81,7 +81,7 @@ void smtpOnDefaultHostAndPortIsDown() throws MessagingException { assertThat(health.getDetails()).doesNotContainKey("location"); Object errorMessage = health.getDetails().get("error"); assertThat(errorMessage).isNotNull(); - assertThat(errorMessage.toString().contains("A test exception")).isTrue(); + assertThat(errorMessage.toString()).contains("A test exception"); } @Test @@ -104,7 +104,7 @@ void smtpOnDefaultHostAndCustomPortIsDown() throws MessagingException { assertThat(health.getDetails().get("location")).isEqualTo(":1234"); Object errorMessage = health.getDetails().get("error"); assertThat(errorMessage).isNotNull(); - assertThat(errorMessage.toString().contains("A test exception")).isTrue(); + assertThat(errorMessage.toString()).contains("A test exception"); } @Test @@ -125,7 +125,7 @@ void smtpOnDefaultPortIsDown() throws MessagingException { assertThat(health.getDetails()).containsEntry("location", "smtp.acme.org"); Object errorMessage = health.getDetails().get("error"); assertThat(errorMessage).isNotNull(); - assertThat(errorMessage.toString().contains("A test exception")).isTrue(); + assertThat(errorMessage.toString()).contains("A test exception"); } @Test diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManagerTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManagerTests.java index ca8d61d3108c..6960ea722752 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManagerTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManagerTests.java @@ -140,18 +140,6 @@ void shutdownWhenDoesNotOwnSchedulerDoesNotShutdownScheduler() { then(otherScheduler).should(never()).shutdown(); } - @Test - @SuppressWarnings("removal") - @Deprecated(since = "3.0.0", forRemoval = true) - void shutdownWhenShutdownOperationIsPushPerformsPushAddOnShutdown() throws Exception { - givenScheduleAtFixedRateWithReturnFuture(); - PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager(this.pushGateway, this.registry, - this.scheduler, this.pushRate, "job", this.groupingKey, ShutdownOperation.PUSH); - manager.shutdown(); - then(this.future).should().cancel(false); - then(this.pushGateway).should().pushAdd(this.registry, "job", this.groupingKey); - } - @Test void shutdownWhenShutdownOperationIsPostPerformsPushAddOnShutdown() throws Exception { givenScheduleAtFixedRateWithReturnFuture(); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusScrapeEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusScrapeEndpointIntegrationTests.java index d74a63e875ec..2d041728f533 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusScrapeEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusScrapeEndpointIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,16 @@ package org.springframework.boot.actuate.metrics.export.prometheus; +import java.util.Properties; + import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.prometheus.PrometheusMeterRegistry; -import io.prometheus.client.CollectorRegistry; -import io.prometheus.client.exporter.common.TextFormat; +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; +import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; +import io.prometheus.metrics.expositionformats.PrometheusProtobufWriter; +import io.prometheus.metrics.expositionformats.PrometheusTextFormatWriter; +import io.prometheus.metrics.model.registry.PrometheusRegistry; import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest; import org.springframework.context.annotation.Bean; @@ -41,8 +45,7 @@ class PrometheusScrapeEndpointIntegrationTests { @WebEndpointTest void scrapeHasContentTypeText004ByDefault(WebTestClient client) { - String expectedContentType = TextFormat.CONTENT_TYPE_004; - assertThat(TextFormat.chooseContentType(null)).isEqualTo(expectedContentType); + String expectedContentType = PrometheusTextFormatWriter.CONTENT_TYPE; client.get() .uri("/actuator/prometheus") .exchange() @@ -58,9 +61,8 @@ void scrapeHasContentTypeText004ByDefault(WebTestClient client) { @WebEndpointTest void scrapeHasContentTypeText004ByDefaultWhenClientAcceptsWildcardWithParameter(WebTestClient client) { - String expectedContentType = TextFormat.CONTENT_TYPE_004; + String expectedContentType = PrometheusTextFormatWriter.CONTENT_TYPE; String accept = "*/*;q=0.8"; - assertThat(TextFormat.chooseContentType(accept)).isEqualTo(expectedContentType); client.get() .uri("/actuator/prometheus") .accept(MediaType.parseMediaType(accept)) @@ -77,7 +79,7 @@ void scrapeHasContentTypeText004ByDefaultWhenClientAcceptsWildcardWithParameter( @WebEndpointTest void scrapeCanProduceOpenMetrics100(WebTestClient client) { - MediaType openMetrics = MediaType.parseMediaType(TextFormat.CONTENT_TYPE_OPENMETRICS_100); + MediaType openMetrics = MediaType.parseMediaType(OpenMetricsTextFormatWriter.CONTENT_TYPE); client.get() .uri("/actuator/prometheus") .accept(openMetrics) @@ -94,8 +96,8 @@ void scrapeCanProduceOpenMetrics100(WebTestClient client) { @WebEndpointTest void scrapePrefersToProduceOpenMetrics100(WebTestClient client) { - MediaType openMetrics = MediaType.parseMediaType(TextFormat.CONTENT_TYPE_OPENMETRICS_100); - MediaType textPlain = MediaType.parseMediaType(TextFormat.CONTENT_TYPE_004); + MediaType openMetrics = MediaType.parseMediaType(OpenMetricsTextFormatWriter.CONTENT_TYPE); + MediaType textPlain = MediaType.parseMediaType(PrometheusTextFormatWriter.CONTENT_TYPE); client.get() .uri("/actuator/prometheus") .accept(openMetrics, textPlain) @@ -109,34 +111,50 @@ void scrapePrefersToProduceOpenMetrics100(WebTestClient client) { @WebEndpointTest void scrapeWithIncludedNames(WebTestClient client) { client.get() - .uri("/actuator/prometheus?includedNames=counter1_total,counter2_total") + .uri("/actuator/prometheus?includedNames=counter1,counter2") .exchange() .expectStatus() .isOk() .expectHeader() - .contentType(MediaType.parseMediaType(TextFormat.CONTENT_TYPE_004)) + .contentType(MediaType.parseMediaType(PrometheusTextFormatWriter.CONTENT_TYPE)) .expectBody(String.class) .value((body) -> assertThat(body).contains("counter1_total") .contains("counter2_total") .doesNotContain("counter3_total")); } + @WebEndpointTest + void scrapeCanProducePrometheusProtobuf(WebTestClient client) { + MediaType prometheusProtobuf = MediaType.parseMediaType(PrometheusProtobufWriter.CONTENT_TYPE); + client.get() + .uri("/actuator/prometheus") + .accept(prometheusProtobuf) + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentType(prometheusProtobuf) + .expectBody(byte[].class) + .value((body) -> assertThat(body).isNotEmpty()); + } + @Configuration(proxyBeanMethods = false) static class TestConfiguration { @Bean - PrometheusScrapeEndpoint prometheusScrapeEndpoint(CollectorRegistry collectorRegistry) { - return new PrometheusScrapeEndpoint(collectorRegistry); + PrometheusScrapeEndpoint prometheusScrapeEndpoint(PrometheusRegistry prometheusRegistry) { + return new PrometheusScrapeEndpoint(prometheusRegistry, new Properties()); } @Bean - CollectorRegistry collectorRegistry() { - return new CollectorRegistry(true); + PrometheusRegistry prometheusRegistry() { + return new PrometheusRegistry(); } @Bean - MeterRegistry registry(CollectorRegistry registry) { - PrometheusMeterRegistry meterRegistry = new PrometheusMeterRegistry((k) -> null, registry, Clock.SYSTEM); + MeterRegistry registry(PrometheusRegistry prometheusRegistry) { + PrometheusMeterRegistry meterRegistry = new PrometheusMeterRegistry((k) -> null, prometheusRegistry, + Clock.SYSTEM); Counter.builder("counter1").register(meterRegistry); Counter.builder("counter2").register(meterRegistry); Counter.builder("counter3").register(meterRegistry); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusSimpleclientScrapeEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusSimpleclientScrapeEndpointIntegrationTests.java new file mode 100644 index 000000000000..94766f193e8c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusSimpleclientScrapeEndpointIntegrationTests.java @@ -0,0 +1,150 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.metrics.export.prometheus; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import io.prometheus.client.CollectorRegistry; +import io.prometheus.client.exporter.common.TextFormat; + +import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link PrometheusSimpleclientScrapeEndpoint}. + * + * @author Jon Schneider + * @author Johnny Lim + */ +@SuppressWarnings("removal") +class PrometheusSimpleclientScrapeEndpointIntegrationTests { + + @WebEndpointTest + void scrapeHasContentTypeText004ByDefault(WebTestClient client) { + String expectedContentType = TextFormat.CONTENT_TYPE_004; + assertThat(TextFormat.chooseContentType(null)).isEqualTo(expectedContentType); + client.get() + .uri("/actuator/prometheus") + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentType(MediaType.parseMediaType(expectedContentType)) + .expectBody(String.class) + .value((body) -> assertThat(body).contains("counter1_total") + .contains("counter2_total") + .contains("counter3_total")); + } + + @WebEndpointTest + void scrapeHasContentTypeText004ByDefaultWhenClientAcceptsWildcardWithParameter(WebTestClient client) { + String expectedContentType = TextFormat.CONTENT_TYPE_004; + String accept = "*/*;q=0.8"; + assertThat(TextFormat.chooseContentType(accept)).isEqualTo(expectedContentType); + client.get() + .uri("/actuator/prometheus") + .accept(MediaType.parseMediaType(accept)) + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentType(MediaType.parseMediaType(expectedContentType)) + .expectBody(String.class) + .value((body) -> assertThat(body).contains("counter1_total") + .contains("counter2_total") + .contains("counter3_total")); + } + + @WebEndpointTest + void scrapeCanProduceOpenMetrics100(WebTestClient client) { + MediaType openMetrics = MediaType.parseMediaType(TextFormat.CONTENT_TYPE_OPENMETRICS_100); + client.get() + .uri("/actuator/prometheus") + .accept(openMetrics) + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentType(openMetrics) + .expectBody(String.class) + .value((body) -> assertThat(body).contains("counter1_total") + .contains("counter2_total") + .contains("counter3_total")); + } + + @WebEndpointTest + void scrapePrefersToProduceOpenMetrics100(WebTestClient client) { + MediaType openMetrics = MediaType.parseMediaType(TextFormat.CONTENT_TYPE_OPENMETRICS_100); + MediaType textPlain = MediaType.parseMediaType(TextFormat.CONTENT_TYPE_004); + client.get() + .uri("/actuator/prometheus") + .accept(openMetrics, textPlain) + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentType(openMetrics); + } + + @WebEndpointTest + void scrapeWithIncludedNames(WebTestClient client) { + client.get() + .uri("/actuator/prometheus?includedNames=counter1_total,counter2_total") + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentType(MediaType.parseMediaType(TextFormat.CONTENT_TYPE_004)) + .expectBody(String.class) + .value((body) -> assertThat(body).contains("counter1_total") + .contains("counter2_total") + .doesNotContain("counter3_total")); + } + + @Configuration(proxyBeanMethods = false) + static class TestConfiguration { + + @Bean + PrometheusSimpleclientScrapeEndpoint prometheusScrapeEndpoint(CollectorRegistry collectorRegistry) { + return new PrometheusSimpleclientScrapeEndpoint(collectorRegistry); + } + + @Bean + CollectorRegistry collectorRegistry() { + return new CollectorRegistry(true); + } + + @Bean + @SuppressWarnings("deprecation") + MeterRegistry registry(CollectorRegistry registry) { + io.micrometer.prometheus.PrometheusMeterRegistry meterRegistry = new io.micrometer.prometheus.PrometheusMeterRegistry( + (k) -> null, registry, Clock.SYSTEM); + Counter.builder("counter1").register(meterRegistry); + Counter.builder("counter2").register(meterRegistry); + Counter.builder("counter3").register(meterRegistry); + return meterRegistry; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/SecondCustomPrometheusScrapeEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/SecondCustomPrometheusScrapeEndpointIntegrationTests.java new file mode 100644 index 000000000000..2f47b4378676 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/SecondCustomPrometheusScrapeEndpointIntegrationTests.java @@ -0,0 +1,210 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.metrics.export.prometheus; + +import java.util.Properties; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.composite.CompositeMeterRegistry; +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; +import io.prometheus.client.CollectorRegistry; +import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; +import io.prometheus.metrics.expositionformats.PrometheusTextFormatWriter; +import io.prometheus.metrics.model.registry.PrometheusRegistry; + +import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint; +import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for exposing a {@link PrometheusScrapeEndpoint} and + * {@link PrometheusSimpleclientScrapeEndpoint} with different IDs. + * + * @author Jon Schneider + * @author Johnny Lim + */ +class SecondCustomPrometheusScrapeEndpointIntegrationTests { + + @WebEndpointTest + void scrapeHasContentTypeText004ByDefault(WebTestClient client) { + scrapeHasContentTypeText004ByDefault(client, "/actuator/prometheus"); + scrapeHasContentTypeText004ByDefault(client, "/actuator/prometheussc"); + } + + private void scrapeHasContentTypeText004ByDefault(WebTestClient client, String uri) { + String expectedContentType = PrometheusTextFormatWriter.CONTENT_TYPE; + client.get() + .uri(uri) + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentType(MediaType.parseMediaType(expectedContentType)) + .expectBody(String.class) + .value((body) -> assertThat(body).contains("counter1_total") + .contains("counter2_total") + .contains("counter3_total")); + } + + @WebEndpointTest + void scrapeHasContentTypeText004ByDefaultWhenClientAcceptsWildcardWithParameter(WebTestClient client) { + scrapeHasContentTypeText004ByDefaultWhenClientAcceptsWildcardWithParameter(client, "/actuator/prometheus"); + scrapeHasContentTypeText004ByDefaultWhenClientAcceptsWildcardWithParameter(client, "/actuator/prometheussc"); + } + + private void scrapeHasContentTypeText004ByDefaultWhenClientAcceptsWildcardWithParameter(WebTestClient client, + String uri) { + String expectedContentType = PrometheusTextFormatWriter.CONTENT_TYPE; + String accept = "*/*;q=0.8"; + client.get() + .uri(uri) + .accept(MediaType.parseMediaType(accept)) + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentType(MediaType.parseMediaType(expectedContentType)) + .expectBody(String.class) + .value((body) -> assertThat(body).contains("counter1_total") + .contains("counter2_total") + .contains("counter3_total")); + } + + @WebEndpointTest + void scrapeCanProduceOpenMetrics100(WebTestClient client) { + scrapeCanProduceOpenMetrics100(client, "/actuator/prometheus"); + scrapeCanProduceOpenMetrics100(client, "/actuator/prometheussc"); + } + + private void scrapeCanProduceOpenMetrics100(WebTestClient client, String uri) { + MediaType openMetrics = MediaType.parseMediaType(OpenMetricsTextFormatWriter.CONTENT_TYPE); + client.get() + .uri(uri) + .accept(openMetrics) + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentType(openMetrics) + .expectBody(String.class) + .value((body) -> assertThat(body).contains("counter1_total") + .contains("counter2_total") + .contains("counter3_total")); + } + + @WebEndpointTest + void scrapePrefersToProduceOpenMetrics100(WebTestClient client) { + scrapePrefersToProduceOpenMetrics100(client, "/actuator/prometheus"); + scrapePrefersToProduceOpenMetrics100(client, "/actuator/prometheussc"); + } + + private void scrapePrefersToProduceOpenMetrics100(WebTestClient client, String uri) { + MediaType openMetrics = MediaType.parseMediaType(OpenMetricsTextFormatWriter.CONTENT_TYPE); + MediaType textPlain = MediaType.parseMediaType(PrometheusTextFormatWriter.CONTENT_TYPE); + client.get() + .uri(uri) + .accept(openMetrics, textPlain) + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentType(openMetrics); + } + + @WebEndpointTest + void scrapeWithIncludedNames(WebTestClient client) { + scrapeWithIncludedNames(client, "/actuator/prometheus?includedNames=counter1,counter2"); + scrapeWithIncludedNames(client, "/actuator/prometheussc?includedNames=counter1_total,counter2_total"); + } + + private void scrapeWithIncludedNames(WebTestClient client, String uri) { + client.get() + .uri(uri) + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentType(MediaType.parseMediaType(PrometheusTextFormatWriter.CONTENT_TYPE)) + .expectBody(String.class) + .value((body) -> assertThat(body).contains("counter1_total") + .contains("counter2_total") + .doesNotContain("counter3_total")); + } + + @SuppressWarnings({ "deprecation", "removal" }) + @Configuration(proxyBeanMethods = false) + static class TestConfiguration { + + @Bean + PrometheusScrapeEndpoint prometheusScrapeEndpoint(PrometheusRegistry prometheusRegistry) { + return new PrometheusScrapeEndpoint(prometheusRegistry, new Properties()); + } + + @Bean + CustomPrometheusScrapeEndpoint customPrometheusScrapeEndpoint(CollectorRegistry collectorRegistry) { + return new CustomPrometheusScrapeEndpoint(collectorRegistry); + } + + @Bean + PrometheusRegistry prometheusRegistry() { + return new PrometheusRegistry(); + } + + @Bean + CollectorRegistry collectorRegistry() { + return new CollectorRegistry(true); + } + + @Bean + PrometheusMeterRegistry registry(PrometheusRegistry prometheusRegistry) { + return new PrometheusMeterRegistry((k) -> null, prometheusRegistry, Clock.SYSTEM); + } + + @Bean + io.micrometer.prometheus.PrometheusMeterRegistry oldRegistry(CollectorRegistry collectorRegistry) { + return new io.micrometer.prometheus.PrometheusMeterRegistry((k) -> null, collectorRegistry, Clock.SYSTEM); + } + + @Bean + CompositeMeterRegistry compositeMeterRegistry(PrometheusMeterRegistry prometheusMeterRegistry, + io.micrometer.prometheus.PrometheusMeterRegistry prometheusSCMeterRegistry) { + CompositeMeterRegistry composite = new CompositeMeterRegistry(); + composite.add(prometheusMeterRegistry).add(prometheusSCMeterRegistry); + Counter.builder("counter1").register(composite); + Counter.builder("counter2").register(composite); + Counter.builder("counter3").register(composite); + return composite; + } + + @WebEndpoint(id = "prometheussc") + static class CustomPrometheusScrapeEndpoint extends PrometheusSimpleclientScrapeEndpoint { + + CustomPrometheusScrapeEndpoint(CollectorRegistry collectorRegistry) { + super(collectorRegistry); + } + + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/ObservationRestClientCustomizerTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/ObservationRestClientCustomizerTests.java new file mode 100644 index 000000000000..b945b4d7f596 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/ObservationRestClientCustomizerTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.metrics.web.client; + +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.tck.TestObservationRegistry; +import org.junit.jupiter.api.Test; + +import org.springframework.http.client.observation.DefaultClientRequestObservationConvention; +import org.springframework.web.client.RestClient; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ObservationRestClientCustomizer}. + * + * @author Brian Clozel + * @author Moritz Halbritter + */ +class ObservationRestClientCustomizerTests { + + private static final String TEST_METRIC_NAME = "http.test.metric.name"; + + private final ObservationRegistry observationRegistry = TestObservationRegistry.create(); + + private final RestClient.Builder restClientBuilder = RestClient.builder(); + + private final ObservationRestClientCustomizer customizer = new ObservationRestClientCustomizer( + this.observationRegistry, new DefaultClientRequestObservationConvention(TEST_METRIC_NAME)); + + @Test + void shouldCustomizeObservationConfiguration() { + this.customizer.customize(this.restClientBuilder); + assertThat(this.restClientBuilder).hasFieldOrPropertyWithValue("observationRegistry", this.observationRegistry); + assertThat(this.restClientBuilder).extracting("observationConvention") + .isInstanceOf(DefaultClientRequestObservationConvention.class) + .hasFieldOrPropertyWithValue("name", TEST_METRIC_NAME); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTagsTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTagsTests.java deleted file mode 100644 index 7dd9a2322b37..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTagsTests.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.client; - -import java.io.IOException; -import java.net.URI; - -import io.micrometer.core.instrument.Tag; -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.client.ClientHttpRequest; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.mock.http.client.MockClientHttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link RestTemplateExchangeTags}. - * - * @author Nishant Raut - * @author Brian Clozel - */ -@SuppressWarnings({ "removal" }) -@Deprecated(since = "3.0.0", forRemoval = true) -class RestTemplateExchangeTagsTests { - - @Test - void outcomeTagIsUnknownWhenResponseIsNull() { - Tag tag = RestTemplateExchangeTags.outcome(null); - assertThat(tag.getValue()).isEqualTo("UNKNOWN"); - } - - @Test - void outcomeTagIsInformationalWhenResponseIs1xx() { - ClientHttpResponse response = new MockClientHttpResponse("foo".getBytes(), HttpStatus.CONTINUE); - Tag tag = RestTemplateExchangeTags.outcome(response); - assertThat(tag.getValue()).isEqualTo("INFORMATIONAL"); - } - - @Test - void outcomeTagIsSuccessWhenResponseIs2xx() { - ClientHttpResponse response = new MockClientHttpResponse("foo".getBytes(), HttpStatus.OK); - Tag tag = RestTemplateExchangeTags.outcome(response); - assertThat(tag.getValue()).isEqualTo("SUCCESS"); - } - - @Test - void outcomeTagIsRedirectionWhenResponseIs3xx() { - ClientHttpResponse response = new MockClientHttpResponse("foo".getBytes(), HttpStatus.MOVED_PERMANENTLY); - Tag tag = RestTemplateExchangeTags.outcome(response); - assertThat(tag.getValue()).isEqualTo("REDIRECTION"); - } - - @Test - void outcomeTagIsClientErrorWhenResponseIs4xx() { - ClientHttpResponse response = new MockClientHttpResponse("foo".getBytes(), HttpStatus.BAD_REQUEST); - Tag tag = RestTemplateExchangeTags.outcome(response); - assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR"); - } - - @Test - void outcomeTagIsServerErrorWhenResponseIs5xx() { - ClientHttpResponse response = new MockClientHttpResponse("foo".getBytes(), HttpStatus.BAD_GATEWAY); - Tag tag = RestTemplateExchangeTags.outcome(response); - assertThat(tag.getValue()).isEqualTo("SERVER_ERROR"); - } - - @Test - void outcomeTagIsUnknownWhenResponseThrowsIOException() throws Exception { - ClientHttpResponse response = mock(ClientHttpResponse.class); - given(response.getStatusCode()).willThrow(IOException.class); - Tag tag = RestTemplateExchangeTags.outcome(response); - assertThat(tag.getValue()).isEqualTo("UNKNOWN"); - } - - @Test - void outcomeTagIsClientErrorWhenResponseIsNonStandardInClientSeries() throws IOException { - ClientHttpResponse response = mock(ClientHttpResponse.class); - given(response.getStatusCode()).willReturn(HttpStatusCode.valueOf(490)); - Tag tag = RestTemplateExchangeTags.outcome(response); - assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR"); - } - - @Test - void outcomeTagIsUnknownWhenResponseStatusIsInUnknownSeries() throws IOException { - ClientHttpResponse response = mock(ClientHttpResponse.class); - given(response.getStatusCode()).willReturn(HttpStatusCode.valueOf(701)); - Tag tag = RestTemplateExchangeTags.outcome(response); - assertThat(tag.getValue()).isEqualTo("UNKNOWN"); - } - - @Test - void clientNameTagIsHostOfRequestUri() { - ClientHttpRequest request = mock(ClientHttpRequest.class); - given(request.getURI()).willReturn(URI.create("https://example.org")); - Tag tag = RestTemplateExchangeTags.clientName(request); - assertThat(tag).isEqualTo(Tag.of("client.name", "example.org")); - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProviderTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProviderTests.java deleted file mode 100644 index c04846e71696..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProviderTests.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.reactive.client; - -import java.io.IOException; -import java.net.URI; - -import io.micrometer.core.instrument.Tag; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.web.reactive.function.client.ClientRequest; -import org.springframework.web.reactive.function.client.ClientResponse; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link DefaultWebClientExchangeTagsProvider} - * - * @author Brian Clozel - * @author Nishant Raut - */ -@SuppressWarnings({ "deprecation", "removal" }) -class DefaultWebClientExchangeTagsProviderTests { - - private static final String URI_TEMPLATE_ATTRIBUTE = WebClient.class.getName() + ".uriTemplate"; - - private final WebClientExchangeTagsProvider tagsProvider = new DefaultWebClientExchangeTagsProvider(); - - private ClientRequest request; - - private ClientResponse response; - - @BeforeEach - void setup() { - this.request = ClientRequest.create(HttpMethod.GET, URI.create("https://example.org/projects/spring-boot")) - .attribute(URI_TEMPLATE_ATTRIBUTE, "https://example.org/projects/{project}") - .build(); - this.response = mock(ClientResponse.class); - given(this.response.statusCode()).willReturn(HttpStatus.OK); - } - - @Test - void tagsShouldBePopulated() { - Iterable tags = this.tagsProvider.tags(this.request, this.response, null); - assertThat(tags).containsExactlyInAnyOrder(Tag.of("method", "GET"), Tag.of("uri", "/projects/{project}"), - Tag.of("client.name", "example.org"), Tag.of("status", "200"), Tag.of("outcome", "SUCCESS")); - } - - @Test - void tagsWhenNoUriTemplateShouldProvideUriPath() { - ClientRequest request = ClientRequest - .create(HttpMethod.GET, URI.create("https://example.org/projects/spring-boot")) - .build(); - Iterable tags = this.tagsProvider.tags(request, this.response, null); - assertThat(tags).containsExactlyInAnyOrder(Tag.of("method", "GET"), Tag.of("uri", "/projects/spring-boot"), - Tag.of("client.name", "example.org"), Tag.of("status", "200"), Tag.of("outcome", "SUCCESS")); - } - - @Test - void tagsWhenIoExceptionShouldReturnIoErrorStatus() { - Iterable tags = this.tagsProvider.tags(this.request, null, new IOException()); - assertThat(tags).containsExactlyInAnyOrder(Tag.of("method", "GET"), Tag.of("uri", "/projects/{project}"), - Tag.of("client.name", "example.org"), Tag.of("status", "IO_ERROR"), Tag.of("outcome", "UNKNOWN")); - } - - @Test - void tagsWhenExceptionShouldReturnClientErrorStatus() { - Iterable tags = this.tagsProvider.tags(this.request, null, new IllegalArgumentException()); - assertThat(tags).containsExactlyInAnyOrder(Tag.of("method", "GET"), Tag.of("uri", "/projects/{project}"), - Tag.of("client.name", "example.org"), Tag.of("status", "CLIENT_ERROR"), Tag.of("outcome", "UNKNOWN")); - } - - @Test - void tagsWhenCancelledRequestShouldReturnClientErrorStatus() { - Iterable tags = this.tagsProvider.tags(this.request, null, null); - assertThat(tags).containsExactlyInAnyOrder(Tag.of("method", "GET"), Tag.of("uri", "/projects/{project}"), - Tag.of("client.name", "example.org"), Tag.of("status", "CLIENT_ERROR"), Tag.of("outcome", "UNKNOWN")); - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTagsTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTagsTests.java deleted file mode 100644 index fbc3860fb4bc..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTagsTests.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.reactive.client; - -import java.io.IOException; -import java.net.URI; - -import io.micrometer.core.instrument.Tag; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.web.reactive.function.client.ClientRequest; -import org.springframework.web.reactive.function.client.ClientResponse; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link WebClientExchangeTags}. - * - * @author Brian Clozel - * @author Nishant Raut - */ -@SuppressWarnings({ "deprecation", "removal" }) -class WebClientExchangeTagsTests { - - private static final String URI_TEMPLATE_ATTRIBUTE = WebClient.class.getName() + ".uriTemplate"; - - private ClientRequest request; - - private ClientResponse response; - - @BeforeEach - void setup() { - this.request = ClientRequest.create(HttpMethod.GET, URI.create("https://example.org/projects/spring-boot")) - .attribute(URI_TEMPLATE_ATTRIBUTE, "https://example.org/projects/{project}") - .build(); - this.response = mock(ClientResponse.class); - } - - @Test - void method() { - assertThat(WebClientExchangeTags.method(this.request)).isEqualTo(Tag.of("method", "GET")); - } - - @Test - void uriWhenAbsoluteTemplateIsAvailableShouldReturnTemplate() { - assertThat(WebClientExchangeTags.uri(this.request)).isEqualTo(Tag.of("uri", "/projects/{project}")); - } - - @Test - void uriWhenRelativeTemplateIsAvailableShouldReturnTemplate() { - this.request = ClientRequest.create(HttpMethod.GET, URI.create("https://example.org/projects/spring-boot")) - .attribute(URI_TEMPLATE_ATTRIBUTE, "/projects/{project}") - .build(); - assertThat(WebClientExchangeTags.uri(this.request)).isEqualTo(Tag.of("uri", "/projects/{project}")); - } - - @Test - void uriWhenTemplateIsMissingShouldReturnPath() { - this.request = ClientRequest.create(HttpMethod.GET, URI.create("https://example.org/projects/spring-boot")) - .build(); - assertThat(WebClientExchangeTags.uri(this.request)).isEqualTo(Tag.of("uri", "/projects/spring-boot")); - } - - @Test - void uriWhenTemplateIsMissingShouldReturnPathWithQueryParams() { - this.request = ClientRequest - .create(HttpMethod.GET, URI.create("https://example.org/projects/spring-boot?section=docs")) - .build(); - assertThat(WebClientExchangeTags.uri(this.request)) - .isEqualTo(Tag.of("uri", "/projects/spring-boot?section=docs")); - } - - @Test - void clientName() { - assertThat(WebClientExchangeTags.clientName(this.request)).isEqualTo(Tag.of("client.name", "example.org")); - } - - @Test - void status() { - given(this.response.statusCode()).willReturn(HttpStatus.OK); - assertThat(WebClientExchangeTags.status(this.response, null)).isEqualTo(Tag.of("status", "200")); - } - - @Test - void statusWhenIOException() { - assertThat(WebClientExchangeTags.status(null, new IOException())).isEqualTo(Tag.of("status", "IO_ERROR")); - } - - @Test - void statusWhenClientException() { - assertThat(WebClientExchangeTags.status(null, new IllegalArgumentException())) - .isEqualTo(Tag.of("status", "CLIENT_ERROR")); - } - - @Test - void statusWhenNonStandard() { - given(this.response.statusCode()).willReturn(HttpStatusCode.valueOf(490)); - assertThat(WebClientExchangeTags.status(this.response, null)).isEqualTo(Tag.of("status", "490")); - } - - @Test - void statusWhenCancelled() { - assertThat(WebClientExchangeTags.status(null, null)).isEqualTo(Tag.of("status", "CLIENT_ERROR")); - } - - @Test - void outcomeTagIsUnknownWhenResponseIsNull() { - Tag tag = WebClientExchangeTags.outcome(null); - assertThat(tag.getValue()).isEqualTo("UNKNOWN"); - } - - @Test - void outcomeTagIsInformationalWhenResponseIs1xx() { - given(this.response.statusCode()).willReturn(HttpStatus.CONTINUE); - Tag tag = WebClientExchangeTags.outcome(this.response); - assertThat(tag.getValue()).isEqualTo("INFORMATIONAL"); - } - - @Test - void outcomeTagIsSuccessWhenResponseIs2xx() { - given(this.response.statusCode()).willReturn(HttpStatus.OK); - Tag tag = WebClientExchangeTags.outcome(this.response); - assertThat(tag.getValue()).isEqualTo("SUCCESS"); - } - - @Test - void outcomeTagIsRedirectionWhenResponseIs3xx() { - given(this.response.statusCode()).willReturn(HttpStatus.MOVED_PERMANENTLY); - Tag tag = WebClientExchangeTags.outcome(this.response); - assertThat(tag.getValue()).isEqualTo("REDIRECTION"); - } - - @Test - void outcomeTagIsClientErrorWhenResponseIs4xx() { - given(this.response.statusCode()).willReturn(HttpStatus.BAD_REQUEST); - Tag tag = WebClientExchangeTags.outcome(this.response); - assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR"); - } - - @Test - void outcomeTagIsServerErrorWhenResponseIs5xx() { - given(this.response.statusCode()).willReturn(HttpStatus.BAD_GATEWAY); - Tag tag = WebClientExchangeTags.outcome(this.response); - assertThat(tag.getValue()).isEqualTo("SERVER_ERROR"); - } - - @Test - void outcomeTagIsClientErrorWhenResponseIsNonStandardInClientSeries() { - given(this.response.statusCode()).willReturn(HttpStatusCode.valueOf(490)); - Tag tag = WebClientExchangeTags.outcome(this.response); - assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR"); - } - - @Test - void outcomeTagIsUnknownWhenResponseStatusIsInUnknownSeries() { - given(this.response.statusCode()).willReturn(HttpStatusCode.valueOf(701)); - Tag tag = WebClientExchangeTags.outcome(this.response); - assertThat(tag.getValue()).isEqualTo("UNKNOWN"); - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProviderTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProviderTests.java deleted file mode 100644 index 39240b2fd341..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProviderTests.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.reactive.server; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -import io.micrometer.core.instrument.Tag; -import org.junit.jupiter.api.Test; - -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link DefaultWebFluxTagsProvider}. - * - * @author Andy Wilkinson - */ -@SuppressWarnings("removal") -@Deprecated(since = "3.0.0", forRemoval = true) -class DefaultWebFluxTagsProviderTests { - - @Test - void whenTagsAreProvidedThenDefaultTagsArePresent() { - ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test")); - Map tags = asMap(new DefaultWebFluxTagsProvider().httpRequestTags(exchange, null)); - assertThat(tags).containsOnlyKeys("exception", "method", "outcome", "status", "uri"); - } - - @Test - void givenSomeContributorsWhenTagsAreProvidedThenDefaultTagsAndContributedTagsArePresent() { - ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test")); - Map tags = asMap( - new DefaultWebFluxTagsProvider(Arrays.asList(new TestWebFluxTagsContributor("alpha"), - new TestWebFluxTagsContributor("bravo", "charlie"))) - .httpRequestTags(exchange, null)); - assertThat(tags).containsOnlyKeys("exception", "method", "outcome", "status", "uri", "alpha", "bravo", - "charlie"); - } - - private Map asMap(Iterable tags) { - return StreamSupport.stream(tags.spliterator(), false) - .collect(Collectors.toMap(Tag::getKey, Function.identity())); - } - - private static final class TestWebFluxTagsContributor implements WebFluxTagsContributor { - - private final List tagNames; - - private TestWebFluxTagsContributor(String... tagNames) { - this.tagNames = Arrays.asList(tagNames); - } - - @Override - public Iterable httpRequestTags(ServerWebExchange exchange, Throwable ex) { - return this.tagNames.stream().map((name) -> Tag.of(name, "value")).toList(); - } - - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsTests.java deleted file mode 100644 index 03d7a1030258..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsTests.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.reactive.server; - -import java.io.EOFException; - -import io.micrometer.core.instrument.Tag; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.web.reactive.HandlerMapping; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.pattern.PathPatternParser; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link WebFluxTags}. - * - * @author Brian Clozel - * @author Michael McFadyen - * @author Madhura Bhave - * @author Stephane Nicoll - */ -@SuppressWarnings("removal") -@Deprecated(since = "3.0.0", forRemoval = true) -class WebFluxTagsTests { - - private MockServerWebExchange exchange; - - private final PathPatternParser parser = new PathPatternParser(); - - @BeforeEach - void setup() { - this.exchange = MockServerWebExchange.from(MockServerHttpRequest.get("")); - } - - @Test - void uriTagValueIsBestMatchingPatternWhenAvailable() { - this.exchange.getAttributes() - .put(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, this.parser.parse("/spring/")); - this.exchange.getResponse().setStatusCode(HttpStatus.MOVED_PERMANENTLY); - Tag tag = WebFluxTags.uri(this.exchange); - assertThat(tag.getValue()).isEqualTo("/spring/"); - } - - @Test - void uriTagValueIsRootWhenBestMatchingPatternIsEmpty() { - this.exchange.getAttributes().put(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, this.parser.parse("")); - this.exchange.getResponse().setStatusCode(HttpStatus.MOVED_PERMANENTLY); - Tag tag = WebFluxTags.uri(this.exchange); - assertThat(tag.getValue()).isEqualTo("root"); - } - - @Test - void uriTagValueWithBestMatchingPatternAndIgnoreTrailingSlashRemoveTrailingSlash() { - this.exchange.getAttributes() - .put(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, this.parser.parse("/spring/")); - Tag tag = WebFluxTags.uri(this.exchange, true); - assertThat(tag.getValue()).isEqualTo("/spring"); - } - - @Test - void uriTagValueWithBestMatchingPatternAndIgnoreTrailingSlashKeepSingleSlash() { - this.exchange.getAttributes().put(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, this.parser.parse("/")); - Tag tag = WebFluxTags.uri(this.exchange, true); - assertThat(tag.getValue()).isEqualTo("/"); - } - - @Test - void uriTagValueIsRedirectionWhenResponseStatusIs3xx() { - this.exchange.getResponse().setStatusCode(HttpStatus.MOVED_PERMANENTLY); - Tag tag = WebFluxTags.uri(this.exchange); - assertThat(tag.getValue()).isEqualTo("REDIRECTION"); - } - - @Test - void uriTagValueIsNotFoundWhenResponseStatusIs404() { - this.exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND); - Tag tag = WebFluxTags.uri(this.exchange); - assertThat(tag.getValue()).isEqualTo("NOT_FOUND"); - } - - @Test - void uriTagToleratesCustomResponseStatus() { - this.exchange.getResponse().setRawStatusCode(601); - Tag tag = WebFluxTags.uri(this.exchange); - assertThat(tag.getValue()).isEqualTo("root"); - } - - @Test - void uriTagValueIsRootWhenRequestHasNoPatternOrPathInfo() { - Tag tag = WebFluxTags.uri(this.exchange); - assertThat(tag.getValue()).isEqualTo("root"); - } - - @Test - void uriTagValueIsRootWhenRequestHasNoPatternAndSlashPathInfo() { - MockServerHttpRequest request = MockServerHttpRequest.get("/").build(); - ServerWebExchange exchange = MockServerWebExchange.from(request); - Tag tag = WebFluxTags.uri(exchange); - assertThat(tag.getValue()).isEqualTo("root"); - } - - @Test - void uriTagValueIsUnknownWhenRequestHasNoPatternAndNonRootPathInfo() { - MockServerHttpRequest request = MockServerHttpRequest.get("/example").build(); - ServerWebExchange exchange = MockServerWebExchange.from(request); - Tag tag = WebFluxTags.uri(exchange); - assertThat(tag.getValue()).isEqualTo("UNKNOWN"); - } - - @Test - void methodTagToleratesNonStandardHttpMethods() { - ServerWebExchange exchange = mock(ServerWebExchange.class); - ServerHttpRequest request = mock(ServerHttpRequest.class); - given(exchange.getRequest()).willReturn(request); - given(request.getMethod()).willReturn(HttpMethod.valueOf("CUSTOM")); - Tag tag = WebFluxTags.method(exchange); - assertThat(tag.getValue()).isEqualTo("CUSTOM"); - } - - @Test - void outcomeTagIsSuccessWhenResponseStatusIsNull() { - this.exchange.getResponse().setStatusCode(null); - Tag tag = WebFluxTags.outcome(this.exchange, null); - assertThat(tag.getValue()).isEqualTo("SUCCESS"); - } - - @Test - void outcomeTagIsSuccessWhenResponseStatusIsAvailableFromUnderlyingServer() { - ServerWebExchange exchange = mock(ServerWebExchange.class); - ServerHttpRequest request = mock(ServerHttpRequest.class); - ServerHttpResponse response = mock(ServerHttpResponse.class); - given(response.getStatusCode()).willReturn(HttpStatus.OK); - given(response.getStatusCode().value()).willReturn(null); - given(exchange.getRequest()).willReturn(request); - given(exchange.getResponse()).willReturn(response); - Tag tag = WebFluxTags.outcome(exchange, null); - assertThat(tag.getValue()).isEqualTo("SUCCESS"); - } - - @Test - void outcomeTagIsInformationalWhenResponseIs1xx() { - this.exchange.getResponse().setStatusCode(HttpStatus.CONTINUE); - Tag tag = WebFluxTags.outcome(this.exchange, null); - assertThat(tag.getValue()).isEqualTo("INFORMATIONAL"); - } - - @Test - void outcomeTagIsSuccessWhenResponseIs2xx() { - this.exchange.getResponse().setStatusCode(HttpStatus.OK); - Tag tag = WebFluxTags.outcome(this.exchange, null); - assertThat(tag.getValue()).isEqualTo("SUCCESS"); - } - - @Test - void outcomeTagIsRedirectionWhenResponseIs3xx() { - this.exchange.getResponse().setStatusCode(HttpStatus.MOVED_PERMANENTLY); - Tag tag = WebFluxTags.outcome(this.exchange, null); - assertThat(tag.getValue()).isEqualTo("REDIRECTION"); - } - - @Test - void outcomeTagIsClientErrorWhenResponseIs4xx() { - this.exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST); - Tag tag = WebFluxTags.outcome(this.exchange, null); - assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR"); - } - - @Test - void outcomeTagIsServerErrorWhenResponseIs5xx() { - this.exchange.getResponse().setStatusCode(HttpStatus.BAD_GATEWAY); - Tag tag = WebFluxTags.outcome(this.exchange, null); - assertThat(tag.getValue()).isEqualTo("SERVER_ERROR"); - } - - @Test - void outcomeTagIsClientErrorWhenResponseIsNonStandardInClientSeries() { - this.exchange.getResponse().setRawStatusCode(490); - Tag tag = WebFluxTags.outcome(this.exchange, null); - assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR"); - } - - @Test - void outcomeTagIsUnknownWhenResponseStatusIsInUnknownSeries() { - this.exchange.getResponse().setRawStatusCode(701); - Tag tag = WebFluxTags.outcome(this.exchange, null); - assertThat(tag.getValue()).isEqualTo("UNKNOWN"); - } - - @Test - void outcomeTagIsUnknownWhenExceptionIsDisconnectedClient() { - Tag tag = WebFluxTags.outcome(this.exchange, new EOFException("broken pipe")); - assertThat(tag.getValue()).isEqualTo("UNKNOWN"); - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/mongo/MongoHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/mongo/MongoHealthIndicatorTests.java index 8999c6929842..d4b4d403c659 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/mongo/MongoHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/mongo/MongoHealthIndicatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,24 +42,24 @@ void mongoIsUp() { Document commandResult = mock(Document.class); given(commandResult.getInteger("maxWireVersion")).willReturn(10); MongoTemplate mongoTemplate = mock(MongoTemplate.class); - given(mongoTemplate.executeCommand("{ isMaster: 1 }")).willReturn(commandResult); + given(mongoTemplate.executeCommand("{ hello: 1 }")).willReturn(commandResult); MongoHealthIndicator healthIndicator = new MongoHealthIndicator(mongoTemplate); Health health = healthIndicator.health(); assertThat(health.getStatus()).isEqualTo(Status.UP); assertThat(health.getDetails()).containsEntry("maxWireVersion", 10); then(commandResult).should().getInteger("maxWireVersion"); - then(mongoTemplate).should().executeCommand("{ isMaster: 1 }"); + then(mongoTemplate).should().executeCommand("{ hello: 1 }"); } @Test void mongoIsDown() { MongoTemplate mongoTemplate = mock(MongoTemplate.class); - given(mongoTemplate.executeCommand("{ isMaster: 1 }")).willThrow(new MongoException("Connection failed")); + given(mongoTemplate.executeCommand("{ hello: 1 }")).willThrow(new MongoException("Connection failed")); MongoHealthIndicator healthIndicator = new MongoHealthIndicator(mongoTemplate); Health health = healthIndicator.health(); assertThat(health.getStatus()).isEqualTo(Status.DOWN); assertThat((String) health.getDetails().get("error")).contains("Connection failed"); - then(mongoTemplate).should().executeCommand("{ isMaster: 1 }"); + then(mongoTemplate).should().executeCommand("{ hello: 1 }"); } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/mongo/MongoReactiveHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/mongo/MongoReactiveHealthIndicatorTests.java index 91a2ae9615f2..2b942028dfe4 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/mongo/MongoReactiveHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/mongo/MongoReactiveHealthIndicatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,7 +45,7 @@ void testMongoIsUp() { Document buildInfo = mock(Document.class); given(buildInfo.getInteger("maxWireVersion")).willReturn(10); ReactiveMongoTemplate reactiveMongoTemplate = mock(ReactiveMongoTemplate.class); - given(reactiveMongoTemplate.executeCommand("{ isMaster: 1 }")).willReturn(Mono.just(buildInfo)); + given(reactiveMongoTemplate.executeCommand("{ hello: 1 }")).willReturn(Mono.just(buildInfo)); MongoReactiveHealthIndicator mongoReactiveHealthIndicator = new MongoReactiveHealthIndicator( reactiveMongoTemplate); Mono health = mongoReactiveHealthIndicator.health(); @@ -59,8 +59,7 @@ void testMongoIsUp() { @Test void testMongoIsDown() { ReactiveMongoTemplate reactiveMongoTemplate = mock(ReactiveMongoTemplate.class); - given(reactiveMongoTemplate.executeCommand("{ isMaster: 1 }")) - .willThrow(new MongoException("Connection failed")); + given(reactiveMongoTemplate.executeCommand("{ hello: 1 }")).willThrow(new MongoException("Connection failed")); MongoReactiveHealthIndicator mongoReactiveHealthIndicator = new MongoReactiveHealthIndicator( reactiveMongoTemplate); Mono health = mongoReactiveHealthIndicator.health(); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointCycloneDxWebIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointCycloneDxWebIntegrationTests.java new file mode 100644 index 000000000000..85e5b12f1993 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointCycloneDxWebIntegrationTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.sbom; + +import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ResourceLoader; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * Integration tests for {@link SbomEndpoint} exposed by Jersey, Spring MVC, and WebFlux + * in CycloneDX format. + * + * @author Moritz Halbritter + */ +class SbomEndpointCycloneDxWebIntegrationTests { + + @WebEndpointTest + void shouldReturnSbomContent(WebTestClient client) { + client.get() + .uri("/actuator/sbom/application") + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentType(MediaType.parseMediaType("application/vnd.cyclonedx+json")) + .expectBody() + .jsonPath("$.bomFormat") + .isEqualTo("CycloneDX"); + } + + @Configuration(proxyBeanMethods = false) + static class TestConfiguration { + + @Bean + SbomProperties sbomProperties() { + SbomProperties properties = new SbomProperties(); + properties.getApplication().setLocation("classpath:sbom/cyclonedx.json"); + return properties; + } + + @Bean + SbomEndpoint sbomEndpoint(SbomProperties properties, ResourceLoader resourceLoader) { + return new SbomEndpoint(properties, resourceLoader); + } + + @Bean + SbomEndpointWebExtension sbomEndpointWebExtension(SbomEndpoint sbomEndpoint, SbomProperties properties) { + return new SbomEndpointWebExtension(sbomEndpoint, properties); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointSpdxWebIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointSpdxWebIntegrationTests.java new file mode 100644 index 000000000000..264e8cd05df5 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointSpdxWebIntegrationTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.sbom; + +import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ResourceLoader; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * Integration tests for {@link SbomEndpoint} exposed by Jersey, Spring MVC, and WebFlux + * in SPDX format. + * + * @author Moritz Halbritter + */ +class SbomEndpointSpdxWebIntegrationTests { + + @WebEndpointTest + void shouldReturnSbomContent(WebTestClient client) { + client.get() + .uri("/actuator/sbom/application") + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentType(MediaType.parseMediaType("application/spdx+json")) + .expectBody() + .jsonPath("$.spdxVersion") + .isEqualTo("SPDX-2.3"); + } + + @Configuration(proxyBeanMethods = false) + static class TestConfiguration { + + @Bean + SbomProperties sbomProperties() { + SbomProperties properties = new SbomProperties(); + properties.getApplication().setLocation("classpath:sbom/spdx.json"); + return properties; + } + + @Bean + SbomEndpoint sbomEndpoint(SbomProperties properties, ResourceLoader resourceLoader) { + return new SbomEndpoint(properties, resourceLoader); + } + + @Bean + SbomEndpointWebExtension sbomEndpointWebExtension(SbomEndpoint sbomEndpoint, SbomProperties properties) { + return new SbomEndpointWebExtension(sbomEndpoint, properties); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointSyftWebIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointSyftWebIntegrationTests.java new file mode 100644 index 000000000000..0c6469322d18 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointSyftWebIntegrationTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.sbom; + +import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ResourceLoader; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * Integration tests for {@link SbomEndpoint} exposed by Jersey, Spring MVC, and WebFlux + * in Syft format. + * + * @author Moritz Halbritter + */ +class SbomEndpointSyftWebIntegrationTests { + + @WebEndpointTest + void shouldReturnSbomContent(WebTestClient client) { + client.get() + .uri("/actuator/sbom/application") + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentType(MediaType.parseMediaType("application/vnd.syft+json")) + .expectBody() + .jsonPath("$.descriptor.name") + .isEqualTo("syft"); + } + + @Configuration(proxyBeanMethods = false) + static class TestConfiguration { + + @Bean + SbomProperties sbomProperties() { + SbomProperties properties = new SbomProperties(); + properties.getApplication().setLocation("classpath:sbom/syft.json"); + return properties; + } + + @Bean + SbomEndpoint sbomEndpoint(SbomProperties properties, ResourceLoader resourceLoader) { + return new SbomEndpoint(properties, resourceLoader); + } + + @Bean + SbomEndpointWebExtension sbomEndpointWebExtension(SbomEndpoint sbomEndpoint, SbomProperties properties) { + return new SbomEndpointWebExtension(sbomEndpoint, properties); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointTests.java new file mode 100644 index 000000000000..f4c724d69031 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.sbom; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.sbom.SbomEndpoint.SbomEndpointRuntimeHints; +import org.springframework.boot.actuate.sbom.SbomEndpoint.Sboms; +import org.springframework.boot.actuate.sbom.SbomProperties.Sbom; +import org.springframework.context.support.GenericApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link SbomEndpoint}. + * + * @author Moritz Halbritter + */ +class SbomEndpointTests { + + private SbomProperties properties; + + @BeforeEach + void setUp() { + this.properties = new SbomProperties(); + } + + @Test + void shouldListSboms() { + this.properties.getApplication().setLocation("classpath:sbom/cyclonedx.json"); + this.properties.getAdditional().put("alpha", sbom("classpath:sbom/cyclonedx.json")); + this.properties.getAdditional().put("beta", sbom("classpath:sbom/cyclonedx.json")); + Sboms sboms = createEndpoint().sboms(); + assertThat(sboms.ids()).containsExactly("alpha", "application", "beta"); + } + + @Test + void shouldFailIfDuplicateSbomIdIsRegistered() { + // This adds an SBOM with id 'application' + this.properties.getApplication().setLocation("classpath:sbom/cyclonedx.json"); + this.properties.getAdditional().put("application", sbom("classpath:sbom/cyclonedx.json")); + assertThatIllegalStateException().isThrownBy(this::createEndpoint) + .withMessage("Duplicate SBOM registration with id 'application'"); + } + + @Test + void shouldUseLocationFromProperties() throws IOException { + this.properties.getApplication().setLocation("classpath:sbom/cyclonedx.json"); + String content = createEndpoint().sbom("application").getContentAsString(StandardCharsets.UTF_8); + assertThat(content).contains("\"bomFormat\" : \"CycloneDX\""); + } + + @Test + void shouldFailIfNonExistingLocationIsGiven() { + this.properties.getApplication().setLocation("classpath:does-not-exist.json"); + assertThatIllegalStateException().isThrownBy(() -> createEndpoint().sbom("application")) + .withMessageContaining("Resource 'classpath:does-not-exist.json' doesn't exist"); + } + + @Test + void shouldNotFailIfNonExistingOptionalLocationIsGiven() { + this.properties.getApplication().setLocation("optional:classpath:does-not-exist.json"); + assertThat(createEndpoint().sbom("application")).isNull(); + } + + @Test + void shouldRegisterHints() { + RuntimeHints hints = new RuntimeHints(); + new SbomEndpointRuntimeHints().registerHints(hints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.resource().forResource("META-INF/sbom/bom.json")).accepts(hints); + assertThat(RuntimeHintsPredicates.resource().forResource("META-INF/sbom/application.cdx.json")).accepts(hints); + } + + private Sbom sbom(String location) { + Sbom result = new Sbom(); + result.setLocation(location); + return result; + } + + private SbomEndpoint createEndpoint() { + return new SbomEndpoint(this.properties, new GenericApplicationContext()); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointWebExtensionTests.java new file mode 100644 index 000000000000..95831aacfdc1 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointWebExtensionTests.java @@ -0,0 +1,149 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.sbom; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; + +import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; +import org.springframework.boot.actuate.sbom.SbomEndpointWebExtension.SbomType; +import org.springframework.boot.actuate.sbom.SbomProperties.Sbom; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.util.MimeType; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SbomEndpointWebExtension}. + * + * @author Moritz Halbritter + */ +class SbomEndpointWebExtensionTests { + + private SbomProperties properties; + + @BeforeEach + void setUp() { + this.properties = new SbomProperties(); + } + + @Test + void shouldReturnHttpOk() { + this.properties.getApplication().setLocation("classpath:sbom/cyclonedx.json"); + WebEndpointResponse response = createWebExtension().sbom("application"); + assertThat(response.getStatus()).isEqualTo(200); + } + + @Test + void shouldReturnNotFoundIfResourceDoesntExist() { + WebEndpointResponse response = createWebExtension().sbom("application"); + assertThat(response.getStatus()).isEqualTo(404); + } + + @Test + void shouldAutoDetectContentTypeForCycloneDx() { + this.properties.getApplication().setLocation("classpath:sbom/cyclonedx.json"); + WebEndpointResponse response = createWebExtension().sbom("application"); + assertThat(response.getContentType()).isEqualTo(MimeType.valueOf("application/vnd.cyclonedx+json")); + } + + @Test + void shouldAutoDetectContentTypeForSpdx() { + this.properties.getApplication().setLocation("classpath:sbom/spdx.json"); + WebEndpointResponse response = createWebExtension().sbom("application"); + assertThat(response.getContentType()).isEqualTo(MimeType.valueOf("application/spdx+json")); + } + + @Test + void shouldAutoDetectContentTypeForSyft() { + this.properties.getApplication().setLocation("classpath:sbom/syft.json"); + WebEndpointResponse response = createWebExtension().sbom("application"); + assertThat(response.getContentType()).isEqualTo(MimeType.valueOf("application/vnd.syft+json")); + } + + @Test + void shouldSupportUnknownFiles() { + this.properties.getApplication().setLocation("classpath:git.properties"); + WebEndpointResponse response = createWebExtension().sbom("application"); + assertThat(response.getContentType()).isNull(); + } + + @Test + void shouldUseContentTypeIfSet() { + this.properties.getApplication().setLocation("classpath:sbom/cyclonedx.json"); + this.properties.getApplication().setMediaType(MimeType.valueOf("text/plain")); + WebEndpointResponse response = createWebExtension().sbom("application"); + assertThat(response.getContentType()).isEqualTo(MimeType.valueOf("text/plain")); + } + + @Test + void shouldUseContentTypeForAdditionalSbomsIfSet() { + this.properties.getAdditional() + .put("alpha", sbom("classpath:sbom/cyclonedx.json", MediaType.valueOf("text/plain"))); + WebEndpointResponse response = createWebExtension().sbom("alpha"); + assertThat(response.getContentType()).isEqualTo(MimeType.valueOf("text/plain")); + } + + @ParameterizedTest + @EnumSource(value = SbomType.class, names = "UNKNOWN", mode = Mode.EXCLUDE) + void shouldAutodetectFormats(SbomType type) throws IOException { + String content = getSbomContent(type); + assertThat(type.matches(content)).isTrue(); + Arrays.stream(SbomType.values()) + .filter((candidate) -> candidate != type) + .forEach((notType) -> assertThat(notType.matches(content)).isFalse()); + } + + private String getSbomContent(SbomType type) throws IOException { + return switch (type) { + case CYCLONE_DX -> readResource("/sbom/cyclonedx.json"); + case SPDX -> readResource("/sbom/spdx.json"); + case SYFT -> readResource("/sbom/syft.json"); + case UNKNOWN -> throw new IllegalArgumentException("UNKNOWN is not supported"); + }; + } + + private String readResource(String resource) throws IOException { + try (InputStream stream = getClass().getResourceAsStream(resource)) { + assertThat(stream).as("Resource '%s'", resource).isNotNull(); + return new String(stream.readAllBytes(), StandardCharsets.UTF_8); + } + } + + private Sbom sbom(String location, MimeType mediaType) { + Sbom sbom = new Sbom(); + sbom.setLocation(location); + sbom.setMediaType(mediaType); + return sbom; + } + + private SbomEndpointWebExtension createWebExtension() { + SbomEndpoint endpoint = new SbomEndpoint(this.properties, new GenericApplicationContext()); + return new SbomEndpointWebExtension(endpoint, this.properties); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointWebIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointWebIntegrationTests.java new file mode 100644 index 000000000000..77b4ecdfc642 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointWebIntegrationTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.sbom; + +import net.minidev.json.JSONArray; + +import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ResourceLoader; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link SbomEndpoint} exposed by Jersey, Spring MVC, and WebFlux. + * + * @author Moritz Halbritter + */ +class SbomEndpointWebIntegrationTests { + + @WebEndpointTest + void shouldReturnSboms(WebTestClient client) { + client.get() + .uri("/actuator/sbom") + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentType(MediaType.parseMediaType("application/vnd.spring-boot.actuator.v3+json")) + .expectBody() + .jsonPath("$.ids") + .value((value) -> assertThat(value).isEqualTo(new JSONArray().appendElement("application"))); + } + + @Configuration(proxyBeanMethods = false) + static class TestConfiguration { + + @Bean + SbomProperties sbomProperties() { + SbomProperties properties = new SbomProperties(); + properties.getApplication().setLocation("classpath:sbom/cyclonedx.json"); + return properties; + } + + @Bean + SbomEndpoint sbomEndpoint(SbomProperties properties, ResourceLoader resourceLoader) { + return new SbomEndpoint(properties, resourceLoader); + } + + @Bean + SbomEndpointWebExtension sbomEndpointWebExtension(SbomEndpoint sbomEndpoint, SbomProperties properties) { + return new SbomEndpointWebExtension(sbomEndpoint, properties); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpointTests.java index 8befd959fcd0..6456d266da62 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpointTests.java @@ -80,7 +80,7 @@ void cronTriggerIsReported() { assertThat(tasks.getCron()).hasSize(1); CronTaskDescriptor description = (CronTaskDescriptor) tasks.getCron().get(0); assertThat(description.getExpression()).isEqualTo("0 0 0/6 1/1 * ?"); - assertThat(description.getRunnable().getTarget()).isEqualTo(CronTriggerRunnable.class.getName()); + assertThat(description.getRunnable().getTarget()).contains(CronTriggerRunnable.class.getName()); }); } @@ -109,7 +109,7 @@ void fixedDelayTriggerIsReported() { FixedDelayTaskDescriptor description = (FixedDelayTaskDescriptor) tasks.getFixedDelay().get(0); assertThat(description.getInitialDelay()).isEqualTo(2000); assertThat(description.getInterval()).isEqualTo(1000); - assertThat(description.getRunnable().getTarget()).isEqualTo(FixedDelayTriggerRunnable.class.getName()); + assertThat(description.getRunnable().getTarget()).contains(FixedDelayTriggerRunnable.class.getName()); }); } @@ -123,7 +123,7 @@ void noInitialDelayFixedDelayTriggerIsReported() { FixedDelayTaskDescriptor description = (FixedDelayTaskDescriptor) tasks.getFixedDelay().get(0); assertThat(description.getInitialDelay()).isEqualTo(0); assertThat(description.getInterval()).isEqualTo(1000); - assertThat(description.getRunnable().getTarget()).isEqualTo(FixedDelayTriggerRunnable.class.getName()); + assertThat(description.getRunnable().getTarget()).contains(FixedDelayTriggerRunnable.class.getName()); }); } @@ -152,7 +152,7 @@ void fixedRateTriggerIsReported() { FixedRateTaskDescriptor description = (FixedRateTaskDescriptor) tasks.getFixedRate().get(0); assertThat(description.getInitialDelay()).isEqualTo(3000); assertThat(description.getInterval()).isEqualTo(2000); - assertThat(description.getRunnable().getTarget()).isEqualTo(FixedRateTriggerRunnable.class.getName()); + assertThat(description.getRunnable().getTarget()).contains(FixedRateTriggerRunnable.class.getName()); }); } @@ -166,7 +166,7 @@ void noInitialDelayFixedRateTriggerIsReported() { FixedRateTaskDescriptor description = (FixedRateTaskDescriptor) tasks.getFixedRate().get(0); assertThat(description.getInitialDelay()).isEqualTo(0); assertThat(description.getInterval()).isEqualTo(2000); - assertThat(description.getRunnable().getTarget()).isEqualTo(FixedRateTriggerRunnable.class.getName()); + assertThat(description.getRunnable().getTarget()).contains(FixedRateTriggerRunnable.class.getName()); }); } @@ -178,7 +178,7 @@ void taskWithCustomTriggerIsReported() { assertThat(tasks.getFixedRate()).isEmpty(); assertThat(tasks.getCustom()).hasSize(1); CustomTriggerTaskDescriptor description = (CustomTriggerTaskDescriptor) tasks.getCustom().get(0); - assertThat(description.getRunnable().getTarget()).isEqualTo(CustomTriggerRunnable.class.getName()); + assertThat(description.getRunnable().getTarget()).contains(CustomTriggerRunnable.class.getName()); assertThat(description.getTrigger()).isEqualTo(CustomTriggerTask.trigger.toString()); }); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/security/AuthenticationAuditListenerTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/security/AuthenticationAuditListenerTests.java index 77f973c65649..2b68d1dfd8e4 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/security/AuthenticationAuditListenerTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/security/AuthenticationAuditListenerTests.java @@ -29,6 +29,7 @@ import org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent; import org.springframework.security.authentication.event.AuthenticationSuccessEvent; import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent; +import org.springframework.security.authentication.event.LogoutSuccessEvent; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent; @@ -60,6 +61,13 @@ void testAuthenticationSuccess() { assertThat(event.getAuditEvent().getType()).isEqualTo(AuthenticationAuditListener.AUTHENTICATION_SUCCESS); } + @Test + void testLogoutSuccess() { + AuditApplicationEvent event = handleAuthenticationEvent( + new LogoutSuccessEvent(new UsernamePasswordAuthenticationToken("user", "password"))); + assertThat(event.getAuditEvent().getType()).isEqualTo(AuthenticationAuditListener.LOGOUT_SUCCESS); + } + @Test void testOtherAuthenticationSuccess() { this.listener.onApplicationEvent(new InteractiveAuthenticationSuccessEvent( diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpointTests.java new file mode 100644 index 000000000000..e5b54926505e --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpointTests.java @@ -0,0 +1,111 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.session; + +import java.time.Duration; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import org.springframework.boot.actuate.session.SessionsDescriptor.SessionDescriptor; +import org.springframework.session.MapSession; +import org.springframework.session.ReactiveFindByIndexNameSessionRepository; +import org.springframework.session.ReactiveSessionRepository; +import org.springframework.session.Session; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link ReactiveSessionsEndpoint}. + * + * @author Vedran Pavic + * @author Moritz Halbritter + */ +class ReactiveSessionsEndpointTests { + + private static final Session session = new MapSession(); + + @SuppressWarnings("unchecked") + private final ReactiveSessionRepository sessionRepository = mock(ReactiveSessionRepository.class); + + @SuppressWarnings("unchecked") + private final ReactiveFindByIndexNameSessionRepository indexedSessionRepository = mock( + ReactiveFindByIndexNameSessionRepository.class); + + private final ReactiveSessionsEndpoint endpoint = new ReactiveSessionsEndpoint(this.sessionRepository, + this.indexedSessionRepository); + + @Test + void sessionsForUsername() { + given(this.indexedSessionRepository.findByPrincipalName("user")) + .willReturn(Mono.just(Collections.singletonMap(session.getId(), session))); + StepVerifier.create(this.endpoint.sessionsForUsername("user")).consumeNextWith((sessions) -> { + List result = sessions.getSessions(); + assertThat(result).hasSize(1); + assertThat(result.get(0).getId()).isEqualTo(session.getId()); + assertThat(result.get(0).getAttributeNames()).isEqualTo(session.getAttributeNames()); + assertThat(result.get(0).getCreationTime()).isEqualTo(session.getCreationTime()); + assertThat(result.get(0).getLastAccessedTime()).isEqualTo(session.getLastAccessedTime()); + assertThat(result.get(0).getMaxInactiveInterval()).isEqualTo(session.getMaxInactiveInterval().getSeconds()); + assertThat(result.get(0).isExpired()).isEqualTo(session.isExpired()); + }).expectComplete().verify(Duration.ofSeconds(1)); + then(this.indexedSessionRepository).should().findByPrincipalName("user"); + } + + @Test + void sessionsForUsernameWhenNoIndexedRepository() { + ReactiveSessionsEndpoint endpoint = new ReactiveSessionsEndpoint(this.sessionRepository, null); + StepVerifier.create(endpoint.sessionsForUsername("user")).expectComplete().verify(Duration.ofSeconds(1)); + } + + @Test + void getSession() { + given(this.sessionRepository.findById(session.getId())).willReturn(Mono.just(session)); + StepVerifier.create(this.endpoint.getSession(session.getId())).consumeNextWith((result) -> { + assertThat(result.getId()).isEqualTo(session.getId()); + assertThat(result.getAttributeNames()).isEqualTo(session.getAttributeNames()); + assertThat(result.getCreationTime()).isEqualTo(session.getCreationTime()); + assertThat(result.getLastAccessedTime()).isEqualTo(session.getLastAccessedTime()); + assertThat(result.getMaxInactiveInterval()).isEqualTo(session.getMaxInactiveInterval().getSeconds()); + assertThat(result.isExpired()).isEqualTo(session.isExpired()); + }).expectComplete().verify(Duration.ofSeconds(1)); + then(this.sessionRepository).should().findById(session.getId()); + } + + @Test + void getSessionWithIdNotFound() { + given(this.sessionRepository.findById("not-found")).willReturn(Mono.empty()); + StepVerifier.create(this.endpoint.getSession("not-found")).expectComplete().verify(Duration.ofSeconds(1)); + then(this.sessionRepository).should().findById("not-found"); + } + + @Test + void deleteSession() { + given(this.sessionRepository.deleteById(session.getId())).willReturn(Mono.empty()); + StepVerifier.create(this.endpoint.deleteSession(session.getId())) + .expectComplete() + .verify(Duration.ofSeconds(1)); + then(this.sessionRepository).should().deleteById(session.getId()); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpointWebIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpointWebIntegrationTests.java new file mode 100644 index 000000000000..1d311a5561d2 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpointWebIntegrationTests.java @@ -0,0 +1,133 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.session; + +import java.util.Collections; + +import net.minidev.json.JSONArray; +import reactor.core.publisher.Mono; + +import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest; +import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest.Infrastructure; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.session.MapSession; +import org.springframework.session.ReactiveFindByIndexNameSessionRepository; +import org.springframework.session.ReactiveSessionRepository; +import org.springframework.session.Session; +import org.springframework.test.web.reactive.server.WebTestClient; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Integration tests for {@link ReactiveSessionsEndpoint} exposed by WebFlux. + * + * @author Vedran Pavic + * @author Moritz Halbritter + */ +class ReactiveSessionsEndpointWebIntegrationTests { + + private static final Session session = new MapSession(); + + @SuppressWarnings("unchecked") + private static final ReactiveSessionRepository sessionRepository = mock(ReactiveSessionRepository.class); + + @SuppressWarnings("unchecked") + private static final ReactiveFindByIndexNameSessionRepository indexedSessionRepository = mock( + ReactiveFindByIndexNameSessionRepository.class); + + @WebEndpointTest(infrastructure = Infrastructure.WEBFLUX) + void sessionsForUsernameWithoutUsernameParam(WebTestClient client) { + client.get() + .uri((builder) -> builder.path("/actuator/sessions").build()) + .exchange() + .expectStatus() + .is4xxClientError(); + } + + @WebEndpointTest(infrastructure = Infrastructure.WEBFLUX) + void sessionsForUsernameNoResults(WebTestClient client) { + given(indexedSessionRepository.findByPrincipalName("user")).willReturn(Mono.just(Collections.emptyMap())); + client.get() + .uri((builder) -> builder.path("/actuator/sessions").queryParam("username", "user").build()) + .exchange() + .expectStatus() + .isOk() + .expectBody() + .jsonPath("sessions") + .isEmpty(); + } + + @WebEndpointTest(infrastructure = Infrastructure.WEBFLUX) + void sessionsForUsernameFound(WebTestClient client) { + given(indexedSessionRepository.findByPrincipalName("user")) + .willReturn(Mono.just(Collections.singletonMap(session.getId(), session))); + client.get() + .uri((builder) -> builder.path("/actuator/sessions").queryParam("username", "user").build()) + .exchange() + .expectStatus() + .isOk() + .expectBody() + .jsonPath("sessions.[*].id") + .isEqualTo(new JSONArray().appendElement(session.getId())); + } + + @WebEndpointTest(infrastructure = Infrastructure.WEBFLUX) + void sessionForIdFound(WebTestClient client) { + given(sessionRepository.findById(session.getId())).willReturn(Mono.just(session)); + client.get() + .uri((builder) -> builder.path("/actuator/sessions/{id}").build(session.getId())) + .exchange() + .expectStatus() + .isOk() + .expectBody() + .jsonPath("id") + .isEqualTo(session.getId()); + } + + @WebEndpointTest(infrastructure = Infrastructure.WEBFLUX) + void sessionForIdNotFound(WebTestClient client) { + given(sessionRepository.findById("not-found")).willReturn(Mono.empty()); + client.get() + .uri((builder) -> builder.path("/actuator/sessions/not-found").build()) + .exchange() + .expectStatus() + .isNotFound(); + } + + @WebEndpointTest(infrastructure = Infrastructure.WEBFLUX) + void deleteSession(WebTestClient client) { + given(sessionRepository.deleteById(session.getId())).willReturn(Mono.empty()); + client.delete() + .uri((builder) -> builder.path("/actuator/sessions/{id}").build(session.getId())) + .exchange() + .expectStatus() + .isNoContent(); + } + + @Configuration(proxyBeanMethods = false) + static class TestConfiguration { + + @Bean + ReactiveSessionsEndpoint sessionsEndpoint() { + return new ReactiveSessionsEndpoint(sessionRepository, indexedSessionRepository); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/SessionsEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/SessionsEndpointTests.java index eb1647e38910..8047c825aba4 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/SessionsEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/SessionsEndpointTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,10 +21,11 @@ import org.junit.jupiter.api.Test; -import org.springframework.boot.actuate.session.SessionsEndpoint.SessionDescriptor; +import org.springframework.boot.actuate.session.SessionsDescriptor.SessionDescriptor; import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.MapSession; import org.springframework.session.Session; +import org.springframework.session.SessionRepository; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @@ -41,13 +42,18 @@ class SessionsEndpointTests { private static final Session session = new MapSession(); @SuppressWarnings("unchecked") - private final FindByIndexNameSessionRepository repository = mock(FindByIndexNameSessionRepository.class); + private final SessionRepository sessionRepository = mock(SessionRepository.class); - private final SessionsEndpoint endpoint = new SessionsEndpoint(this.repository); + @SuppressWarnings("unchecked") + private final FindByIndexNameSessionRepository indexedSessionRepository = mock( + FindByIndexNameSessionRepository.class); + + private final SessionsEndpoint endpoint = new SessionsEndpoint(this.sessionRepository, + this.indexedSessionRepository); @Test void sessionsForUsername() { - given(this.repository.findByPrincipalName("user")) + given(this.indexedSessionRepository.findByPrincipalName("user")) .willReturn(Collections.singletonMap(session.getId(), session)); List result = this.endpoint.sessionsForUsername("user").getSessions(); assertThat(result).hasSize(1); @@ -57,11 +63,18 @@ void sessionsForUsername() { assertThat(result.get(0).getLastAccessedTime()).isEqualTo(session.getLastAccessedTime()); assertThat(result.get(0).getMaxInactiveInterval()).isEqualTo(session.getMaxInactiveInterval().getSeconds()); assertThat(result.get(0).isExpired()).isEqualTo(session.isExpired()); + then(this.indexedSessionRepository).should().findByPrincipalName("user"); + } + + @Test + void sessionsForUsernameWhenNoIndexedRepository() { + SessionsEndpoint endpoint = new SessionsEndpoint(this.sessionRepository, null); + assertThat(endpoint.sessionsForUsername("user")).isNull(); } @Test void getSession() { - given(this.repository.findById(session.getId())).willReturn(session); + given(this.sessionRepository.findById(session.getId())).willReturn(session); SessionDescriptor result = this.endpoint.getSession(session.getId()); assertThat(result.getId()).isEqualTo(session.getId()); assertThat(result.getAttributeNames()).isEqualTo(session.getAttributeNames()); @@ -69,18 +82,20 @@ void getSession() { assertThat(result.getLastAccessedTime()).isEqualTo(session.getLastAccessedTime()); assertThat(result.getMaxInactiveInterval()).isEqualTo(session.getMaxInactiveInterval().getSeconds()); assertThat(result.isExpired()).isEqualTo(session.isExpired()); + then(this.sessionRepository).should().findById(session.getId()); } @Test void getSessionWithIdNotFound() { - given(this.repository.findById("not-found")).willReturn(null); + given(this.sessionRepository.findById("not-found")).willReturn(null); assertThat(this.endpoint.getSession("not-found")).isNull(); + then(this.sessionRepository).should().findById("not-found"); } @Test void deleteSession() { this.endpoint.deleteSession(session.getId()); - then(this.repository).should().deleteById(session.getId()); + then(this.sessionRepository).should().deleteById(session.getId()); } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/SessionsEndpointWebIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/SessionsEndpointWebIntegrationTests.java index 0a6b28dd83b5..fcf8d5e57d83 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/SessionsEndpointWebIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/SessionsEndpointWebIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import net.minidev.json.JSONArray; import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest; +import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest.Infrastructure; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.session.FindByIndexNameSessionRepository; @@ -45,7 +46,7 @@ class SessionsEndpointWebIntegrationTests { private static final FindByIndexNameSessionRepository repository = mock( FindByIndexNameSessionRepository.class); - @WebEndpointTest + @WebEndpointTest(infrastructure = { Infrastructure.JERSEY, Infrastructure.MVC }) void sessionsForUsernameWithoutUsernameParam(WebTestClient client) { client.get() .uri((builder) -> builder.path("/actuator/sessions").build()) @@ -54,7 +55,7 @@ void sessionsForUsernameWithoutUsernameParam(WebTestClient client) { .isBadRequest(); } - @WebEndpointTest + @WebEndpointTest(infrastructure = { Infrastructure.JERSEY, Infrastructure.MVC }) void sessionsForUsernameNoResults(WebTestClient client) { given(repository.findByPrincipalName("user")).willReturn(Collections.emptyMap()); client.get() @@ -67,7 +68,7 @@ void sessionsForUsernameNoResults(WebTestClient client) { .isEmpty(); } - @WebEndpointTest + @WebEndpointTest(infrastructure = { Infrastructure.JERSEY, Infrastructure.MVC }) void sessionsForUsernameFound(WebTestClient client) { given(repository.findByPrincipalName("user")).willReturn(Collections.singletonMap(session.getId(), session)); client.get() @@ -80,7 +81,7 @@ void sessionsForUsernameFound(WebTestClient client) { .isEqualTo(new JSONArray().appendElement(session.getId())); } - @WebEndpointTest + @WebEndpointTest(infrastructure = { Infrastructure.JERSEY, Infrastructure.MVC }) void sessionForIdNotFound(WebTestClient client) { client.get() .uri((builder) -> builder.path("/actuator/sessions/session-id-not-found").build()) @@ -89,12 +90,21 @@ void sessionForIdNotFound(WebTestClient client) { .isNotFound(); } + @WebEndpointTest(infrastructure = { Infrastructure.JERSEY, Infrastructure.MVC }) + void deleteSession(WebTestClient client) { + client.delete() + .uri((builder) -> builder.path("/actuator/sessions/{id}").build(session.getId())) + .exchange() + .expectStatus() + .isNoContent(); + } + @Configuration(proxyBeanMethods = false) static class TestConfiguration { @Bean SessionsEndpoint sessionsEndpoint() { - return new SessionsEndpoint(repository); + return new SessionsEndpoint(repository, repository); } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/resources/sbom/cyclonedx.json b/spring-boot-project/spring-boot-actuator/src/test/resources/sbom/cyclonedx.json new file mode 100644 index 000000000000..d5c78df8ea6f --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/resources/sbom/cyclonedx.json @@ -0,0 +1,4615 @@ +{ + "bomFormat" : "CycloneDX", + "specVersion" : "1.5", + "serialNumber" : "urn:uuid:13862013-3360-43e5-8055-3645aa43c548", + "version" : 1, + "metadata" : { + "timestamp" : "2024-01-12T11:10:49Z", + "tools" : [ + { + "vendor" : "CycloneDX", + "name" : "cyclonedx-gradle-plugin", + "version" : "1.8.1" + } + ], + "component" : { + "group" : "org.example", + "name" : "cyclonedx", + "version" : "0.0.1-SNAPSHOT", + "purl" : "pkg:maven/org.example/cyclonedx@0.0.1-SNAPSHOT?type=jar", + "type" : "library", + "bom-ref" : "pkg:maven/org.example/cyclonedx@0.0.1-SNAPSHOT?type=jar" + } + }, + "components" : [ + { + "publisher" : "Spring IO", + "group" : "org.springframework", + "name" : "spring-aop", + "version" : "6.1.2", + "description" : "Spring AOP", + "hashes" : [ + { + "alg" : "MD5", + "content" : "c9b8757051ed6c1cc9fda0e379283348" + }, + { + "alg" : "SHA-1", + "content" : "a247bd81df8fa9c6a002b95969692bfd146a70b2" + }, + { + "alg" : "SHA-256", + "content" : "e47b66833ebec281374d55b4e36352b80fe3fa64c94252481a8a7e8d31d9d601" + }, + { + "alg" : "SHA-512", + "content" : "b1cb69feb2931bd4af48b2329614f8e2a0d1afe77267af5f5ea9717ab24c83fd524c8bc7aa8d357a6ccbc497535c4fd282ddfb6d78364a349895a14825af8b9c" + }, + { + "alg" : "SHA-384", + "content" : "09c3c2711a054993922d28b76357c376649a942bf0d7410915e540339c3fa42d5a498211b02e0b09493e68fac7a0d833" + }, + { + "alg" : "SHA3-384", + "content" : "b30a6ea50e454373bd74779d983fc941bb1775368ea67ff0464edbdf0dd3d1c137760bee64a620bd51daf5b65281f15e" + }, + { + "alg" : "SHA3-256", + "content" : "291404410acd2cfbcc804bd91a9777276f622fb3b82788298254c0bf1856b49f" + }, + { + "alg" : "SHA3-512", + "content" : "8101ef2cc88af43b2bfc6126547de4e4a4cc29bf49bffd83aa9d299cab9e9cdb6a5246d46c00119dd88ca02dbf7959c3076dbd32d23e8e1366144ccbbda13316" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework/spring-aop@6.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io/projects/spring-framework" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-framework/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-framework" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework/spring-aop@6.1.2?type=jar" + }, + { + "group" : "com.fasterxml.jackson.datatype", + "name" : "jackson-datatype-jdk8", + "version" : "2.15.3", + "description" : "Add-on module for Jackson (http://jackson.codehaus.org) to support JDK 8 data types.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "3b6579ff944e128c4eccb34e76ff67e0" + }, + { + "alg" : "SHA-1", + "content" : "80158cb020c7bd4e4ba94d8d752a65729dc943b2" + }, + { + "alg" : "SHA-256", + "content" : "29995d3677f72dde74bf32bbf268b96beb952492b742d93f4c70af6c44b2156e" + }, + { + "alg" : "SHA-512", + "content" : "1b13d4f0a955af18a2c68ca45deca79c38d7f9f065d7053bddf2a3dc2fafe729b3355676f7442012451e363aa0da0cd8a0b7a44ded7057cf513df98a475cbbf6" + }, + { + "alg" : "SHA-384", + "content" : "9a29961097a15d3aeabc1ab870699dce827511df9902fc66fe9f836d294c8cea68617498d52fe7dbe920bb5c745f2789" + }, + { + "alg" : "SHA3-384", + "content" : "55570097f9979197eafda91156db909f25dd1b37387656893564060a673dcbc6d85c1f5dc6fd5c8b379b48a4974e6757" + }, + { + "alg" : "SHA3-256", + "content" : "362c3a494e16016f7adc3f512ebe8c8f8da4dbdfc1ca285d05ac085a9198258f" + }, + { + "alg" : "SHA3-512", + "content" : "1aebbe19a11236b7dbf85fd4c457e1a9b5a60fad9c818cc9fd462d7eb489dd5d3a378b4c7c42c6e3777e0b70263968c964cf1aaf8247fc97ec445481af2418a8" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jdk8@2.15.3?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jdk8@2.15.3?type=jar" + }, + { + "group" : "org.apiguardian", + "name" : "apiguardian-api", + "version" : "1.1.2", + "description" : "@API Guardian", + "hashes" : [ + { + "alg" : "MD5", + "content" : "8c7de3f82037fa4a2e8be2a2f13092af" + }, + { + "alg" : "SHA-1", + "content" : "a231e0d844d2721b0fa1b238006d15c6ded6842a" + }, + { + "alg" : "SHA-256", + "content" : "b509448ac506d607319f182537f0b35d71007582ec741832a1f111e5b5b70b38" + }, + { + "alg" : "SHA-512", + "content" : "d7ccd0e7019f1a997de39d66dc0ad4efe150428fdd7f4c743c93884f1602a3e90135ad34baea96d5b6d925ad6c0c8487c8e78304f0a089a12383d4a62e2c9a61" + }, + { + "alg" : "SHA-384", + "content" : "5ae11cfedcee7da43a506a67946ddc8a7a2622284a924ba78f74541e9a22db6868a15f5d84edb91a541e38afded734ea" + }, + { + "alg" : "SHA3-384", + "content" : "c146116b3dfd969200b2ce52d96b92dd02d6f5a45a86e7e85edf35600ddbc2f3c6e8a1ad7e2db4dcd2c398c09fad0927" + }, + { + "alg" : "SHA3-256", + "content" : "b4b436d7f615fc0b820204e69f83c517d1c1ccc5f6b99e459209ede4482268de" + }, + { + "alg" : "SHA3-512", + "content" : "7b95b7ac68a6891b8901b5507acd2c24a0c1e20effa63cd513764f513eab4eb55f8de5178edbe0a400c11f3a18d3f56243569d6d663100f06dd98288504c09c5" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.apiguardian/apiguardian-api@1.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/apiguardian-team/apiguardian" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.apiguardian/apiguardian-api@1.1.2?type=jar" + }, + { + "group" : "jakarta.annotation", + "name" : "jakarta.annotation-api", + "version" : "2.1.1", + "description" : "Jakarta Annotations API", + "hashes" : [ + { + "alg" : "MD5", + "content" : "5dac2f68e8288d0add4dc92cb161711d" + }, + { + "alg" : "SHA-1", + "content" : "48b9bda22b091b1f48b13af03fe36db3be6e1ae3" + }, + { + "alg" : "SHA-256", + "content" : "5f65fdaf424eee2b55e1d882ba9bb376be93fb09b37b808be6e22e8851c909fe" + }, + { + "alg" : "SHA-512", + "content" : "eabe8b855b735663684052ec4cc357cc737936fa57cebf144eb09f70b3b6c600db7fa6f1c93a4f36c5994b1b37dad2dfcec87a41448872e69552accfd7f52af6" + }, + { + "alg" : "SHA-384", + "content" : "798597a6b80b423844d70609c54b00d725a357031888da7e5c3efd3914d1770be69aa7135de13ddb89a4420a5550e35b" + }, + { + "alg" : "SHA3-384", + "content" : "9629b8ca82f61674f5573723bbb3c137060e1442062eb52fa9c90fc8f57ea7d836eb2fb765d160ec8bf300bcb6b820be" + }, + { + "alg" : "SHA3-256", + "content" : "f71ffc2a2c2bd1a00dfc00c4be67dbe5f374078bd50d5b24c0b29fbcc6634ecb" + }, + { + "alg" : "SHA3-512", + "content" : "aa4e29025a55878db6edb0d984bd3a0633f3af03fa69e1d26c97c87c6d29339714003c96e29ff0a977132ce9c2729d0e27e36e9e245a7488266138239bdba15e" + } + ], + "licenses" : [ + { + "license" : { + "id" : "EPL-2.0" + } + }, + { + "license" : { + "id" : "GPL-2.0-with-classpath-exception" + } + } + ], + "purl" : "pkg:maven/jakarta.annotation/jakarta.annotation-api@2.1.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "issue-tracker", + "url" : "https://github.com/eclipse-ee4j/common-annotations-api/issues" + }, + { + "type" : "mailing-list", + "url" : "https://dev.eclipse.org/mhonarc/lists/ca-dev" + }, + { + "type" : "vcs", + "url" : "https://github.com/eclipse-ee4j/common-annotations-api" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/jakarta.annotation/jakarta.annotation-api@2.1.1?type=jar" + }, + { + "group" : "com.fasterxml.jackson.core", + "name" : "jackson-annotations", + "version" : "2.15.3", + "description" : "Core annotations used for value types, used by Jackson data binding package.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "f478f693731e4a2f0f0d3c7bba119b32" + }, + { + "alg" : "SHA-1", + "content" : "79baf4e605eb3bbb60b1c475d44a7aecceea1d60" + }, + { + "alg" : "SHA-256", + "content" : "aae865c3d88256d61b11523cb1e88bd48d5b9ad5855fa1fc859504fd2204708a" + }, + { + "alg" : "SHA-512", + "content" : "c496afd736fa8acbf8126887e2ff375f162212f451326451fbb4b9194231d814e25bccacbaead9db98beec454f6b8d9ed706c5c88e2145bf7e1a37e13fd81af0" + }, + { + "alg" : "SHA-384", + "content" : "13b4d153cc113a69008147974d8887f868f2f3f0a551ef0bacaccf0add17a3168465a94a471e075913f9c6649980a3cb" + }, + { + "alg" : "SHA3-384", + "content" : "dcf8ed73f748eb32e1ab25eba3c294344cc0ddb2cc7bb4376814f1866df42c3093f1336291ce9ed9e1c8730663e0017c" + }, + { + "alg" : "SHA3-256", + "content" : "59f42bc85ee3a8a5b422085b0462aed2a770cf52d7a3660f2cd6dd257ec6e694" + }, + { + "alg" : "SHA3-512", + "content" : "1d1a6fd0e6851d419e79f82170f4060981c233ec8dc61656b84ce7988e9b71bbeecd7364cdadac066ddaf0b3de4dc8aa5acc411ebd1641f549a3af5ba214667b" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.15.3?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/FasterXML/jackson-annotations" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.15.3?type=jar" + }, + { + "publisher" : "Spring IO", + "group" : "org.springframework", + "name" : "spring-jcl", + "version" : "6.1.2", + "description" : "Spring Commons Logging Bridge", + "hashes" : [ + { + "alg" : "MD5", + "content" : "1638acc7030a001c37f803185dbd6eaf" + }, + { + "alg" : "SHA-1", + "content" : "285eb725861c9eacf2a3e4965d4e897932e335ea" + }, + { + "alg" : "SHA-256", + "content" : "eb9ebadb1581f0fe598216f7cf032a3b44a84c96de06ffa8d6f41bcc47305134" + }, + { + "alg" : "SHA-512", + "content" : "2e80d7485b7ad4de6cc372d86ed73db9808be6a5a33e3c9fabccc7915fe57b73011bed75b4567c44456fedad5ae2186658a7f5cc331b4aad64e2a7cc78acdcfa" + }, + { + "alg" : "SHA-384", + "content" : "a6a6422a6c2654eff951af0d6dfb6e93501bdcb4e38ec353d515ca8de919a34b9e1fe37c562106f3f33f844cf071e010" + }, + { + "alg" : "SHA3-384", + "content" : "71098eb263af3ab42d93b8e7a96ceb90fb2069f2ecca85754e702b82f9876255abf5e3f9b48beb4a200f2d9e13599794" + }, + { + "alg" : "SHA3-256", + "content" : "7f49ddd5db9841bb2d7ca8cb5ce52fa1e8982c7c37bc0c6e987eca8f5fc70d38" + }, + { + "alg" : "SHA3-512", + "content" : "4a417d058ecd3619a9716c5d47ecc506f4cb9c3684ee589c443c7b7996b630949932295186135cb3ce5fb0154c29436de4b6c1dbf7f135563449050973510200" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework/spring-jcl@6.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io/projects/spring-framework" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-framework/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-framework" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework/spring-jcl@6.1.2?type=jar" + }, + { + "publisher" : "Spring IO", + "group" : "org.springframework", + "name" : "spring-webmvc", + "version" : "6.1.2", + "description" : "Spring Web MVC", + "hashes" : [ + { + "alg" : "MD5", + "content" : "0fcf00ac160e0d42ad9cd242c796e47a" + }, + { + "alg" : "SHA-1", + "content" : "906ee995372076e22ef9666d8628845c75bf5c42" + }, + { + "alg" : "SHA-256", + "content" : "de42748c3c94c06131c3fe97d81f5c685e4492b9e986baa88af768bb12ea7738" + }, + { + "alg" : "SHA-512", + "content" : "8e7ad7afa2a605d8dbb6cb36c11caf0e626a5ca5849c06f0b35524e5ad6a13eec1ddff8625e1cc278b3082555a940ec3865657828458ab8d60d1c99d513aba0f" + }, + { + "alg" : "SHA-384", + "content" : "5ec328ff12f857baf85ce6f44c849f8818658aaabb4e4d0940ea6b5ad2b009ce3c7717b6b02843f641f8125d0cec4291" + }, + { + "alg" : "SHA3-384", + "content" : "75605b286d839df688bbfb9594dbb83d1eb22f2cae52a6f4b35d485e91ab94a55e94158086684ef3b059f1346af6dc85" + }, + { + "alg" : "SHA3-256", + "content" : "2e67bcc31eede462f5105a09dbf5b40a3e0ccc52d637c6e2720b43412da01525" + }, + { + "alg" : "SHA3-512", + "content" : "d7c5330069c3c0f5eda1417a52384a4b5adc4451c405315a992ed147f26466a19487ffc5e39b90a1ec4cb0df3f804a4d26203f9aaf4e74cf906d1e811abfbf3b" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework/spring-webmvc@6.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io/projects/spring-framework" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-framework/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-framework" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework/spring-webmvc@6.1.2?type=jar" + }, + { + "group" : "org.apache.tomcat.embed", + "name" : "tomcat-embed-websocket", + "version" : "10.1.17", + "description" : "Core Tomcat implementation", + "hashes" : [ + { + "alg" : "MD5", + "content" : "cfc1778713fba9b5bc33d3db64071dff" + }, + { + "alg" : "SHA-1", + "content" : "9ee2f34b51144b75878c9b42768e17de8fbdc74b" + }, + { + "alg" : "SHA-256", + "content" : "00b16e507bea58c6e8a7cb64f129cd2ffd62da092a67a693a8a6af1efdc7dd6d" + }, + { + "alg" : "SHA-512", + "content" : "72da073d4ec4f7473c9a91b4d11607d02a3d18ca8af10348f9130a280f898814625a5865cb44244e6be6d6ab915099805bf06a60f80fd9b8ff2c47840d5266e9" + }, + { + "alg" : "SHA-384", + "content" : "3f4c1d108ca60a7a658839b8ac45eba94354ad20e641d36d2ecf777bac252d371df1e8806a5460ccaf9da222f72a4a9c" + }, + { + "alg" : "SHA3-384", + "content" : "2d0703de58338d38fbae7f4a38390a766d66e3875e3a6a7f2620ae478c838c8f306a39cdac8652890e1116a3859e56e1" + }, + { + "alg" : "SHA3-256", + "content" : "e594abbc4cb6dc0896c08a89cb3fa376980587d5995bace2b3c0798d99c1e454" + }, + { + "alg" : "SHA3-512", + "content" : "3a35964398627fc8bcd323dd9fb6d4e51ea183b704074320822906c074aeb50a0f8732e42b98bdad9c5f0aa4eb421da96dde7e97f094ccdbcb70f668c6d4ff6e" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.apache.tomcat.embed/tomcat-embed-websocket@10.1.17?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.apache.tomcat.embed/tomcat-embed-websocket@10.1.17?type=jar" + }, + { + "group" : "net.bytebuddy", + "name" : "byte-buddy", + "version" : "1.14.10", + "description" : "Byte Buddy is a Java library for creating Java classes at run time. This artifact is a build of Byte Buddy with all ASM dependencies repackaged into its own name space.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "4e5bd83559bf8533b51f92dcd911d16c" + }, + { + "alg" : "SHA-1", + "content" : "8117daf4a612122eb4f517f66adff778cb8b4737" + }, + { + "alg" : "SHA-256", + "content" : "30e6e0446437a67db37e2b7f7d33f50787ddfd970359319dfd05469daa2dcbce" + }, + { + "alg" : "SHA-512", + "content" : "583512f3c47513cf17735aad4e600be44c97e9978c9f6a45227de8a160a879960b1fe01672751e7583176935e0db5477aba581bf68ef5c94f52436a0683a306e" + }, + { + "alg" : "SHA-384", + "content" : "efcce5a139f498de410e182a52e5b2465823a2ebf845001c9a733d87418118342c3854d00a0fae7945ae8dcb1916ba90" + }, + { + "alg" : "SHA3-384", + "content" : "cace3217b1c2c77a4bc194ecc602a28886d9e448efa26b1985e9fd09d90c92bc2e1b50ed70475106ddf266f8c2d14160" + }, + { + "alg" : "SHA3-256", + "content" : "71647273afb1561b70d2cfa519f707a98711f9ae5b891249ae5803c00c25a788" + }, + { + "alg" : "SHA3-512", + "content" : "4aba6f5dcac177c8f8aed902307c62916c32be61841adcf12b9c9885de2de9795a965c0b939729ed67ee7d49b0fbfaf0dfd922be1bf1cdbfbe7b1f09e083831b" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/net.bytebuddy/byte-buddy@1.14.10?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/net.bytebuddy/byte-buddy@1.14.10?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-test-autoconfigure", + "version" : "3.2.1", + "description" : "Spring Boot Test AutoConfigure", + "hashes" : [ + { + "alg" : "MD5", + "content" : "d6f93aa42df4cb27a58835750597d835" + }, + { + "alg" : "SHA-1", + "content" : "bfc34c523b3ab295fb01f46373e903f9729cdd43" + }, + { + "alg" : "SHA-256", + "content" : "86c51c743babfc591be09af7fedcd778410706e567e9ed27218448ccd2297ef4" + }, + { + "alg" : "SHA-512", + "content" : "701b6ee27c87081e4a65ba76fe721f74e917a655575b19b9205b314f4a561889564e09ceadaa880aaf30f70cd8b48dc70fc5e32f511204b1ea031a12349fd9be" + }, + { + "alg" : "SHA-384", + "content" : "74d4cf202399e946789a5572007aa4fbf1daf26cfac27f83a3d8550711f99700083029b1f900037b8f263543ac9824a1" + }, + { + "alg" : "SHA3-384", + "content" : "ac0b64ec94b558b4f806c09f68247eff80bcc8e33b97f5d09f5517a2339187e4b11c8e2287400a173cb128e3fdb4ab06" + }, + { + "alg" : "SHA3-256", + "content" : "5ca85cd0c052076d625c262cf445e4e8fb255b13323ba4ab08cbfcf32ec236b3" + }, + { + "alg" : "SHA3-512", + "content" : "04ce88c724852938057c723a7ec637af2f8e601879a592a6fe135eaa26940f8fd9d9ac8f6917e761cb0ff31547bb849ff88a66e1f6e93c1032a4009fe1fdef1d" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-test-autoconfigure@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-test-autoconfigure@3.2.1?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-starter-json", + "version" : "3.2.1", + "description" : "Starter for reading and writing json", + "hashes" : [ + { + "alg" : "MD5", + "content" : "bea54cf408b022894c0b1b013c58c0a9" + }, + { + "alg" : "SHA-1", + "content" : "ecda50de20ab6d3c49ea30df4c1982048f5d31ac" + }, + { + "alg" : "SHA-256", + "content" : "572f1a4171dff33b5a9260bbd704473442adf24f890386abe33ecc18c047836a" + }, + { + "alg" : "SHA-512", + "content" : "c611e0d07093d99dbcded7a00e7c00355a7c13c24a69d33105ca88ec63cc68ba76339b5a96b84f2b666bb883849980776e1e24ee2df9c7dd07b2dde0992289b5" + }, + { + "alg" : "SHA-384", + "content" : "ed40ffb527cf8442dbe3eb7b542970317e4827ed00196387d78f123490a77b08b3bc2fd5f53b83f6bee1d4eed29215bf" + }, + { + "alg" : "SHA3-384", + "content" : "26d5852f479f1c72f501569a8ea0c0e4c93f9049676921dca94b467e68f221214e4485c41647e6a92005e5090a6a7c80" + }, + { + "alg" : "SHA3-256", + "content" : "dc69eefb2f1441bbec58c219ccedd895b863b1e1d25cc3805936f0c9b072f2e6" + }, + { + "alg" : "SHA3-512", + "content" : "bf6fce60937e78550fb3d411c19aad2200d8129138fade809e9d0abc307c7f06b54732f1e94fa86ebb82d4da0293f7bce43345416b3fdae1b3c2edbac6706310" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-starter-json@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-json@3.2.1?type=jar" + }, + { + "group" : "com.fasterxml.jackson.datatype", + "name" : "jackson-datatype-jsr310", + "version" : "2.15.3", + "description" : "Add-on module to support JSR-310 (Java 8 Date & Time API) data types.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "acd8ae6da000eb831a69b4acdc182b7f" + }, + { + "alg" : "SHA-1", + "content" : "4a20a0e104931bfa72f24ef358c2eb63f1ef2aaf" + }, + { + "alg" : "SHA-256", + "content" : "bea1d78009ebc4e5d54918a3f7aec5da9fbd09f662c191a217ffcf37e8527c5e" + }, + { + "alg" : "SHA-512", + "content" : "1c5bde6c91a2a89f3c1f231f4e17c435063d9012babbfcba509a3b25363b1fd99f0dcd4234f1e00559e43d3dc8e6c71834282c72f2ebf15484ae900754c5d757" + }, + { + "alg" : "SHA-384", + "content" : "cc72f54d89bc0f7ffae9af36dfba38e5a61ac83db2f0d8de3c74e405a0bfd77b6d463217ece19c64eeb16291d80a69f5" + }, + { + "alg" : "SHA3-384", + "content" : "096944bac7583e5c97e8afcfbc928ca4a87a7d3e5eb74cc77394a19ca8bc6f26185da7fdf5d6bd2179582bf51940edc5" + }, + { + "alg" : "SHA3-256", + "content" : "0301cf719fd327643b3228b91c36688aaea3fccf3487c3e09bae3de636340dc7" + }, + { + "alg" : "SHA3-512", + "content" : "b9a4a8c9785e8ec2786690bfede18c76e08d81fc9c77bb2dad88e1a034f97f7d20020531ac1cb9b0b6e61645b08ea441aba35fc0732edc2fc1dc4b36d6f1695c" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310@2.15.3?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310@2.15.3?type=jar" + }, + { + "group" : "org.hdrhistogram", + "name" : "HdrHistogram", + "version" : "2.1.12", + "description" : "HdrHistogram supports the recording and analyzing sampled data value counts across a configurable integer value range with configurable value precision within the range. Value precision is expressed as the number of significant digits in the value recording, and provides control over value quantization behavior across the value range and the subsequent value resolution at any given level.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "4b1acf3448b750cb485da7e37384fcd8" + }, + { + "alg" : "SHA-1", + "content" : "6eb7552156e0d517ae80cc2247be1427c8d90452" + }, + { + "alg" : "SHA-256", + "content" : "9b47fbae444feaac4b7e04f0ea294569e4bc282bc69d8c2ce2ac3f23577281e2" + }, + { + "alg" : "SHA-512", + "content" : "b03b7270eb7962c88324858f94313adb3a53876f1e11568a78a5b7e00a9419e4d7ab8774747427bff6974b971b6dfc47a127fca11cb30eaf7d83b716e09b1a0d" + }, + { + "alg" : "SHA-384", + "content" : "06977d680dafd803d32441994474e598384a584411a67c95ab4a64698c9e4cbd613e0119b54685cea275b507a0a6f362" + }, + { + "alg" : "SHA3-384", + "content" : "b5ccb4d39bf7cc8ccc33f0f8fcbab0a63c99a94feda840b5d80fc3ae061127f1475cfb869b060933783a1f2eafb103a1" + }, + { + "alg" : "SHA3-256", + "content" : "ef2113f27862af1d24d90c2028fc566902720248468d3c0f2f1807cc86918882" + }, + { + "alg" : "SHA3-512", + "content" : "4fca2f75bdfd3f2ac40dc227ae2ef0272142802b1546d4f5edf9155eaeed84eff07b0c3a978291a1df096ec94724b0defb045365e6a51acfdd5da68d72c5a8eb" + } + ], + "licenses" : [ + { + "license" : { + "id" : "CC0-1.0" + } + }, + { + "license" : { + "id" : "BSD-2-Clause", + "url" : "https://opensource.org/licenses/BSD-2-Clause" + } + } + ], + "purl" : "pkg:maven/org.hdrhistogram/HdrHistogram@2.1.12?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "issue-tracker", + "url" : "https://github.com/HdrHistogram/HdrHistogram/issues" + }, + { + "type" : "vcs", + "url" : "scm:git:git://github.com/HdrHistogram/HdrHistogram.git" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.hdrhistogram/HdrHistogram@2.1.12?type=jar" + }, + { + "group" : "io.micrometer", + "name" : "micrometer-commons", + "version" : "1.12.1", + "description" : "Module containing common code", + "hashes" : [ + { + "alg" : "MD5", + "content" : "2518ae277e56aea5e37e3fc2f578dfa4" + }, + { + "alg" : "SHA-1", + "content" : "abcc6b294e60582afdfae6c559c94ad1d412ce2d" + }, + { + "alg" : "SHA-256", + "content" : "295785b04cd4de7711bb16730da5e9829bac55a8879d52120625dac6c89904ed" + }, + { + "alg" : "SHA-512", + "content" : "25d65699a25fe3b90de17a0539233fdad37df864f6d493475976e9a513bd7767520a882cbf6bbd98714a1fe94acdb77a160cd68f549475d2b93624ffe8672a00" + }, + { + "alg" : "SHA-384", + "content" : "8523ae45ce6dd4a068cce108cd31da24629839d3d293fca92353cf45db9eae88107744c9e66b82ed14abb96782c562da" + }, + { + "alg" : "SHA3-384", + "content" : "9af1fc3aad2d0131c337b843c38b05510d31e7931a48841a4bdb618257f185286ed393f8a4418ae4c5f91da7f9c76cbf" + }, + { + "alg" : "SHA3-256", + "content" : "d5dbeadc5f629430202c81a6736dff2efbfbf3ea2c09844b1194f316772a93f7" + }, + { + "alg" : "SHA3-512", + "content" : "c7b1dd1727000936bf51c02f9bf9b262a412e2b815531df4a9f7aad675ef0f728d4492327a404b37b1ef36d41a240b83dbfeea3367b3b4faa22cdc2decc5bac9" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/io.micrometer/micrometer-commons@1.12.1?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/io.micrometer/micrometer-commons@1.12.1?type=jar" + }, + { + "group" : "org.mockito", + "name" : "mockito-core", + "version" : "5.7.0", + "description" : "Mockito mock objects library core API and implementation", + "hashes" : [ + { + "alg" : "MD5", + "content" : "4df8dd230071bc192161d0e54a76f6b5" + }, + { + "alg" : "SHA-1", + "content" : "a1c258331ab91d66863c983aff7136357e9de056" + }, + { + "alg" : "SHA-256", + "content" : "dbad5e746654910a11a59ecb4d01e38461f3e5d16161689dc2588d5554432521" + }, + { + "alg" : "SHA-512", + "content" : "5a2f00df2b1b2dbca06686f88806b86990f1eea6f7c25281c0e7ec7cf7904a0a9227477279b11630d80f8e88d6b6e9dbdb40ad094a4077cc6a44cd2072d12662" + }, + { + "alg" : "SHA-384", + "content" : "3f2caa05fe4a5d5b385654ce60d0655724200fdd333652459b86848c3b895a9ad0b0daca8a014851d6b5c744cd0e9372" + }, + { + "alg" : "SHA3-384", + "content" : "06ba4583220a4aaa47d79ccab11783d48900d8850a346e4a1efc61c057630fcf0bb9c95cec74833ab5e6ee08e55625ec" + }, + { + "alg" : "SHA3-256", + "content" : "f1f9899edf629fffaf8b4483ac04430945996393f4fdcedc38eba22a9a5c715d" + }, + { + "alg" : "SHA3-512", + "content" : "d6f479d52534b382088012e3d1a83fa267dfb046322a72e84438d21973165617d1d710bb42f1cb2d2d3d7f891969320232031be33f4abb2ea1526217e16e8c63" + } + ], + "licenses" : [ + { + "license" : { + "id" : "MIT", + "url" : "https://opensource.org/licenses/MIT" + } + } + ], + "purl" : "pkg:maven/org.mockito/mockito-core@5.7.0?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "build-system", + "url" : "https://github.com/mockito/mockito/actions" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/mockito/mockito/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/mockito/mockito.git" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.mockito/mockito-core@5.7.0?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-actuator-autoconfigure", + "version" : "3.2.1", + "description" : "Spring Boot Actuator AutoConfigure", + "hashes" : [ + { + "alg" : "MD5", + "content" : "3afea56b25f872cee2c929c761b0790d" + }, + { + "alg" : "SHA-1", + "content" : "0fe81034352a15731322fba326447ba70bfa3962" + }, + { + "alg" : "SHA-256", + "content" : "3850d85c0f6074fe9286dece9b44f8bded5e194e9b816860735e0fc728173d65" + }, + { + "alg" : "SHA-512", + "content" : "7197158ef14a580edc836ab7af10a9f5f567ba60e21267b624fc4143debd2638c7b8bd8e2e5973fdd5c5d512be73df96500fb0a4273f20a21b42161e9f7add75" + }, + { + "alg" : "SHA-384", + "content" : "4a35eb1f124d8d7812d32f87b16a24dd56d4cb43278ce66f216f4a4af34db357e7481fc1b26de9bde7c2dd6847687721" + }, + { + "alg" : "SHA3-384", + "content" : "8369a8b49cae80b92abbfcc0218d55b9cecd86778735c66b9b0cc6fbc7251784725249392e716c314e3ec08c995557bb" + }, + { + "alg" : "SHA3-256", + "content" : "ee742160e4951e1f6145d575f6c6ebb908a46f38a8b3b81b7d61aac7c111a87f" + }, + { + "alg" : "SHA3-512", + "content" : "dcb1b214577203c9b3e2e5dcb3aaef8e46aec5f75a40a606f42e230c6e1af39c37250d58de6bf694c5a62d70fb1a6dcba436d696f71d7aa1a52b9f4dea5aa9a9" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-actuator-autoconfigure@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-actuator-autoconfigure@3.2.1?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-starter-tomcat", + "version" : "3.2.1", + "description" : "Starter for using Tomcat as the embedded servlet container. Default servlet container starter used by spring-boot-starter-web", + "hashes" : [ + { + "alg" : "MD5", + "content" : "db4df0f653e84bfd545894c4567b19ff" + }, + { + "alg" : "SHA-1", + "content" : "d8efc48034015522958cb3fea5831b4cbcd4fcfb" + }, + { + "alg" : "SHA-256", + "content" : "bf93da73a8fb4caf9fa68e4f3b97adcc9dbb8c79220a828b3d70ecf12d410117" + }, + { + "alg" : "SHA-512", + "content" : "d2bce5bb0271525766283e17160513de530c20e0452cecc3c9d5be3890986cc071c1423a3c11c54a36d2f83bd3a238b0fcbcc6218976a5633f0753a313418f6f" + }, + { + "alg" : "SHA-384", + "content" : "1f9ae7504b1345595377a4d35163315824dcf25f29ac9d522385e6e1672b813719655989708eb03b419e808f1f102be9" + }, + { + "alg" : "SHA3-384", + "content" : "9d890c3314b5ec30f39de30bf70471aef5f19e64d6d2f60b6fe66b3c57978bbda0a981cf92e42f18f27b72ed2ddb3574" + }, + { + "alg" : "SHA3-256", + "content" : "43d38219fbe556c2bac8670fa0aa4f89e2ac273fda77d8bceac8d9d34d7b27c2" + }, + { + "alg" : "SHA3-512", + "content" : "6a4e9a2ff89293c60c8a05cb79a65695dbe9823978be93f1b309d702338f87f108aabeaeafe8ff0ebf08bcd5483efbbb4a85c566e1357acd1d2fab565c910a80" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-starter-tomcat@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-tomcat@3.2.1?type=jar" + }, + { + "group" : "org.apache.logging.log4j", + "name" : "log4j-to-slf4j", + "version" : "2.21.1", + "description" : "The Apache Log4j binding between Log4j 2 API and SLF4J.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "00b957af4a40bea6a7bf61400b6ccf63" + }, + { + "alg" : "SHA-1", + "content" : "d77b2ba81711ed596cd797cc2b5b5bd7409d841c" + }, + { + "alg" : "SHA-256", + "content" : "de143c565ba78b0f2c0be58f132c7aec75e6e1a10845ebda5a4f17c2a35d9990" + }, + { + "alg" : "SHA-512", + "content" : "8a7a682dc5ae6a123c8de6002f1470ad2682795c65b47b06397d9ad9a31729e588c406013bfa989f9c2a51750c353cd7a147bc036f2d66b0f8f0b3f13798a637" + }, + { + "alg" : "SHA-384", + "content" : "8f3e4f1eea069f47b2c6111f1233448ea9ccc723b7c8a8bd308b7317a6ec1f47008d2952c1cb274152a38d3e21da750b" + }, + { + "alg" : "SHA3-384", + "content" : "822f93c3bba450b89a7f64b4d81aab48a7f5c2f693b53a4dcc83eba3a8300ff90c9e7727223f3491c782c80bee9dc707" + }, + { + "alg" : "SHA3-256", + "content" : "1f3f3aace32b45e9a6271c7b4ac76ddf86eb4f32e28e147a3e054dc8c836def1" + }, + { + "alg" : "SHA3-512", + "content" : "bb61c16d22aeed2d6b18972f68a6c4670fb8a07eeb79407748a7d499bc64e8ad8eb9774d372d9286227665686fe90878f2ef7e7f8595b74cd448d0f847aec02e" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0", + "url" : "https://www.apache.org/licenses/LICENSE-2.0" + } + } + ], + "purl" : "pkg:maven/org.apache.logging.log4j/log4j-to-slf4j@2.21.1?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.apache.logging.log4j/log4j-to-slf4j@2.21.1?type=jar" + }, + { + "group" : "jakarta.xml.bind", + "name" : "jakarta.xml.bind-api", + "version" : "4.0.1", + "hashes" : [ + { + "alg" : "MD5", + "content" : "e62084f1afb23eccde6645bf3a9eb06f" + }, + { + "alg" : "SHA-1", + "content" : "ca2330866cbc624c7e5ce982e121db1125d23e15" + }, + { + "alg" : "SHA-256", + "content" : "287f3b6d0600082e0b60265d7de32be403ee7d7269369c9718d9424305b89d95" + }, + { + "alg" : "SHA-512", + "content" : "dcc70e8301a7f274bbb6d6b3fe84ad8c9e5beda318699c05aeac0c42b9e1e210fc6953911be2cb1a2ef49ac5159c331608365b1b83a14a8e86f89f630830dd28" + }, + { + "alg" : "SHA-384", + "content" : "16ff377d0cfd7d8f23f45417e1e0df72de7f77780832ae78a1d2c51d77c4b2f8d270bd9ce4b73d07b70b060a9c39c56e" + }, + { + "alg" : "SHA3-384", + "content" : "773fd2d1e1a647bea7a5365490483fd56e7a49d9b731298d3202b4f93602c9a1a7add0eee868bc5a7ac961da7dda8c8e" + }, + { + "alg" : "SHA3-256", + "content" : "26214bba5cee45014859be8018dc631c14146e0a5959bb88e05d98472c88de8b" + }, + { + "alg" : "SHA3-512", + "content" : "32bdc043b7d616d73bbc26e0b36308126b15658cd032a354770760c5b5656429a4240dd3ddcea835556e813b6ae8618307ebeb96e2e46ba8ab16f6a485fa4d32" + } + ], + "licenses" : [ + { + "license" : { + "id" : "BSD-3-Clause" + } + } + ], + "purl" : "pkg:maven/jakarta.xml.bind/jakarta.xml.bind-api@4.0.1?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/jakarta.xml.bind/jakarta.xml.bind-api@4.0.1?type=jar" + }, + { + "group" : "org.yaml", + "name" : "snakeyaml", + "version" : "2.2", + "description" : "YAML 1.1 parser and emitter for Java", + "hashes" : [ + { + "alg" : "MD5", + "content" : "d78aacf5f2de5b52f1a327470efd1ad7" + }, + { + "alg" : "SHA-1", + "content" : "3af797a25458550a16bf89acc8e4ab2b7f2bfce0" + }, + { + "alg" : "SHA-256", + "content" : "1467931448a0817696ae2805b7b8b20bfb082652bf9c4efaed528930dc49389b" + }, + { + "alg" : "SHA-512", + "content" : "11547e75cc80bee26f532e2598bc6e4ffa802941496dc0d8ce017f1b15e01ebbb80e91ed17d1047916e32bf2fc58da532bc71a1dfe93afccc277a296d86634ba" + }, + { + "alg" : "SHA-384", + "content" : "dae0cb1a7ab9ccc75413f46f18ae160e12e91dfef0c17a07ea547a365e9fb422c071aa01579f2a320f15ce6ee4c29038" + }, + { + "alg" : "SHA3-384", + "content" : "654b418f330fa02f1111a20c27395ec5c7f463907ae44f60057c94da04f81e815cf1c3959f005026381ef79030049694" + }, + { + "alg" : "SHA3-256", + "content" : "2c4deb8d79876b80b210ef72dc5de2b19607e50fbe3abf09a4324576ca0881fc" + }, + { + "alg" : "SHA3-512", + "content" : "0d9be5610b2bcb6bb7562ee8bcc0d68f81d3771958ce9299c5e57e8ec952c96906d711587b7f72936328c72fb41687b4f908c4de3070b78cc1f3e257cf4b715e" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.yaml/snakeyaml@2.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "issue-tracker", + "url" : "https://bitbucket.org/snakeyaml/snakeyaml/issues" + }, + { + "type" : "vcs", + "url" : "https://bitbucket.org/snakeyaml/snakeyaml/src" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.yaml/snakeyaml@2.2?type=jar" + }, + { + "group" : "org.junit.platform", + "name" : "junit-platform-commons", + "version" : "1.10.1", + "description" : "Module \"junit-platform-commons\" of JUnit 5.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "cd430f3f7345c0888f8408ce8795c751" + }, + { + "alg" : "SHA-1", + "content" : "2bfcd4a4e38b10c671b6916d7e543c20afe25579" + }, + { + "alg" : "SHA-256", + "content" : "7d9855ee3f3f71f015eb1479559bf923783243c24fbfbd8b29bed8e8099b5672" + }, + { + "alg" : "SHA-512", + "content" : "4aa83350e7a6df21feb9ba8756bb4a68986f33f8c6e384720d1daa448444016c0def1781729788e3e884664abd6703b1e3c0ec6b79893a9d5645c3a4809c0ad2" + }, + { + "alg" : "SHA-384", + "content" : "d264f2c8ceaff384b0f22ee77890195ed3d918b01f338e35fc2ee12f82df15e59533918509f535883b4f4befed28595e" + }, + { + "alg" : "SHA3-384", + "content" : "d1fa76d6b2567e831b37ff7843df6d7d65028d4e53c570c6f580cbbf13269d2aa2afedfedfe5a3f2cf92d7de6d3c89b2" + }, + { + "alg" : "SHA3-256", + "content" : "eef0f968f2d2fc31f8b4a4ed43bafeb46977de1ac3d59477ab6e2b014f97e070" + }, + { + "alg" : "SHA3-512", + "content" : "93340cc2c378c830c755b25006bc4f73ec77ad10661f05625b23efa0854d456da8e62bdbe7e7edf3418dda864e6e0d7a6b9d34cea23d525b8991258f3d75fc9c" + } + ], + "licenses" : [ + { + "license" : { + "id" : "EPL-2.0" + } + } + ], + "purl" : "pkg:maven/org.junit.platform/junit-platform-commons@1.10.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/junit-team/junit5" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.junit.platform/junit-platform-commons@1.10.1?type=jar" + }, + { + "publisher" : "Spring IO", + "group" : "org.springframework", + "name" : "spring-web", + "version" : "6.1.2", + "description" : "Spring Web", + "hashes" : [ + { + "alg" : "MD5", + "content" : "a39761bc7a706c70f6ca3ab805a97b34" + }, + { + "alg" : "SHA-1", + "content" : "0f26b98778376cc39afb04fbb6fdd7543bef89f2" + }, + { + "alg" : "SHA-256", + "content" : "3f2012a24c6213f155b6bc69aa3ecafe2a373c1e92a26dbecc62ff575c3a1fb3" + }, + { + "alg" : "SHA-512", + "content" : "f07f054feaf53c2a97b82150882281035824cf0b815f317a22ba1954afa721bc5d57cb07faa19bad99fc235373b62edd7013f7ac2cd0a3d0db91faf49f216741" + }, + { + "alg" : "SHA-384", + "content" : "57418cf2a9b3256201c0874e7721966b09929030c64f5e5a85007bd645294dfbf1a14d4632a5aa5fcf70af5bf733d542" + }, + { + "alg" : "SHA3-384", + "content" : "83daa608abc0124ec237f65231d5f1dd1a5d751e459d3ea255a3d12a56e92ac83037fb72c5793f497fbecb9e389eb299" + }, + { + "alg" : "SHA3-256", + "content" : "1a17acdfa8920b1849a16e4260bb4b960f60da07732148a5281cfcba21d1e4a8" + }, + { + "alg" : "SHA3-512", + "content" : "3e5e020cb1068250eb0e58e9bc0368c44db96d59022047ecffe286a51b0896e4320d9696f2f9136b4c0aed547d8dd1af1bbc2b4b053aa994246bb43bd7397f05" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework/spring-web@6.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io/projects/spring-framework" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-framework/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-framework" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework/spring-web@6.1.2?type=jar" + }, + { + "group" : "org.objenesis", + "name" : "objenesis", + "version" : "3.3", + "description" : "A library for instantiating Java objects", + "hashes" : [ + { + "alg" : "MD5", + "content" : "ab0e0b2ab81affdd7f38bcc60fd85571" + }, + { + "alg" : "SHA-1", + "content" : "1049c09f1de4331e8193e579448d0916d75b7631" + }, + { + "alg" : "SHA-256", + "content" : "02dfd0b0439a5591e35b708ed2f5474eb0948f53abf74637e959b8e4ef69bfeb" + }, + { + "alg" : "SHA-512", + "content" : "1fa990d15bd179f07ffbc460d580a6fd0562e45dee8bd4a9405917536b78f45c0d6f644b67f85d781c758aa56eff90aef23eedcc9bd7f5ff887a67b716083e61" + }, + { + "alg" : "SHA-384", + "content" : "2f6878f91a12db32c244afcee619d57c3ad6ff0297f4e41c2247e737c1ccc5fcc1ce03256b479b0f9b87900410bc4502" + }, + { + "alg" : "SHA3-384", + "content" : "a3dd9f6908fe732900d50eb209988183ffcf511afb4e401ef95b75c51777709d2d10e1dc9ee386b7357c5c2cbcf8c00e" + }, + { + "alg" : "SHA3-256", + "content" : "fd2b66d174ed68cbfcda41d5cbd29db766c5676866d6b2324b446a87afab3a9f" + }, + { + "alg" : "SHA3-512", + "content" : "ef509e8bcea73bc282287205ffc7625508080be44c16948137274f189459624891dcf109118c9feff109e1aa99becf176f8db837ac4fd586201510c3ae2ea30a" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.objenesis/objenesis@3.3?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.objenesis/objenesis@3.3?type=jar" + }, + { + "group" : "com.vaadin.external.google", + "name" : "android-json", + "version" : "0.0.20131108.vaadin1", + "description" : "  JSON (JavaScript Object Notation) is a lightweight data-interchange format. This is the org.json compatible Android implementation extracted from the Android SDK  ", + "hashes" : [ + { + "alg" : "MD5", + "content" : "10612241a9cc269501a7a2b8a984b949" + }, + { + "alg" : "SHA-1", + "content" : "fa26d351fe62a6a17f5cda1287c1c6110dec413f" + }, + { + "alg" : "SHA-256", + "content" : "dfb7bae2f404cfe0b72b4d23944698cb716b7665171812a0a4d0f5926c0fac79" + }, + { + "alg" : "SHA-512", + "content" : "c4a06a0a3ce7bdbee702c06944265c050a4c8d2fbd21c248936e2edfdab63acea30f2cf3568d3c21a559940d939985a8b10d30aff972a3e8cbeb392c0b02da3a" + }, + { + "alg" : "SHA-384", + "content" : "60d1044b5439cdf5eb621118cb0581365ab4f023a30998b238b87854236f03d8395d45b0262fb812335ff904cb77f25f" + }, + { + "alg" : "SHA3-384", + "content" : "b80ebdbec2127279ca402ca52e50374d3ca773376258f6aa588b442822ee7362de8cca206db71b79862bde84018cf450" + }, + { + "alg" : "SHA3-256", + "content" : "6285b1ac8ec5fd339c7232affd9c08e6daf91dfa18ef8ae7855f52281d76627e" + }, + { + "alg" : "SHA3-512", + "content" : "de7ed83f73670213b4eeacfd7b3ceb7fec7d88ac877f41aeaacf43351d04b34572f2edc9a8f623af5b3fccab3dac2cc048f5c8803c1d4dcd1ff975cd6005124d" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0", + "url" : "https://www.apache.org/licenses/LICENSE-2.0" + } + } + ], + "purl" : "pkg:maven/com.vaadin.external.google/android-json@0.0.20131108.vaadin1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "distribution", + "url" : "http://oss.sonatype.org/content/repositories/vaadin-releases/" + }, + { + "type" : "vcs", + "url" : "http://developer.android.com/sdk/" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/com.vaadin.external.google/android-json@0.0.20131108.vaadin1?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-starter-logging", + "version" : "3.2.1", + "description" : "Starter for logging using Logback. Default logging starter", + "hashes" : [ + { + "alg" : "MD5", + "content" : "7ac01b9dee045285c365cf6a3d8d8451" + }, + { + "alg" : "SHA-1", + "content" : "0df8ec78dc87885298998ca3c9bd603ee7bfe5b8" + }, + { + "alg" : "SHA-256", + "content" : "0b7e411cfc44a15fc63a36cd05a73b34c3558f1b06e4f297b1919361b8a351a7" + }, + { + "alg" : "SHA-512", + "content" : "23baf0a59d56809db43101fbddb712b515012c64530362665cebe84c53bbd716218d3602024315f3250dea923138845c09d5c56dd9c7fb26a53d5e21a325e52e" + }, + { + "alg" : "SHA-384", + "content" : "f5ff55d346828eaec7b535bdd1d6096acc3819e81f6fa0a3d2396d523616e2e356d58115de8b8c49adf035216fa6ea83" + }, + { + "alg" : "SHA3-384", + "content" : "6e5bd5c09d127a2984a55bbfc296cc515e399f35ee2ca949b10639c5ef583bee58dc9eeb60f6bec1f05904f8b91b4a26" + }, + { + "alg" : "SHA3-256", + "content" : "99b21628e6efb820b4955e0e17bb54345a6974dc785b79abb7af8186a261159e" + }, + { + "alg" : "SHA3-512", + "content" : "91625907d0200fb80f025aa6ed098372955053bfb277db124d95ce2dd5049c20e9e7f2b97cffd6f247d9ae8da1bc26c004b688687056a87ccb3033d57a7c20f3" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-starter-logging@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-logging@3.2.1?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-actuator", + "version" : "3.2.1", + "description" : "Spring Boot Actuator", + "hashes" : [ + { + "alg" : "MD5", + "content" : "d5ede97972b567fe75db1d2bbfc035d8" + }, + { + "alg" : "SHA-1", + "content" : "9089b9fff0c17eae54aabc466b78e010eac3a04f" + }, + { + "alg" : "SHA-256", + "content" : "b870c0a601dc0d6d98b33a6b59d41799285848de267f7cfb466a6f167f30c4d2" + }, + { + "alg" : "SHA-512", + "content" : "9577f4ba268b688ad100d4038f6dba97139a29b82127f6a581b948f0ee08fc8159f51fa5f7deb200e5a61559fd321559d2255af75c3e28cf293e815b8b1bb8ac" + }, + { + "alg" : "SHA-384", + "content" : "96adde3cd5a4f729a6d382566800e62e89c93d1c3b9120ffefcd9a666d755fc5d6dc3dd12577f927bcaf03b7f1b0922b" + }, + { + "alg" : "SHA3-384", + "content" : "c3f71bfae2d560ec46f76e833aee6964b5ad57639cb4ded937cd6d1e39b213a4c255d9b83ba59882d22dd31a4ef7b5f5" + }, + { + "alg" : "SHA3-256", + "content" : "d7a251040e99b14a5d926f86bdcb1fcf505518d31cb421e6aaf32d59d8f7f2eb" + }, + { + "alg" : "SHA3-512", + "content" : "3b642b5433989ba548cffebd7c155d5ada680b96996eac432895de56a27d7529c795d7263e8419854c9d118cddc0492d142d260a2e5434058134c9bc17ab8253" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-actuator@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-actuator@3.2.1?type=jar" + }, + { + "group" : "ch.qos.logback", + "name" : "logback-core", + "version" : "1.4.14", + "description" : "logback-core module", + "hashes" : [ + { + "alg" : "MD5", + "content" : "7367629d307fa3d0b82d76b9d3f1d09a" + }, + { + "alg" : "SHA-1", + "content" : "4d3c2248219ac0effeb380ed4c5280a80bf395e8" + }, + { + "alg" : "SHA-256", + "content" : "f8c2f05f42530b1852739507c1792f0080167850ed8f396444c6913d6617a293" + }, + { + "alg" : "SHA-512", + "content" : "d18159d4b378973e49182c4711b3d5b1f3600674ddd7bde26793247854bbd3a7233df7f74c356ecc86e4160ac6f866e0b32c109df6e1b428a10cddd4bc7f44e8" + }, + { + "alg" : "SHA-384", + "content" : "afe21cf21e8804d069514a1f0d57c92b4caf56f8b010bd681d19fff67f237fcf0bbe1e1c9bfc4cedcfe602a3ea859b57" + }, + { + "alg" : "SHA3-384", + "content" : "38cc28c8a578f4053412440d88b41938fa029a8ee3d350fe7474b34afa0f17889298d00f3c2cec4510d72d3342d29a77" + }, + { + "alg" : "SHA3-256", + "content" : "6c7d3be575969be97a49e90a97a8dc1bb25380b1b302073e00d2e21cb266e6a6" + }, + { + "alg" : "SHA3-512", + "content" : "8e9ce45d599bffac71e35a0d59c4dcff067f628157a75e9e28c1930f31537fb1dd058ddd9906322c1154f29436252a36bc50595578bfee9bcad4a9705c85726a" + } + ], + "licenses" : [ + { + "license" : { + "id" : "EPL-1.0" + } + }, + { + "license" : { + "name" : "GNU Lesser General Public License", + "url" : "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" + } + } + ], + "purl" : "pkg:maven/ch.qos.logback/logback-core@1.4.14?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/ch.qos.logback/logback-core@1.4.14?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-test", + "version" : "3.2.1", + "description" : "Spring Boot Test", + "hashes" : [ + { + "alg" : "MD5", + "content" : "5c793b3b61ba2637840a6c865aa2901e" + }, + { + "alg" : "SHA-1", + "content" : "142fbe3cfe3370c57d0ed55cca0d8d96e1d6f26e" + }, + { + "alg" : "SHA-256", + "content" : "0fb27aeb59ab757e60c48f9810d0ab54dc858a4c1cd9cc75b4ad07456c9c3e7c" + }, + { + "alg" : "SHA-512", + "content" : "975428c3f753ec1375f9c0ca2c47756a22896cc510193b53f7a8501255634a2e0d2165e699055667f4127cbaa8e79c9c128aef6de0854fccd4e158dce4422939" + }, + { + "alg" : "SHA-384", + "content" : "c3abb4c4a9961cab0fde6119d5b86755ea0c43fdd266b51d369a8544818463ce1876df2b13b0a2478f36b1e5282a305d" + }, + { + "alg" : "SHA3-384", + "content" : "641f9090f373f299d61bf54dd06e7ea15217c5b06424e970ddaed1f64e2a25aae74bdc10e04c9c4e934f2a3a5ab95c4b" + }, + { + "alg" : "SHA3-256", + "content" : "45d05dd704757c997b11f13961762e371309bec11292b32af3f244ca3b49642c" + }, + { + "alg" : "SHA3-512", + "content" : "53001dd1610347d6cf92f737067271fe3c638828a0b1e0b6aca62429e97a85018daf6ab3e10f065acd79ed7c93dc3a4c57f89eda3e2feb48ab548ca7e906b414" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-test@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-test@3.2.1?type=jar" + }, + { + "group" : "io.micrometer", + "name" : "micrometer-jakarta9", + "version" : "1.12.1", + "description" : "Module for Jakarta 9+ based instrumentations", + "hashes" : [ + { + "alg" : "MD5", + "content" : "0e247019d91d3c357b440436e1af2fba" + }, + { + "alg" : "SHA-1", + "content" : "2dc7257970669fa45e342b0b36902d868af2dbed" + }, + { + "alg" : "SHA-256", + "content" : "e8c66d7aee8fbc8a9d2e15c6c53df92bd7ecbf94f1ca8562d62d9a2693aa4633" + }, + { + "alg" : "SHA-512", + "content" : "3a481de081b216d42bd9b741b3a830c93d917c5ae8a11f670785b53b55cff601e1cdfd037b12d8b95cd8557c4493d6e04e51980860e421f444f2b4a953070969" + }, + { + "alg" : "SHA-384", + "content" : "cdbca1958c2502bcdad18446401f7f21ec2bc2c4055fd2fafa8fdad30cb8c8fd9aa9863de5ddd9cb852cafda487d29b0" + }, + { + "alg" : "SHA3-384", + "content" : "13f29eca056350277ee80d786945386abdd1c8b7c04dc35a94c7ac8146e7b6cafa617652fca15e79b8376341ae5576d0" + }, + { + "alg" : "SHA3-256", + "content" : "f095b2247aa3ada3c824121b4720dcceb3b65f7a2b9e880acdedc613a62d9be6" + }, + { + "alg" : "SHA3-512", + "content" : "773cd6f711b68a27d958ecb01f85d8480835014d23d3484e69e1c63bc736f50697bd6cf7d5e7776a13ae946ed10621334cb84ba8357b26d45cb6c9990826f993" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/io.micrometer/micrometer-jakarta9@1.12.1?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/io.micrometer/micrometer-jakarta9@1.12.1?type=jar" + }, + { + "group" : "com.fasterxml.jackson.module", + "name" : "jackson-module-parameter-names", + "version" : "2.15.3", + "description" : "Add-on module for Jackson (http://jackson.codehaus.org) to support introspection of method/constructor parameter names, without having to add explicit property name annotation.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "495868f770056602bfe13ea781656f03" + }, + { + "alg" : "SHA-1", + "content" : "8d251b90c5358677e7d8161e0c2488e6f84f49da" + }, + { + "alg" : "SHA-256", + "content" : "baf1a3156a23cb407e05374161a07ed8560f78a7ae249955de04a9a2fa2d0f2b" + }, + { + "alg" : "SHA-512", + "content" : "497b08f55f601b7ff6294e0b8307e015e60ad45c7949bd80ed3f5ee19daa93fad7f0b5a93abb8082ec46480667ab8539337633213d0fd5992e4a10c710f0a7aa" + }, + { + "alg" : "SHA-384", + "content" : "1a50ca6c0e0b4e3ecf83e3f327670a3b36f2b847b46ab5e193e9bccc36fee3bd41c1aa937dda88c4936339eafc73fc93" + }, + { + "alg" : "SHA3-384", + "content" : "30d05f1dd78a796ba4abb79be93dae2d7e4e5269de18d85a9d89b1c92f6ff8fe09ac1953a48a0b2b51906bbaadb56fca" + }, + { + "alg" : "SHA3-256", + "content" : "9e50d137efbe3de957a64fa4b90532cbb67efc2b09ba11824362315d1f57b812" + }, + { + "alg" : "SHA3-512", + "content" : "9418c5c18e429e201d7f6a4d5f05a52a433dbe4bf72a82e3ea69010c1d4b9ec99fc651804f2f8339a53841f88416318e3ab7fb1a07391cde5ea745ebbfcf98bc" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/com.fasterxml.jackson.module/jackson-module-parameter-names@2.15.3?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/com.fasterxml.jackson.module/jackson-module-parameter-names@2.15.3?type=jar" + }, + { + "group" : "org.junit.platform", + "name" : "junit-platform-engine", + "version" : "1.10.1", + "description" : "Module \"junit-platform-engine\" of JUnit 5.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "4d571057589cd109f3f4bedf7bbf5e7a" + }, + { + "alg" : "SHA-1", + "content" : "f32ae4af74fde68414b8a3d2b7cf1fb43824a83a" + }, + { + "alg" : "SHA-256", + "content" : "baa48e470d6dee7369a0a8820c51da89c1463279eda6e13a304d11f45922c760" + }, + { + "alg" : "SHA-512", + "content" : "52ea2f11ec2ef0457384335d1b09263f4efecf63d9df99c5f8396f74d972722c51f8f766370e85e030f4476e805dac72603296942593c5bbe56993454b9d8e30" + }, + { + "alg" : "SHA-384", + "content" : "7c520e04c995a47c19c94fdcbbcba9bb117696191e6a989a82d9f960e0e315e5cf87d28022ac5cb2701c85d5f38eefde" + }, + { + "alg" : "SHA3-384", + "content" : "79d4f2fb987d6a44174dda99b1bd827e8dfd0399495c3e994371d4f69631212768dee8b891313aac89045388a1bed9db" + }, + { + "alg" : "SHA3-256", + "content" : "5c3fcec688368188688cb6949c1230c2822211e53f3a65b7b3abf4a38051798b" + }, + { + "alg" : "SHA3-512", + "content" : "30a0834e88bbc62287e5f49302c4a07b6da1bf4d9774faddbe7e606fb296c0dcd71c7e90ef8fff3e18dd050e5a19f7b903c91674ff4806cdb97111e4f0cfc199" + } + ], + "licenses" : [ + { + "license" : { + "id" : "EPL-2.0" + } + } + ], + "purl" : "pkg:maven/org.junit.platform/junit-platform-engine@1.10.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/junit-team/junit5" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.junit.platform/junit-platform-engine@1.10.1?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-autoconfigure", + "version" : "3.2.1", + "description" : "Spring Boot AutoConfigure", + "hashes" : [ + { + "alg" : "MD5", + "content" : "29fb14fe1d383588e87a73da4508604d" + }, + { + "alg" : "SHA-1", + "content" : "b100d2d21d45dddd740d496357ca6f3813d777d0" + }, + { + "alg" : "SHA-256", + "content" : "371f0f36d226a8db972c37c73f0a0896ee4d3e77c29b54dbce8a64af731a6e53" + }, + { + "alg" : "SHA-512", + "content" : "42bc3a99f9c9ffc9fd08447303a946fce1c81e3a869a5788c7d3b669536455eedc8009428ae4660d66b0d74ab170968b6aad905455b53342d7c521e7ec4c262f" + }, + { + "alg" : "SHA-384", + "content" : "f47603c4009bb767f9d5cb0bf3fcba69167daab53cbfafd217450977464073e8b814c76aa545b1eccee587201fe93eef" + }, + { + "alg" : "SHA3-384", + "content" : "bbd77376c9a46de290522662f327a8e6b0221a6c0105632e73b527799bec8a162d98948d0d05b32509650b4f47a6465e" + }, + { + "alg" : "SHA3-256", + "content" : "9e9549dda419ad6f482e3b376c595c69ccb93cebf365c1b18a59bf226c3264db" + }, + { + "alg" : "SHA3-512", + "content" : "1473f0de013447eb40d0b6d2a30013d2a7d262ce1e0259d4a27f88e421e5538234a46704f88b27c227aab7ae2261995a73f4075a6a43124e39c7234c6d164fe2" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.2.1?type=jar" + }, + { + "group" : "org.junit.jupiter", + "name" : "junit-jupiter-engine", + "version" : "5.10.1", + "description" : "Module \"junit-jupiter-engine\" of JUnit 5.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "71d86cd027062c4da0796c2493ae94fe" + }, + { + "alg" : "SHA-1", + "content" : "6c9ff773f9aa842b91d1f2fe4658973252ce2428" + }, + { + "alg" : "SHA-256", + "content" : "02930dfe495f93fe70b26550ace3a28f7e1b900c84426c2e4626ce020c7282d6" + }, + { + "alg" : "SHA-512", + "content" : "1fcc9406d1e0301e27538757c9649545d784e83743a8800932971881cfd78a14a264ad13c0b92fad9ae1be50963c540427a19cb2d1fee06888ef48105aad4c8b" + }, + { + "alg" : "SHA-384", + "content" : "6657ac1bb11d7a40bbcb020add01e57edbbc521645116908d857074d9ea319eab3e7b7f2e9fa1ff8df08b5db3774f4dc" + }, + { + "alg" : "SHA3-384", + "content" : "607313914c11274c577b0aaaae6c68aa6ecf25d8302f55d4e334aa6b58df2e543d2399785e2019a56b85aac7716c9623" + }, + { + "alg" : "SHA3-256", + "content" : "be3560971111d3f548bef24aa6660ec2a126fd17b3bd68b7deeb1ab48735a9d1" + }, + { + "alg" : "SHA3-512", + "content" : "4ba6cb70f8fc1918dcedc874340488909c48e0f976d1834ec433f4b5c6cff55b16a996a0443a1b68a0d0ad84a37bf51386633905628728bde08b5820ee67dfaa" + } + ], + "licenses" : [ + { + "license" : { + "id" : "EPL-2.0" + } + } + ], + "purl" : "pkg:maven/org.junit.jupiter/junit-jupiter-engine@5.10.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/junit-team/junit5" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.junit.jupiter/junit-jupiter-engine@5.10.1?type=jar" + }, + { + "group" : "io.micrometer", + "name" : "micrometer-observation", + "version" : "1.12.1", + "description" : "Module containing Observation related code", + "hashes" : [ + { + "alg" : "MD5", + "content" : "b55c9caac5c8f778996937c3f6cf4101" + }, + { + "alg" : "SHA-1", + "content" : "fbd0e0e9b6a36effd53e0eee35b050ed1f548ae5" + }, + { + "alg" : "SHA-256", + "content" : "48f6607b248e8b77ee9f7b3934f70124471daf947b30480c1b9c0e9d9f996c83" + }, + { + "alg" : "SHA-512", + "content" : "3e12e101b161715e5c30eb166578de7ae76749a2c4d22435bc57395be14d1313073d5fa76dcc883ed807d4982d343addfa24540e283cd0432f1428ff00962d98" + }, + { + "alg" : "SHA-384", + "content" : "791f99b503d7fa16733a74d92ebd02e72dfce4d648245f149f5363019beabe7e317e7ef0df0bcb67832dbab03943ff53" + }, + { + "alg" : "SHA3-384", + "content" : "ccb83eb15cd8004295bdb40b948cb9d3efaa4281b0d02a97b49970a2699822d7cd15b83206c236c3a41e49063caa5ded" + }, + { + "alg" : "SHA3-256", + "content" : "773e3647329d707d79efcb92c88cbe0719b4dcd820f06983e6e283e666875acc" + }, + { + "alg" : "SHA3-512", + "content" : "922f6c81c3a7b8e8c1296eb3359723161e91bac646d4bef954904c70a40ccfd9dc95c783715fcedc788f67ef06ea5514a918c7cc6811f2bdd39eb011a36698e7" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/io.micrometer/micrometer-observation@1.12.1?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/io.micrometer/micrometer-observation@1.12.1?type=jar" + }, + { + "group" : "org.awaitility", + "name" : "awaitility", + "version" : "4.2.0", + "description" : "A Java DSL for synchronizing asynchronous operations", + "hashes" : [ + { + "alg" : "MD5", + "content" : "8f3644827b9e3037de42068c57006260" + }, + { + "alg" : "SHA-1", + "content" : "2c39784846001a9cffd6c6b89c78de62c0d80fb8" + }, + { + "alg" : "SHA-256", + "content" : "2d23b79211fdd19036f6940cc783543779320aaf86f38d6e385a2ff26da41272" + }, + { + "alg" : "SHA-512", + "content" : "4c422b4aef3dfceb040898f45cd1b2efb7bbf213ef9487334a0d0e674e494e120fef61348f8a81ce726f2f66dc426e133917de20c52b5d39d792e2dca7bc82d8" + }, + { + "alg" : "SHA-384", + "content" : "11d15d6efb32707cae528eefb8fa4ab7820649ed528c3447660efd984518ee2906421af5ee76ea8181c904d594e8e719" + }, + { + "alg" : "SHA3-384", + "content" : "71eff4441379fb1d13bec42264d48dd1ed4817c7a226a4ef1e5255e5afcc8e5e61aa92677ae98fdce2bf4824b4dbe4fc" + }, + { + "alg" : "SHA3-256", + "content" : "4fc8b38b34625336be520d2be1edcab4c8dd8e0667fecb2aa6aea83b9bad7f28" + }, + { + "alg" : "SHA3-512", + "content" : "074f8629ab499c28155e505513e0a25c83ce722747d196966eac6327de604853503ca5f54b84effe8e2e3ab78d9ce285bdba82bf738ff8bff0f1009549230521" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.awaitility/awaitility@4.2.0?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.awaitility/awaitility@4.2.0?type=jar" + }, + { + "group" : "org.hamcrest", + "name" : "hamcrest", + "version" : "2.2", + "description" : "Core API and libraries of hamcrest matcher framework.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "10b47e837f271d0662f28780e60388e8" + }, + { + "alg" : "SHA-1", + "content" : "1820c0968dba3a11a1b30669bb1f01978a91dedc" + }, + { + "alg" : "SHA-256", + "content" : "5e62846a89f05cd78cd9c1a553f340d002458380c320455dd1f8fc5497a8a1c1" + }, + { + "alg" : "SHA-512", + "content" : "6b1141329b83224f69f074cb913dbff6921d6b8693ede8d2599acb626481255dae63de42eb123cbd5f59a261ac32faae012be64e8e90406ae9215543fbca5546" + }, + { + "alg" : "SHA-384", + "content" : "89bdcfdb28da13eaa09a40f5e3fd5667c3cf789cf43e237b8581d1cd814fee392ada66a79cbe77295950e996f485f887" + }, + { + "alg" : "SHA3-384", + "content" : "0d011b75ed22fe456ff683b420875636c4c05b3b837d8819f3f38fd33ec52b3ce2f854acfb7bebffc6659046af8fa204" + }, + { + "alg" : "SHA3-256", + "content" : "92d05019d2aec2c45f0464df5bf29a2e41c1af1ee3de05ec9d8ca82e0ee4f0b0" + }, + { + "alg" : "SHA3-512", + "content" : "4c5cbbe0dcaa9878e1dc6d3caa523c795a96280cb53843577164e5af458572cde0e82310cf5b52c1ea370c434d5631f02e06980d63126843d9b16e357a5f7483" + } + ], + "licenses" : [ + { + "license" : { + "id" : "BSD-3-Clause", + "url" : "https://opensource.org/licenses/BSD-3-Clause" + } + } + ], + "purl" : "pkg:maven/org.hamcrest/hamcrest@2.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/hamcrest/JavaHamcrest" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.hamcrest/hamcrest@2.2?type=jar" + }, + { + "group" : "org.junit.jupiter", + "name" : "junit-jupiter-api", + "version" : "5.10.1", + "description" : "Module \"junit-jupiter-api\" of JUnit 5.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "c6b8b04f2910f6cef6ac10846f43a92d" + }, + { + "alg" : "SHA-1", + "content" : "eb90c7d8bfaae8fdc97b225733fcb595ddd72843" + }, + { + "alg" : "SHA-256", + "content" : "60d5c398c32dc7039b99282514ad6064061d8417cf959a1f6bd2038cc907c913" + }, + { + "alg" : "SHA-512", + "content" : "b1fef44d4aa781bb119ab723c3c2a6f0d27efc4493a1fa26b603c7c7a8884c4d6274bccec6536f120d55f876f8d052aaf6cc003074c27cc704deb2c4bc08b6f0" + }, + { + "alg" : "SHA-384", + "content" : "0fd81f893be859a50766bfbf3bd74bd7d359c6d481b7fe3099e220402f585d3d46b6ad42a36b1d88eefbb6fd27a3cefa" + }, + { + "alg" : "SHA3-384", + "content" : "5e13ba92f757499ca52d719869d318cade9bde9c948ee9c68d753a21ec273f7b56ad68ff8cb281614efeef1d4c479db0" + }, + { + "alg" : "SHA3-256", + "content" : "997c9e0cc57d61a85a8eec568d0f014d47af5bf655602a2c3518b6530b089905" + }, + { + "alg" : "SHA3-512", + "content" : "e97c3e2c1faa1f77b174ef6ce7b24a2339e547f5976a4e40348653e84498e0c3bb96068447facef6df6b54d4af34b807f19b4d2bb1d31e26f97d6dae07843bf6" + } + ], + "licenses" : [ + { + "license" : { + "id" : "EPL-2.0" + } + } + ], + "purl" : "pkg:maven/org.junit.jupiter/junit-jupiter-api@5.10.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/junit-team/junit5" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.junit.jupiter/junit-jupiter-api@5.10.1?type=jar" + }, + { + "group" : "org.skyscreamer", + "name" : "jsonassert", + "version" : "1.5.1", + "description" : "A library to develop RESTful but flexible APIs", + "hashes" : [ + { + "alg" : "MD5", + "content" : "60a7d3d352b233487d735f4b86802717" + }, + { + "alg" : "SHA-1", + "content" : "6d842d0faf4cf6725c509a5e5347d319ee0431c3" + }, + { + "alg" : "SHA-256", + "content" : "1e9a7c443d0dd579906646d767f3701918a78cb88a93112f528305fc9095d261" + }, + { + "alg" : "SHA-512", + "content" : "51221bbeb30ed47840494d31128e605e29a96249f3e4b9c00985a865f8ed58b73e045772e3b0af74a35018a9dd004b5cc2182344b9154d9a50604ad1a073f2dd" + }, + { + "alg" : "SHA-384", + "content" : "941cec8d4ce1fab19f32b36f0afd2c7de27325659c5f85ab90948182098de4afe327b49cea57b946f18671af8037aefd" + }, + { + "alg" : "SHA3-384", + "content" : "3fb46460472c82901ec6fa5deab84eea18369e74aad920e3ee9e0fb8a859e8397a287428d0bf1c2b137368b6579c5c4b" + }, + { + "alg" : "SHA3-256", + "content" : "24b6c0f73ee51c19d5fdae62588dff9d0bf172da7e6ad1595e275920c8de829c" + }, + { + "alg" : "SHA3-512", + "content" : "686fb7b0ee0849bc78b6eeb74a941795252cec9a62ea153e6bd1e77d51fb6ee14f64970cb52cc13f581d21b166c6f1b28b8fbc4c7ae0c3b225df385a92635f0c" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.skyscreamer/jsonassert@1.5.1?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.skyscreamer/jsonassert@1.5.1?type=jar" + }, + { + "group" : "org.mockito", + "name" : "mockito-junit-jupiter", + "version" : "5.7.0", + "description" : "Mockito JUnit 5 support", + "hashes" : [ + { + "alg" : "MD5", + "content" : "ab44b412aa650651eedf323e945fe367" + }, + { + "alg" : "SHA-1", + "content" : "ac2d6a3431747a7986b8f4abef465f72bf3a21ae" + }, + { + "alg" : "SHA-256", + "content" : "e2416a260c3a45ba77d674cfe27d49428e57efe21a7b2ddeae733ebb5c5d85bf" + }, + { + "alg" : "SHA-512", + "content" : "39cccb119c0767f4e443567873af78d882c4a1e99c553ad39d4efae2698933de602d9c0046a70a05be552793569d4b43e75c2a798fd1f7f0a8c5ab34db8b9c94" + }, + { + "alg" : "SHA-384", + "content" : "f02eeae7fe867ff8580164b4d20d269efbad2a18ba2ffc8ba9744c603c589fb5155399361b14ab2a6549d605d26a4694" + }, + { + "alg" : "SHA3-384", + "content" : "6b95b5f5efcc97a2531c9c108e53fe5465ae0249d46988fe7fd47df7ad4d154de40a66471a996ae7abd75bd0c1f6c9b4" + }, + { + "alg" : "SHA3-256", + "content" : "30978340a8749b094a5b0f42dffbb91e72f7d7eaea6924efce13f47a44048fdf" + }, + { + "alg" : "SHA3-512", + "content" : "80601cb4de8850a0255b7c28cb7993be667a238d961fd281c7152b7ba40eec399240a2ab9d686cd1463872652876e88ef221d699acb61a2acf041c9f187053ab" + } + ], + "licenses" : [ + { + "license" : { + "id" : "MIT", + "url" : "https://opensource.org/licenses/MIT" + } + } + ], + "purl" : "pkg:maven/org.mockito/mockito-junit-jupiter@5.7.0?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "build-system", + "url" : "https://github.com/mockito/mockito/actions" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/mockito/mockito/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/mockito/mockito.git" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.mockito/mockito-junit-jupiter@5.7.0?type=jar" + }, + { + "group" : "org.apache.logging.log4j", + "name" : "log4j-api", + "version" : "2.21.1", + "description" : "The Apache Log4j API", + "hashes" : [ + { + "alg" : "MD5", + "content" : "b5e9bf76dd128b37666ecd9a252b50ec" + }, + { + "alg" : "SHA-1", + "content" : "74c65e87b9ce1694a01524e192d7be989ba70486" + }, + { + "alg" : "SHA-256", + "content" : "1db48e180881bef1deb502022006a025a248d8f6a26186789b0c7ce487c602d6" + }, + { + "alg" : "SHA-512", + "content" : "4cbf72fbea7009ec2fc363aae2ccfe11ea2023967d65be39335eedd1d8917b7402eeb2219efd5a1f11d03833dd1f57eecab428616b03124ef2266c6cca06ac56" + }, + { + "alg" : "SHA-384", + "content" : "edd8429f2f88476afbfa63314f7846d1341a4cfc58d3abe55b3cda236613feb6859f711e0ae60bd7821b74e488fb0666" + }, + { + "alg" : "SHA3-384", + "content" : "b67292ff0c7ca988a4b40b6ec14582ef579990d275a37944ac9572ecdfd4bf6e9fff2ab982b21d159a1135c21a32495f" + }, + { + "alg" : "SHA3-256", + "content" : "b2641c2db75d3c676e451a53b5f60dfaf030a84e0230747bd50d00414f8a27b3" + }, + { + "alg" : "SHA3-512", + "content" : "f1f4d9c48a9d088460e1ad3d71126b243069e522588cdc5534ac8f201ec0574287e8f1fba182f8925ee75b78726269487cc0160f7f8bd1aa21cc8e587fdb5c4a" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0", + "url" : "https://www.apache.org/licenses/LICENSE-2.0" + } + } + ], + "purl" : "pkg:maven/org.apache.logging.log4j/log4j-api@2.21.1?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.apache.logging.log4j/log4j-api@2.21.1?type=jar" + }, + { + "group" : "org.assertj", + "name" : "assertj-core", + "version" : "3.24.2", + "description" : "Rich and fluent assertions for testing in Java", + "hashes" : [ + { + "alg" : "MD5", + "content" : "b596a91049e6ce526bc5595c1bebea2c" + }, + { + "alg" : "SHA-1", + "content" : "ebbf338e33f893139459ce5df023115971c2786f" + }, + { + "alg" : "SHA-256", + "content" : "df3d0b348f1fe806bdddcb10fa4ae63c6679e9888d4bc7055f09848517976aa3" + }, + { + "alg" : "SHA-512", + "content" : "d8e3159effc7954258f2398e26c34eab6c243675408c7b5fcd7ed04a7b7dc06006514510ad15be9e7725f724cbf6e5c534cb22cbfb7c0aed71b81d4ed5755220" + }, + { + "alg" : "SHA-384", + "content" : "4f06196b5329e215282476d8e3aa5065092924bccb91da4eb0aa2e8fcd2509f249369654f0c17b59c38f11b878a305e3" + }, + { + "alg" : "SHA3-384", + "content" : "3029ae58aef975843e9205f130dcdd8f8e7da5ff1bfad62b7d918ffe52b74a3c34a859af13393abe122124a9132f3feb" + }, + { + "alg" : "SHA3-256", + "content" : "2db6965251a03be26f5baa83792a002444b4de34aaaefb0e6cf3cccf0a20939e" + }, + { + "alg" : "SHA3-512", + "content" : "fa3ffb87bc40c3f881fb477d41c8565cbc1ce46ead2030442674bb86a425c722b75fce5bb3c22425b21cc3122ac46e0f28b2eaba2bcf5d5ddcb31f47d967b890" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.assertj/assertj-core@3.24.2?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.assertj/assertj-core@3.24.2?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-starter-web", + "version" : "3.2.1", + "description" : "Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container", + "hashes" : [ + { + "alg" : "MD5", + "content" : "8a6aea9e1fbdbabbd00e35038739200f" + }, + { + "alg" : "SHA-1", + "content" : "e27e36d4222fd4d589e634e1c7f5f09f0316147c" + }, + { + "alg" : "SHA-256", + "content" : "2f14d3a4a0ae3ad634bcfa07117542001c1789c0bdce3504baee8f2bc45ef006" + }, + { + "alg" : "SHA-512", + "content" : "2fcfc8d9abfcd0518b6755737c6e520544600b3c26b42b60d1ab3fcfceb31582d5dbcd5d86a98ec312442d335e49f0db0ecf21d8e99089ef41d962ece42d97ae" + }, + { + "alg" : "SHA-384", + "content" : "e3c8cb02b18ea5b7aa2a7c9c97c62385fcaa8fc53f41d7bf0b98d262a10473e9674924ad287964f6e58fb9c5915da8d1" + }, + { + "alg" : "SHA3-384", + "content" : "713c9200480f14fd4bcd073d43ac7900771c9d36b4e72b50ddf80733670948ad57700ea37336de5078d16557e426de79" + }, + { + "alg" : "SHA3-256", + "content" : "3346906c7b4b455c00226fd9804a237d3a667523800e0c2083413fde4592b7c3" + }, + { + "alg" : "SHA3-512", + "content" : "99ba750d8e1c97636eb47122ce259b1bc9b91c51fecc50d13604f7ae7096a20f1fa38562d83786c1d4c3ba07ff94b286d869d671a5f0d00fd6c378f032332f63" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-starter-web@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-web@3.2.1?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-starter-test", + "version" : "3.2.1", + "description" : "Starter for testing Spring Boot applications with libraries including JUnit Jupiter, Hamcrest and Mockito", + "hashes" : [ + { + "alg" : "MD5", + "content" : "f808bed72032367a1170477e74e57f7e" + }, + { + "alg" : "SHA-1", + "content" : "e6a20062864e3a9a0bba0ac3b0c5a819453045b9" + }, + { + "alg" : "SHA-256", + "content" : "2e0a11d69fed912dd6f5a6b0f492ce1530e2ac932de9588d4b7df0ab548eea0a" + }, + { + "alg" : "SHA-512", + "content" : "83c1f7e7b404be7b9f603a386ca2d0c84c7e0b73190ffb19ef2b0dff5cbc1ebd57ce73be663ee01ed28f1c4f41d91db7f070d7b37a3f2ae6b9b6814dd930a089" + }, + { + "alg" : "SHA-384", + "content" : "3a5159cad10587b250f0a1f7cf6ebea9f2cbda539c008094fec1dff47eeced5b2119be3ad007eab0598445b9282164f4" + }, + { + "alg" : "SHA3-384", + "content" : "9303b808eed6e0425d5c7e968601960d9ff2e0c2fd840ffd041b01f0499b1f86ae05c50e968e925374a54b26e9298410" + }, + { + "alg" : "SHA3-256", + "content" : "a18f18bd0a077a38ea0b3aeae85730b9f104d65d4d48f88210f2954c45739eae" + }, + { + "alg" : "SHA3-512", + "content" : "e021bfc51b8d6b8cdc1b44cf5042778c208db09b349250e33630b28ace2ed97d52bd89750ab70e14b650578f379a7e6172838c83bbb2c974394132cb80381f27" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-starter-test@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-test@3.2.1?type=jar" + }, + { + "group" : "jakarta.activation", + "name" : "jakarta.activation-api", + "version" : "2.1.2", + "description" : "${project.name} ${spec.version} Specification", + "hashes" : [ + { + "alg" : "MD5", + "content" : "1af11450fafc7ee26c633d940286bc16" + }, + { + "alg" : "SHA-1", + "content" : "640c0d5aff45dbff1e1a1bc09673ff3a02b1ba12" + }, + { + "alg" : "SHA-256", + "content" : "f53f578dd0eb4170c195a4e215c59a38abfb4123dcb95dd902fef92876499fbb" + }, + { + "alg" : "SHA-512", + "content" : "383283f469aba01a274591e29f1aa398fefa273bca180162d9d11c87509ffb55cb2dde51783bd6cae6f2c4347e0ac7358cf11f4c85787d5d2857354b9e29d877" + }, + { + "alg" : "SHA-384", + "content" : "e34ac294c104cb67ac06f7fc60752e54a881c04f68271b758899739a5df5be2d2d0e707face2705b95fa5a26cedf9313" + }, + { + "alg" : "SHA3-384", + "content" : "ffd74b0335a4bfdd9a0c733c77ecdfa967d5280500c7d2f01e2be8499d39a9f0cd29c9063ae634223347bb00f4e60c33" + }, + { + "alg" : "SHA3-256", + "content" : "c97236eaebb15b8aefa034b23834eaeed848dacf119746c6d87832c47581e74d" + }, + { + "alg" : "SHA3-512", + "content" : "147dfa2bf46bb47c81462c36ac6612f9f807169ffb785e2bbd45538205c5713f33af4373f3324a2063350c2367baff37e9c2cf085c38c96870ad88c60a7fbea4" + } + ], + "licenses" : [ + { + "license" : { + "id" : "BSD-3-Clause" + } + } + ], + "purl" : "pkg:maven/jakarta.activation/jakarta.activation-api@2.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "issue-tracker", + "url" : "https://github.com/jakartaee/jaf-api/issues/" + }, + { + "type" : "vcs", + "url" : "https://github.com/jakartaee/jaf-api" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/jakarta.activation/jakarta.activation-api@2.1.2?type=jar" + }, + { + "group" : "io.micrometer", + "name" : "micrometer-core", + "version" : "1.12.1", + "description" : "Core module of Micrometer containing instrumentation API and implementation", + "hashes" : [ + { + "alg" : "MD5", + "content" : "30dcc7ea6a0e99663e5908bce7371206" + }, + { + "alg" : "SHA-1", + "content" : "b72e9a2f26355ecb8ababa0148a5c3c4ac648f14" + }, + { + "alg" : "SHA-256", + "content" : "97d0a5309e9c584f4dec6f549a383ae25d8727abff43cff8e0b90580ee797b67" + }, + { + "alg" : "SHA-512", + "content" : "2acd080a1b40cb5a1ca0b7266af829392e318291dab57e6239ca97d15112cc206992b78316f4c02400454124519a084341e4de55dd729c96805b3fb196707a64" + }, + { + "alg" : "SHA-384", + "content" : "9a3998a9a219fc049ace5731fde94944948332eccbe589dbc34456057a2df173ef17e3b0642233e513d3118bcfba565f" + }, + { + "alg" : "SHA3-384", + "content" : "22c97b3fb49d299ebc36674a6e32d9fd05726d88109ede3323e3e97e82100d1ed6d7010e86749a2b07ffe994fb3b7833" + }, + { + "alg" : "SHA3-256", + "content" : "3b272686c89e274b5944715db002871e072f0f8c7099228f6d6909656b6ba3f4" + }, + { + "alg" : "SHA3-512", + "content" : "b1d82086950a2e61ed3e016fa962af2e9c3b2d543c4c311d40d9f7fc402b9beb3e5d09261d336cb1634b186f723bf584874f3fb8a29c38198d5ddd2b386c4413" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/io.micrometer/micrometer-core@1.12.1?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/io.micrometer/micrometer-core@1.12.1?type=jar" + }, + { + "group" : "org.junit.jupiter", + "name" : "junit-jupiter-params", + "version" : "5.10.1", + "description" : "Module \"junit-jupiter-params\" of JUnit 5.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "5e8e17f6f2a5dedb42d9846a3352dd31" + }, + { + "alg" : "SHA-1", + "content" : "c8f15d4e99940c4564098af78c10809c00fdca06" + }, + { + "alg" : "SHA-256", + "content" : "c8cf62debcbb354deefe1ffd0671eff785514907567d22a615ff8a8de4522b21" + }, + { + "alg" : "SHA-512", + "content" : "dbd8a3bca0a03b6eef54de2b489685c8125e0c6f23cbdb633174b21e07cc7b97a24b55dcb5b60ec1a496683a918bfdf1ea0459950689e3755aa965ea9e106ee9" + }, + { + "alg" : "SHA-384", + "content" : "882b3106163d7c195867e08db9948a0997e1469a23c847bff523efa30a9b274c0588f8228fca98c78abf9b61709a7ff2" + }, + { + "alg" : "SHA3-384", + "content" : "6e4e9a7dbb32cc3f16f21a14fe036aa13488c5b94e3cb6cc53b417c4588b90b5ae118caa3eb9f4bc9c513d06e2c1f408" + }, + { + "alg" : "SHA3-256", + "content" : "171a08027b527e3be1ad66082405eacf4a55746dd983c46d9ff7ee5552276615" + }, + { + "alg" : "SHA3-512", + "content" : "c435b4a17208b67f6fa35ebe74872c3d2c3557b290437bb682ac86701402bbe17d0e53446c674bb94c7feaae4bbfa99d888c7bf7181707e27fe08ff7934c00f6" + } + ], + "licenses" : [ + { + "license" : { + "id" : "EPL-2.0" + } + } + ], + "purl" : "pkg:maven/org.junit.jupiter/junit-jupiter-params@5.10.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/junit-team/junit5" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.junit.jupiter/junit-jupiter-params@5.10.1?type=jar" + }, + { + "group" : "com.fasterxml.jackson.core", + "name" : "jackson-databind", + "version" : "2.15.3", + "description" : "General data-binding functionality for Jackson: works on core streaming API", + "hashes" : [ + { + "alg" : "MD5", + "content" : "5f453c55f127690fa8491ce347aa055c" + }, + { + "alg" : "SHA-1", + "content" : "a734bc2c47a9453c4efa772461a3aeb273c010d9" + }, + { + "alg" : "SHA-256", + "content" : "c3c53333a2172a80678bda1803e39cff45bec6ae3e9c7d4f44a81ec4e2ab18dc" + }, + { + "alg" : "SHA-512", + "content" : "490ccc99a9c28238fe28455bae08196b83df034cae8a1947d27ff89e500a5d812cf4be36c61942e647c62ad540d8eb4428f49855f0cc8db0ee9e7a5b12ba2454" + }, + { + "alg" : "SHA-384", + "content" : "b53f4a6fddbf677a8d02c65e9f0a96372140c68286d68740987fb462f946de878abaeea421d3e4716751f04d88c16ad1" + }, + { + "alg" : "SHA3-384", + "content" : "5a407605544e303abf8a212651bf5e5594fa313804a399bf03401f449c0baf26ef965def518b05c275b2f38f18457739" + }, + { + "alg" : "SHA3-256", + "content" : "d0880002ac261d181e663499627fcce5763f3a9120bb76e758adfb9939d17c98" + }, + { + "alg" : "SHA3-512", + "content" : "e97bfe0e9117dad82e0799cb2c105c4553c6aa5ce9abdefee4fd5b584876555309aafa9a19ca586e928e292e32f23452849a10da7364966e11e4f7afcc6aec78" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.3?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/FasterXML/jackson-databind" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.3?type=jar" + }, + { + "group" : "org.slf4j", + "name" : "jul-to-slf4j", + "version" : "2.0.9", + "description" : "JUL to SLF4J bridge", + "hashes" : [ + { + "alg" : "MD5", + "content" : "24f86e89ee3f71ea91f644150c507740" + }, + { + "alg" : "SHA-1", + "content" : "09ef7c70b248185845f013f49a33ff9ca65b7975" + }, + { + "alg" : "SHA-256", + "content" : "69b4e5f8d3bd3f6f54367d19f2c1ee95dd5877802f12d868282e218dd76b00bf" + }, + { + "alg" : "SHA-512", + "content" : "c1cdfbc0c867917d65ab58e039b01c5b119368aef82abcb406d91646da208a4bfad91831a5a425eacfa8253ccd5713a9d4325d45665288483929cce7a6a56eb7" + }, + { + "alg" : "SHA-384", + "content" : "a8d45375ec27c0833a441f28055ba2c07b601fb7a9bc54945672fc2f7b957d8ada5d574ab607ef3f9a279c32c0a7b0a5" + }, + { + "alg" : "SHA3-384", + "content" : "d65edaa8f6ad8bbea84617e414ede438ec4aafffa3734f2d38e6dd0a01c1f42f9397acaf6291a73489fb252d7369c71e" + }, + { + "alg" : "SHA3-256", + "content" : "69416188261a8af7cb686a6d68a809f4e7cab668f6b12d4456ce8fd9df7a1c25" + }, + { + "alg" : "SHA3-512", + "content" : "52d54c80e3934913a184efc091978201934b0ee47a6b4f9c8555a4d549becd26957e17592aff46dfdcfcbcb2313bfad09699ee84cfd7112ed2a00422c87399e8" + } + ], + "licenses" : [ + { + "license" : { + "id" : "MIT", + "url" : "https://opensource.org/licenses/MIT" + } + } + ], + "purl" : "pkg:maven/org.slf4j/jul-to-slf4j@2.0.9?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.slf4j/jul-to-slf4j@2.0.9?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot", + "version" : "3.2.1", + "description" : "Spring Boot", + "hashes" : [ + { + "alg" : "MD5", + "content" : "6f7384977eae04c804b1062df9217959" + }, + { + "alg" : "SHA-1", + "content" : "faa2ce019bee68a8d17529d0a08ebc427f927e13" + }, + { + "alg" : "SHA-256", + "content" : "6fde604399114e77b12519b3d117117c607cb73b89a88800856fb0e0cc82ea7a" + }, + { + "alg" : "SHA-512", + "content" : "8619959d143ef38f5c846591b8b10b0c50906a3301a5e9ed3e3df44124bdfbe3197cd4ecfb214c3250f40a0c1b11138b7a3f6865755445879f0685d2e88a6846" + }, + { + "alg" : "SHA-384", + "content" : "e237fdf6fdb8d21f2fc19fc15a370901c368266ae8d2b157f41b5eeed50b211a871fabc352dda10bb3aec60975d233f5" + }, + { + "alg" : "SHA3-384", + "content" : "cd6240fc102daf1efcd9fdd6532ce21297d5477e9bde3f5651cc9ec9505d526f63ea2284e484c2aee2a8e63841137839" + }, + { + "alg" : "SHA3-256", + "content" : "3959b52aebe7405a95f82d8990b8122cf21b89967f691dad851b85191973f9cb" + }, + { + "alg" : "SHA3-512", + "content" : "1b4ef33997158ddb97ccbcec7011cd55f0e019428d25410b01a83ca58c9420f2f8805be955cf704605145abe582522db0c8afb9698ae4efac141a3807a457ae5" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot@3.2.1?type=jar" + }, + { + "group" : "org.latencyutils", + "name" : "LatencyUtils", + "version" : "2.0.3", + "description" : "LatencyUtils is a package that provides latency recording and reporting utilities.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "2ad12e1ef7614cecfb0483fa9ac6da73" + }, + { + "alg" : "SHA-1", + "content" : "769c0b82cb2421c8256300e907298a9410a2a3d3" + }, + { + "alg" : "SHA-256", + "content" : "a32a9ffa06b2f4e01c5360f8f9df7bc5d9454a5d373cd8f361347fa5a57165ec" + }, + { + "alg" : "SHA-512", + "content" : "bb81a42498c65389366205f4e07cee336920e2f05cc0daae213f2784b1d0ce9a908b038daec20478f23eb00b2bf704f96c5b00f63c99615193ab2a3cc4a9f890" + }, + { + "alg" : "SHA-384", + "content" : "16ca4640dc9d848e6c6d15441897e1b5a9f27f34207b0bb456dd54d8f267b73b348092e548e78634144de44ba3515205" + }, + { + "alg" : "SHA3-384", + "content" : "406c2b5c6f64b0c090568e479b5e6136a04a4e77f8eea65d32b4e2b01deebcdf6a0a851240cdb740c25b5a5e61e6c179" + }, + { + "alg" : "SHA3-256", + "content" : "50ae828358301033542fd7c412e86ee318d5451f89a182e2a679aaf18099d26d" + }, + { + "alg" : "SHA3-512", + "content" : "456c337b9fb385579aae707409ed6a04d08e5fc87b1a46733dca617c22c625bf253dc4747e0cdbf5e7d8b48102d2938cb482b6b688a79aab645a7459c592258f" + } + ], + "licenses" : [ + { + "license" : { + "id" : "CC0-1.0" + } + } + ], + "purl" : "pkg:maven/org.latencyutils/LatencyUtils@2.0.3?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "issue-tracker", + "url" : "https://github.com/LatencyUtils/LatencyUtils/issues" + }, + { + "type" : "vcs", + "url" : "scm:git:git://github.com/LatencyUtils/LatencyUtils.git" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.latencyutils/LatencyUtils@2.0.3?type=jar" + }, + { + "group" : "org.apache.tomcat.embed", + "name" : "tomcat-embed-el", + "version" : "10.1.17", + "description" : "Core Tomcat implementation", + "hashes" : [ + { + "alg" : "MD5", + "content" : "f9171a84574782d1d68acd8b07177172" + }, + { + "alg" : "SHA-1", + "content" : "9ad7312421535d7d3aabe0f541e852baccb59726" + }, + { + "alg" : "SHA-256", + "content" : "bac12b9c993a9181ffc88ea8ba085491a482729e64ae105750a7475a7b85e549" + }, + { + "alg" : "SHA-512", + "content" : "77cf7be4536d7f1f4761fec33562134150c0ebc74d582160ff913c8be37b1502ed63e90bce81bc8617cfcd76c774903c2dca4209a972146f4c976f786456c596" + }, + { + "alg" : "SHA-384", + "content" : "62b14b49de8ee6efb41831ff172114af56a18379a797de732915ac356bce3e5582764253852c9831a3c3b6c1e52dea65" + }, + { + "alg" : "SHA3-384", + "content" : "05cb21cbf8b221332d7ad588cc6aa2087c60e8ce92c5ff2bddcd16465ef2a0198f74d4595dc3313d1acc68ea945c8672" + }, + { + "alg" : "SHA3-256", + "content" : "c18e9b240138c21a23b0bf2f502d1d667084c5a50d7b3340a4a08799a3175de9" + }, + { + "alg" : "SHA3-512", + "content" : "663d02ece35a989d8da1cdbdea002974f0115ae8c727dd71f0505f299c63f04c0e83b718e4c3e65412bea1c79d872e9ca7d9431c7deb63a312d3191d419620ab" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.apache.tomcat.embed/tomcat-embed-el@10.1.17?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.apache.tomcat.embed/tomcat-embed-el@10.1.17?type=jar" + }, + { + "publisher" : "Spring IO", + "group" : "org.springframework", + "name" : "spring-context", + "version" : "6.1.2", + "description" : "Spring Context", + "hashes" : [ + { + "alg" : "MD5", + "content" : "ca23d3013c2afc6d3b30b993f3c5cd69" + }, + { + "alg" : "SHA-1", + "content" : "15df19852991220556b4462a366269b8e15278eb" + }, + { + "alg" : "SHA-256", + "content" : "af22a435469956415bbee873de6c05995ef12f2d29622abf510a94581ea52de2" + }, + { + "alg" : "SHA-512", + "content" : "eca3cb14e8c0fb65d27bc21a8041aab3baea14f278fb546356fcec9874d0dcd10353fe697e94ebc35a78abb3387d5a41b67c1cbc9341eb05359c1b535147a9c9" + }, + { + "alg" : "SHA-384", + "content" : "374207d989f7f27ded5468f35867d0aace78927cdaf98c31b2b6345210fbbe960ae5e5143bb0308347b7ef386159fa04" + }, + { + "alg" : "SHA3-384", + "content" : "236c1d366734b231ef4a334da4220b311dd58b1707ae854b2a50ff89b6b348913458fecdab14d196128b695de6dc9832" + }, + { + "alg" : "SHA3-256", + "content" : "e1e1e87df37dbc064315d7afaa59480c830a0f445ed0df2ff5968931f96e9e86" + }, + { + "alg" : "SHA3-512", + "content" : "a600b2720ed8e5c6ecbb2a68b6a5fb5320811818e2128016b9888df705901a8d0f38dfa99b8d458724a85e769b4da2ce14d461133e085f8aab23f59e9e520c11" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework/spring-context@6.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io/projects/spring-framework" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-framework/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-framework" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework/spring-context@6.1.2?type=jar" + }, + { + "group" : "org.opentest4j", + "name" : "opentest4j", + "version" : "1.3.0", + "description" : "Open Test Alliance for the JVM", + "hashes" : [ + { + "alg" : "MD5", + "content" : "03c404f727531f3fd3b4c73997899327" + }, + { + "alg" : "SHA-1", + "content" : "152ea56b3a72f655d4fd677fc0ef2596c3dd5e6e" + }, + { + "alg" : "SHA-256", + "content" : "48e2df636cab6563ced64dcdff8abb2355627cb236ef0bf37598682ddf742f1b" + }, + { + "alg" : "SHA-512", + "content" : "78fc698a7871bb50305e3657893c10500595f043348d875f57bc39ca4a6a51eda3967b7c8c8a7ec3e8f85f2171bca4aa98823e912e416e87e81c6ba5b70a37c3" + }, + { + "alg" : "SHA-384", + "content" : "10398b6998c9202a0731e2e19ae1c3f9d8a83582c2663fe7bdda15794ee6fa816727dbd8f7c7164bd5395ee1cfe7c97e" + }, + { + "alg" : "SHA3-384", + "content" : "3abe706fd78509c25a402c7bbf6f9ddf71ffb5b35054864ba0fdf7902207115f888a0ba728fd71d2e87a9360d2498121" + }, + { + "alg" : "SHA3-256", + "content" : "d961907a1bfa1dcda329dca494ffbc251b31fabcaca5ab7095661a8ce3c1d654" + }, + { + "alg" : "SHA3-512", + "content" : "0ad661617bcac51bcd26f7ad4611c69b1fd9811b50dbf734e041a3243ab1f845e7796620e8a7c40c4a2df3946864598b1251396c7d9bd813203d82710788cce0" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.opentest4j/opentest4j@1.3.0?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/ota4j-team/opentest4j" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.opentest4j/opentest4j@1.3.0?type=jar" + }, + { + "publisher" : "Spring IO", + "group" : "org.springframework", + "name" : "spring-core", + "version" : "6.1.2", + "description" : "Spring Core", + "hashes" : [ + { + "alg" : "MD5", + "content" : "98bedebd5de314d344ed3a7dcad01c66" + }, + { + "alg" : "SHA-1", + "content" : "e43c71a9eaca454654621f7d272f15b53c68d583" + }, + { + "alg" : "SHA-256", + "content" : "8e3f7378e98c26500bdb5ecd6865778f57a22787eb2f11b9bd5fb8e438a0c631" + }, + { + "alg" : "SHA-512", + "content" : "9654f2d77899116d66dbf5808815c866da0bc7a965532da059c7819bde3928e8d3692f0dc97e06f94c44e5452b785b50eb364a1cb7e46385653ba0e2c7195306" + }, + { + "alg" : "SHA-384", + "content" : "3b63b4a26c5706ef2e379ff7bce89df983e7ae449a927905ce23ecf26e22bbcf8e91dc53cc75f4f7cd72bc09d7e7bb20" + }, + { + "alg" : "SHA3-384", + "content" : "ca29e88f0764a6a9279fc93d5cb9284a04c6ccca6a8a5beaa404079b90674286fc6458d14b0b0a727d31e00b8009e4f9" + }, + { + "alg" : "SHA3-256", + "content" : "861fc1147deae5a55165bd32c3fd4e18687afcc37876205c10bf1feede582ff9" + }, + { + "alg" : "SHA3-512", + "content" : "659a0d2e5ba153be219e1ebbafb28f9b48c44a2acd78d695e7479551a1c1641b7893d7df071a3cc7436de03735b0c8024b2f758bd0286711eae64ab005f6e929" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework/spring-core@6.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io/projects/spring-framework" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-framework/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-framework" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework/spring-core@6.1.2?type=jar" + }, + { + "group" : "com.jayway.jsonpath", + "name" : "json-path", + "version" : "2.8.0", + "description" : "A library to query and verify JSON", + "hashes" : [ + { + "alg" : "MD5", + "content" : "501b9f34e6a05c20dd74e6b40e066617" + }, + { + "alg" : "SHA-1", + "content" : "b4ab3b7a9e425655a0ca65487bbbd6d7ddb75160" + }, + { + "alg" : "SHA-256", + "content" : "9601707e95cd79fb98570a01ea8cfb857b5cde948744d6e0edf733c11002c95b" + }, + { + "alg" : "SHA-512", + "content" : "8d1521092a2acb13a2667774b8b81debc1f2a0e937007e27e5bd28bb222910774b64d6e269f33473f765c810c03a34e715d16065dc9a4be8d8d081436282ba7e" + }, + { + "alg" : "SHA-384", + "content" : "aeea493be7c23574a77df50a0652776b768d52e4238efd504b8ef3b142bbe6caf0dae8955b30c2173a54f70243d36a36" + }, + { + "alg" : "SHA3-384", + "content" : "c11c80614c007f350fa2fe758c0f4505e7ed7d25590622f133abc59ccffeb4e0b2abfd393b83e58dff4668307f28704f" + }, + { + "alg" : "SHA3-256", + "content" : "d7a7d1d7845dde343617ec009dd0d76e6bf012f182324e3b9d0f23c52bb7f67f" + }, + { + "alg" : "SHA3-512", + "content" : "da023255dfa2271a0b6b35b7d35980c3c502f3f63b3d515714f7dea54046f527bd6cbd903fec9492aad88ad03a1b85dc2b05fca4b34ded3c3b427c4cbfab02fe" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/com.jayway.jsonpath/json-path@2.8.0?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "scm:git:git://github.com/jayway/JsonPath.git" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/com.jayway.jsonpath/json-path@2.8.0?type=jar" + }, + { + "group" : "org.slf4j", + "name" : "slf4j-api", + "version" : "2.0.9", + "description" : "The slf4j API", + "hashes" : [ + { + "alg" : "MD5", + "content" : "45630e54b0f0ac2b3c80462515ad8fda" + }, + { + "alg" : "SHA-1", + "content" : "7cf2726fdcfbc8610f9a71fb3ed639871f315340" + }, + { + "alg" : "SHA-256", + "content" : "0818930dc8d7debb403204611691da58e49d42c50b6ffcfdce02dadb7c3c2b6c" + }, + { + "alg" : "SHA-512", + "content" : "069e6ddce79617e37d61758120c7e68348ee62f255781948937f7bec3058e46244026d7f6a11e90fbc15cd4288c4bb1acee4f242af521c721a9e68a05e64d526" + }, + { + "alg" : "SHA-384", + "content" : "fd6f7ad85d02ac63cd1a586c8bb158c1fc000495f512f097731ea9f749b5da2637615b821294962805ba312c738f40aa" + }, + { + "alg" : "SHA3-384", + "content" : "17cd61f59a162250b52a89c7c56eb60da253b776210500313c7b82744483ff84717946f969251fb4d76f9bb12a2458fe" + }, + { + "alg" : "SHA3-256", + "content" : "9dcb04582c64c79e788f9191195834ec75bb3457133d22a176a0ccb069b97103" + }, + { + "alg" : "SHA3-512", + "content" : "990faffa454598a3fa82affe30f1323db769d2e1fff20d9c7163ef6fd95ac7a0874c06a634207a2eaed9e5afbdee68b225138fc75018717ba97efe3ffe92c88a" + } + ], + "licenses" : [ + { + "license" : { + "id" : "MIT", + "url" : "https://opensource.org/licenses/MIT" + } + } + ], + "purl" : "pkg:maven/org.slf4j/slf4j-api@2.0.9?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.slf4j/slf4j-api@2.0.9?type=jar" + }, + { + "group" : "ch.qos.logback", + "name" : "logback-classic", + "version" : "1.4.14", + "description" : "logback-classic module", + "hashes" : [ + { + "alg" : "MD5", + "content" : "204b49a7fa041b2b2c455193079dc1d2" + }, + { + "alg" : "SHA-1", + "content" : "d98bc162275134cdf1518774da4a2a17ef6fb94d" + }, + { + "alg" : "SHA-256", + "content" : "8e832f7263ca606ae36dabb2d8b24c2f43d82cf634e81dad9d1640fa6ee3c596" + }, + { + "alg" : "SHA-512", + "content" : "77b535f2cf5a2fdb807017cb6fe456c40dcb11491e743ff86f99df2714a1b12bb9182ac193d37c8a6dd7eb2bf4c7d24390a6d551d02a280083673516eecdabc4" + }, + { + "alg" : "SHA-384", + "content" : "606400251082b8193a57bb20f1774ee2d6e439fab2ddb0207643fe9cee66cf61edba5e5c80d4b3bc9785a7bab910f8df" + }, + { + "alg" : "SHA3-384", + "content" : "d9d9b1412d2fea3eeb5d110a0e7d44c9bc13459fd2b2f5cbb30b95174081f0184758abe43b5e6b6197a716c3ba7b310f" + }, + { + "alg" : "SHA3-256", + "content" : "e1b0d59a9a91fd7878c92b3680cde8c34896823612a2f04715c05e977c09db82" + }, + { + "alg" : "SHA3-512", + "content" : "e0a39dacbb91b7d9f00bdf78829918079f6f2e749c28f31a359064bac9ac7eb65c87e581795946814460f787e33b8829a9cf0e933a0f87dd7d48f288d45f5064" + } + ], + "licenses" : [ + { + "license" : { + "id" : "EPL-1.0" + } + }, + { + "license" : { + "name" : "GNU Lesser General Public License", + "url" : "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" + } + } + ], + "purl" : "pkg:maven/ch.qos.logback/logback-classic@1.4.14?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/ch.qos.logback/logback-classic@1.4.14?type=jar" + }, + { + "publisher" : "Chemouni Uriel", + "group" : "net.minidev", + "name" : "accessors-smart", + "version" : "2.5.0", + "description" : "Java reflect give poor performance on getter setter an constructor calls, accessors-smart use ASM to speed up those calls.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "fc814b28882dd9f2552eda21add0698f" + }, + { + "alg" : "SHA-1", + "content" : "aca011492dfe9c26f4e0659028a4fe0970829dd8" + }, + { + "alg" : "SHA-256", + "content" : "12314fc6881d66a413fd66370787adba16e504fbf7e138690b0f3952e3fbd321" + }, + { + "alg" : "SHA-512", + "content" : "77b21fdd3401a0557d2d04a14c27563897afe9e001fc520398e22083bc18afee5e48dd9f5fc6561d0f327a30a9303bf5cc20f0a2ce741d80b3792e258276faac" + }, + { + "alg" : "SHA-384", + "content" : "7464bf3917d11712b235c7e1af339766d01cb4b41ec98941c3c69bc4ab9a4d0e6c832cbf01482425100dc8f1611ce3a0" + }, + { + "alg" : "SHA3-384", + "content" : "be26dc2bfc5fdc1a45e14f1c2fcfe224994e66d39049e235ea83c714fb90bb685d3f2209c0d550528e2cd9b2d9d95a6e" + }, + { + "alg" : "SHA3-256", + "content" : "6a914eb757ec313842f13c837eeb628e606323cc63dc24127e7a9804e2746d12" + }, + { + "alg" : "SHA3-512", + "content" : "edbddef0538aac87bf6af714e12c4078fd6ada069b6fd0e1e5c1038b060999764e06c28b3ca38b8d540d0f60c72f7321ddc22d2537156999bad5098c89b6975a" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/net.minidev/accessors-smart@2.5.0?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://urielch.github.io/" + }, + { + "type" : "distribution", + "url" : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" + }, + { + "type" : "vcs", + "url" : "https://github.com/netplex/json-smart-v2" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/net.minidev/accessors-smart@2.5.0?type=jar" + }, + { + "group" : "com.fasterxml.jackson.core", + "name" : "jackson-core", + "version" : "2.15.3", + "description" : "Core Jackson processing abstractions (aka Streaming API), implementation for JSON", + "hashes" : [ + { + "alg" : "MD5", + "content" : "c86c75392bf138d54d2a219bb1d0cbcd" + }, + { + "alg" : "SHA-1", + "content" : "60d600567c1862840397bf9ff5a92398edc5797b" + }, + { + "alg" : "SHA-256", + "content" : "51fab7aad51ed588482edc507fd542747936c5094d1ab76ed21ddb63b96b610d" + }, + { + "alg" : "SHA-512", + "content" : "112de40a31dc7d011f256f1d2fe0d9e2afc301a1f31974318f8d070c3e362b2ba96005167384244f630b915451db6694bd3cf6a9b793872351bc18f21c9de5e4" + }, + { + "alg" : "SHA-384", + "content" : "9daaf08467525e462234c53ddbf7287bcef15d8df7fbc64bcd558a91d11e8335b3a79368d194b126d3c8fb846800025b" + }, + { + "alg" : "SHA3-384", + "content" : "0b4fdc8d11fc060461e74e773fce2e64d1a98bed7db6edf51784bb1b801da4bae744a2958e81c2e24cb992fec892fb6c" + }, + { + "alg" : "SHA3-256", + "content" : "751ad4f10a78cb36fccbbe1dfe208816f17619edd5adeabc86b7509201e03c3d" + }, + { + "alg" : "SHA3-512", + "content" : "aa5807b7d92d150fada6a4ecdbfce998bbea825a09af8381127ba3736de029ae9923f54d770b2e5c3f5c85d9b4bcf21e6893a5a3089db2d02f1432b85dfa0fe7" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.3?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/FasterXML/jackson-core" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.3?type=jar" + }, + { + "group" : "org.xmlunit", + "name" : "xmlunit-core", + "version" : "2.9.1", + "description" : "XMLUnit for Java", + "hashes" : [ + { + "alg" : "MD5", + "content" : "011288450a3905a7d97e3957b69e713e" + }, + { + "alg" : "SHA-1", + "content" : "e5833662d9a1279a37da3ef6f62a1da29fcd68c4" + }, + { + "alg" : "SHA-256", + "content" : "7e70f23d4f75e05f0ee79f0f6b9e13b6cf51d34f36c5fc3a6b839429dde1efef" + }, + { + "alg" : "SHA-512", + "content" : "1d07dc1582a1930664ab3cffd1443e85c83fec138c663f3070a9d3b283f818157b2cdd1589595867281a96d3b444b18c22c1ee3249a75c857c6ee9682785e8a3" + }, + { + "alg" : "SHA-384", + "content" : "f54a506a08b66776d92d4379712ae9f7658cc89bd7b780eb629bd37143ff68e28cb2314539dc3c1ff13dc9cccba394f2" + }, + { + "alg" : "SHA3-384", + "content" : "7fd679371624f72417612491bac721a49f229744df3fc7455e5fd3983bd2de452a4eaabb707be7bac328f3beeea88d99" + }, + { + "alg" : "SHA3-256", + "content" : "c517aa9c543a4a3df361c30ba6609082a1dd5dc2abc351643ad5b733a1282773" + }, + { + "alg" : "SHA3-512", + "content" : "3797bade2087f791697f6736296381f8b158a2a93f50faeabcd96b4c9f48ad26fd78af56cc1036c449c35e624181961d54acdd7623b84c23c81c72d5d0fa57f1" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.xmlunit/xmlunit-core@2.9.1?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.xmlunit/xmlunit-core@2.9.1?type=jar" + }, + { + "publisher" : "OW2", + "group" : "org.ow2.asm", + "name" : "asm", + "version" : "9.3", + "description" : "ASM, a very small and fast Java bytecode manipulation framework", + "hashes" : [ + { + "alg" : "MD5", + "content" : "e1c3b96035117ab516ffe0de9bd696e0" + }, + { + "alg" : "SHA-1", + "content" : "8e6300ef51c1d801a7ed62d07cd221aca3a90640" + }, + { + "alg" : "SHA-256", + "content" : "1263369b59e29c943918de11d6d6152e2ec6085ce63e5710516f8c67d368e4bc" + }, + { + "alg" : "SHA-512", + "content" : "04362f50a2b66934c2635196bf8e6bd2adbe4435f312d1d97f4733c911e070f5693941a70f586928437043d01d58994325e63744e71886ae53a62c824927a4d4" + }, + { + "alg" : "SHA-384", + "content" : "304aa6673d587a68a06dd8601c6db0dc4d387f89a058b7600459522d94780e9e8d87a2778604fc41b81c43a57bf49ad6" + }, + { + "alg" : "SHA3-384", + "content" : "9744884ed03ced46ed36c68c7bb1f523678bcbb4f32ebeaa220157b8631e862d6573066dfc2092ed77dc7826ad17aef2" + }, + { + "alg" : "SHA3-256", + "content" : "2be2d22fdbafe87b7cdda0498fc4f45db8d77a720b63ec1f7ffe8351e173b77b" + }, + { + "alg" : "SHA3-512", + "content" : "a3ff403dd3eefbb7511d2360ab1ca3d1bf33b2f9d1c5738284be9d132eb6ad869f2d97e790ed0969132af30271e544d3725c02252267fe55e0339f89f3669ce1" + } + ], + "licenses" : [ + { + "license" : { + "id" : "BSD-3-Clause", + "url" : "https://opensource.org/licenses/BSD-3-Clause" + } + } + ], + "purl" : "pkg:maven/org.ow2.asm/asm@9.3?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "http://www.ow2.org/" + }, + { + "type" : "issue-tracker", + "url" : "https://gitlab.ow2.org/asm/asm/issues" + }, + { + "type" : "mailing-list", + "url" : "https://mail.ow2.org/wws/arc/asm/" + }, + { + "type" : "vcs", + "url" : "https://gitlab.ow2.org/asm/asm/" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.ow2.asm/asm@9.3?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-starter", + "version" : "3.2.1", + "description" : "Core starter, including auto-configuration support, logging and YAML", + "hashes" : [ + { + "alg" : "MD5", + "content" : "d9eb815815944bcdaeed5e63f32e5d7f" + }, + { + "alg" : "SHA-1", + "content" : "bc03d7075fb9d9d4877218db48d5dae3dd72a65d" + }, + { + "alg" : "SHA-256", + "content" : "a25f2f4172c34f46b73fff03293370c3daf231a1db2883ef8032aa471779fb8b" + }, + { + "alg" : "SHA-512", + "content" : "35cc80f9b10e81624324083a024c97e247e12f54762cfaadf40504903b0ebdc76d0226af1e4646bca445211b039913709ff48289dd57e27ecab18fd6e427d306" + }, + { + "alg" : "SHA-384", + "content" : "9acae9f3f77733a83d37641d3bd32d762225a08dcb20d61ff33a9038e8a4fe2dd39026bb08026cdb618437f68fc11382" + }, + { + "alg" : "SHA3-384", + "content" : "1e605937a46c8371423b7876d5dae4363f718f70200a1276056bd6466d03096aa580708c7abc76618a141a542df29b24" + }, + { + "alg" : "SHA3-256", + "content" : "331b3c120493fb5d9dd628beb8aa10382772a08d0a687103a2e87a4516fffde6" + }, + { + "alg" : "SHA3-512", + "content" : "9f2612fbecec4664979896868e4766b1f66aaebc914e46a07a7ef7e5ff76786e5a73ae9ca5f364d23ae41f8bea2fb44e5034014950423fdc3a438ae1dc275820" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-starter@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-starter@3.2.1?type=jar" + }, + { + "group" : "org.apache.tomcat.embed", + "name" : "tomcat-embed-core", + "version" : "10.1.17", + "description" : "Core Tomcat implementation", + "hashes" : [ + { + "alg" : "MD5", + "content" : "81d2d784780b1fe54275ab4f3d0c3830" + }, + { + "alg" : "SHA-1", + "content" : "5b9185ee002f9e194d2cb21ddcf8bc5f3d4a69da" + }, + { + "alg" : "SHA-256", + "content" : "5d70fa6ae0548f89fb4c070423ecc2db050cebf248b0d5f3f2294375a6762382" + }, + { + "alg" : "SHA-512", + "content" : "9fb1726f3a10f5e0bdd1cafcdc9532536679d04e5cdde9e54bdf18819ea2651bcaac0efddd6a8b5dbf3cfb8dfcd7ab0453f2ff3fa4e21a0f3796d4dd6d630433" + }, + { + "alg" : "SHA-384", + "content" : "e644a094c17574fc9334772913aeabd6de0be8eacb0718981dbd97ee197a21f43ff3efe2c073f8863a4ff111f4ccb303" + }, + { + "alg" : "SHA3-384", + "content" : "2e8d5d4b1e202e19529270adc7992e9d187ad34bdd62ab7633359f3394059cdade69c88dddd3879dea40487cb17702da" + }, + { + "alg" : "SHA3-256", + "content" : "25826af7f0a6fd192e83cd14481055b0c5477c325e51d17355d9ff97963380a0" + }, + { + "alg" : "SHA3-512", + "content" : "0b2513e578a484562ad47a8a1a4d1fe8253a9a276fac49ea9732877d976a2d1827037caa5a6401d5659c765317acb94127e62f99373a4efea63b44ab4a1824be" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@10.1.17?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@10.1.17?type=jar" + }, + { + "group" : "net.bytebuddy", + "name" : "byte-buddy-agent", + "version" : "1.14.10", + "description" : "The Byte Buddy agent offers convenience for attaching an agent to the local or a remote VM.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "389b6aca1ee862684592f6f041f81724" + }, + { + "alg" : "SHA-1", + "content" : "90ed94ac044ea8953b224304c762316e91fd6b31" + }, + { + "alg" : "SHA-256", + "content" : "67993a89d47ca58ff868802a4448ddd150e5fe4e5a5645ded990d7b4d557a6b9" + }, + { + "alg" : "SHA-512", + "content" : "7f1a1310b1a0f60d6ff07dee8d9b7e404e8fb9a25a5c0c186e00cafc834e5a026a7694fb65279367dabfa1789c1f16192d0ea794b7f511f0bb3414b8d519e9a5" + }, + { + "alg" : "SHA-384", + "content" : "ed1e1d594a7c2837311accf3f718cbc7c6e2034afcab13c63d72313ee1ffd18a53863f1ccd194b85b7e0ffed78bafc9c" + }, + { + "alg" : "SHA3-384", + "content" : "b3baeae67826ec4e4f71b2870220c362f153d2a126b04557302b5b8e24a58b9741bef7afa9c4e4f0fa1ea9371cbcb1df" + }, + { + "alg" : "SHA3-256", + "content" : "01ccb9e430868deef5b51124073643eaf6dd2c8c7e4d6e70b59042c9d28e3361" + }, + { + "alg" : "SHA3-512", + "content" : "b621fa443ade355b10cc45329a5e0f700942dd39e633a8f2343ece00446cd42f5c1217b041a67b3143df86397c363f8dcad226f1e70b8755126512a74f878262" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/net.bytebuddy/byte-buddy-agent@1.14.10?type=jar", + "modified" : false, + "type" : "library", + "bom-ref" : "pkg:maven/net.bytebuddy/byte-buddy-agent@1.14.10?type=jar" + }, + { + "publisher" : "Spring IO", + "group" : "org.springframework", + "name" : "spring-test", + "version" : "6.1.2", + "description" : "Spring TestContext Framework", + "hashes" : [ + { + "alg" : "MD5", + "content" : "fadfe62dd198a4acce4416acb28e2869" + }, + { + "alg" : "SHA-1", + "content" : "c393079051398e02c20d8b24e02822f365123719" + }, + { + "alg" : "SHA-256", + "content" : "2155779c3e461df55f3b093f0e6e4bda398664e3452efe599690bc9a3f1932f0" + }, + { + "alg" : "SHA-512", + "content" : "5e6e4f76edbf17a321302bf6257c09ed7893e32c50fb3cace37b2271f3c488d397c67b5315ef3019ee6d28544f52cf593e0475bf00927cd67f0c668d6b3909a3" + }, + { + "alg" : "SHA-384", + "content" : "151df7daac9a3e3e74732405bd4feb17ad9ff3e4de196e767f39da675d4480994ed8da13e3b1b27c7b4ee9ebc17feef8" + }, + { + "alg" : "SHA3-384", + "content" : "9069193468f2ae4c65c94d3950541efe37498a4e19245ddc67909181e83e14019f956baba54da0b9d2e8a262db13abd0" + }, + { + "alg" : "SHA3-256", + "content" : "8ccf71564f5ee7e6a578031c7c8530a5ddf136cc1dce483818ebd30d53c851df" + }, + { + "alg" : "SHA3-512", + "content" : "31049da217d1115b589780ffaa3ddfbf676cc58e70bd4cbc1f24c0cb2aea6b155539f8f9b3f6757f19719fed0a6102110f195b34cdd464b5e375132c25e7bb51" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework/spring-test@6.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io/projects/spring-framework" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-framework/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-framework" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework/spring-test@6.1.2?type=jar" + }, + { + "group" : "org.junit.jupiter", + "name" : "junit-jupiter", + "version" : "5.10.1", + "description" : "Module \"junit-jupiter\" of JUnit 5.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "32fd55a03f648868767c1bebedd198df" + }, + { + "alg" : "SHA-1", + "content" : "6e5c7dd668d6349cb99e52ab8321e73479a309bc" + }, + { + "alg" : "SHA-256", + "content" : "c1a386e901fae28e493185a47c8cea988fb1a37422b353a0f8b4df2e6c5d6037" + }, + { + "alg" : "SHA-512", + "content" : "c97a2f9eefa6f34441fc0c97744873040bbe49d335954edab43bab25876a33f4b3f11347459420569ef660449728aa093bbae5d42c0fa733a0b624706b57a65d" + }, + { + "alg" : "SHA-384", + "content" : "873dfccaf8366ce5b14dc0b5498205debecd90ecba20b1f1c924721764d546b5b9629dd57c486e5a5a2bc38954bf3824" + }, + { + "alg" : "SHA3-384", + "content" : "67f09e3174ae3fac6ddea13b56dcf078165e715cb18afd73d86bb980357e365cef6e62083231f09ae2accddfe62f5bcb" + }, + { + "alg" : "SHA3-256", + "content" : "1c2a60003b13025c959e7728b3f4469b67bad8649d2080c0871418fb52b1c078" + }, + { + "alg" : "SHA3-512", + "content" : "7c03cfaeabed9c57b26e083bcb0ca9a114c491216fc7e9652a39a5468579175e575ace315493610fdc7711c6557eff11933fbd28f5433c237d2277bee102c5a6" + } + ], + "licenses" : [ + { + "license" : { + "id" : "EPL-2.0" + } + } + ], + "purl" : "pkg:maven/org.junit.jupiter/junit-jupiter@5.10.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "vcs", + "url" : "https://github.com/junit-team/junit5" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.junit.jupiter/junit-jupiter@5.10.1?type=jar" + }, + { + "publisher" : "Chemouni Uriel", + "group" : "net.minidev", + "name" : "json-smart", + "version" : "2.5.0", + "description" : "JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate. It is based on a subset of the JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999. JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others. These properties make JSON an ideal data-interchange language.", + "hashes" : [ + { + "alg" : "MD5", + "content" : "af9b7eda9c435acaf22e840991c7b10f" + }, + { + "alg" : "SHA-1", + "content" : "57a64f421b472849c40e77d2e7cce3a141b41e99" + }, + { + "alg" : "SHA-256", + "content" : "432b9e545848c4141b80717b26e367f83bf33f19250a228ce75da6e967da2bc7" + }, + { + "alg" : "SHA-512", + "content" : "56284bb3cee2bcc3684cdcc610115c7eacafdbd70aa852cb0209616b0503dfd448c5110b50e11a71b1c61a6e7ea27594ff63cc968230374555cc6f652d69d372" + }, + { + "alg" : "SHA-384", + "content" : "0fbbd6899d344c3158007f2f033165284323f1ecdfa49e17730d9d2bed8b3d77bbdc209a72a388e9e15a5bed9d9c8eef" + }, + { + "alg" : "SHA3-384", + "content" : "0f18f178117f8c640e7e1ac2ed4c2b28e331f658f40eac2f5974e891f7130b760e4f057859a537caaa046ba9c086a24a" + }, + { + "alg" : "SHA3-256", + "content" : "4c91eaa12f7c0ee08264ad95d016cfa41af08c963055b7f9076771da402e93e0" + }, + { + "alg" : "SHA3-512", + "content" : "0c5fad6395cf3fd25c04fd1e2c915351da4849475b463e017b760ef97800addb170d11f89791dd29ab867e343c35fd1f3ea7935622ba728d789c9f2e7fd1da51" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/net.minidev/json-smart@2.5.0?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://urielch.github.io/" + }, + { + "type" : "distribution", + "url" : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" + }, + { + "type" : "vcs", + "url" : "https://github.com/netplex/json-smart-v2" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/net.minidev/json-smart@2.5.0?type=jar" + }, + { + "publisher" : "Spring IO", + "group" : "org.springframework", + "name" : "spring-expression", + "version" : "6.1.2", + "description" : "Spring Expression Language (SpEL)", + "hashes" : [ + { + "alg" : "MD5", + "content" : "2f56216dc7ee08cbeafa54ccf18cad35" + }, + { + "alg" : "SHA-1", + "content" : "98786397734b27b7c8843a6b01a7fa34d40d6806" + }, + { + "alg" : "SHA-256", + "content" : "0fef5fb19f375a8632d2a117f4b3aed059b959e9693e90c3b7f57b7cad2f9e0b" + }, + { + "alg" : "SHA-512", + "content" : "a28e984d9ff1d4078d57f139ff28065ffba7f325c891c74c0774cd3ccfe50a9462cd93483c28c8ca4674b581ab723687c37c5c88e7cb080823d5629fa684e7f8" + }, + { + "alg" : "SHA-384", + "content" : "a84fb64144a67b56ce322fc9f4948a9491f6f5876d198eb57c99f38540971a0779a2949b93cc5f32662f97a83823ea87" + }, + { + "alg" : "SHA3-384", + "content" : "b099ce06de6a5543e52a2d43c97c4ed6567e82263db29849ff09cf37bf48e3e9974308698c2f272187508e242f756576" + }, + { + "alg" : "SHA3-256", + "content" : "efa3768de47e3b1ff9257f8367a528e38b3eec9c972eb7ba3dd8f60da626fb17" + }, + { + "alg" : "SHA3-512", + "content" : "95d7011482520e797a25f9d9b8db1b1bf6c24b3ddb3ca4b70fe5a1a58ed04ea870f86f8393f884dad8b893a6fc53ad8da1b21fdc01d9169564c3dc0229824b27" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework/spring-expression@6.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io/projects/spring-framework" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-framework/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-framework" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework/spring-expression@6.1.2?type=jar" + }, + { + "publisher" : "VMware, Inc.", + "group" : "org.springframework.boot", + "name" : "spring-boot-starter-actuator", + "version" : "3.2.1", + "description" : "Starter for using Spring Boot's Actuator which provides production ready features to help you monitor and manage your application", + "hashes" : [ + { + "alg" : "MD5", + "content" : "59713236dc4fc4b1562a3ea9788bde1e" + }, + { + "alg" : "SHA-1", + "content" : "ca17ff67e80a230f04d40d73321d623b769e361d" + }, + { + "alg" : "SHA-256", + "content" : "31c28021755feab49cc9310a8353382b3ca35d0adf02926b83e4c44ea4942898" + }, + { + "alg" : "SHA-512", + "content" : "ed618c7f1e3337c90919551ad4f14996bb2a78f773ba00c1e02d5a991d1c578e940d9b73f5e01045115c7b5d3f096f8de6720ba0d28992a586ef834948f17766" + }, + { + "alg" : "SHA-384", + "content" : "45956cbd019f099f96f36391c98fd23ea32698035f90f6e4e4df0d9a43dc03ef6db2954c2871da76a038511280591b43" + }, + { + "alg" : "SHA3-384", + "content" : "3a08b673deb39ab5db9561281245b76e9f57410601e5ce4040cefedb02e2a19abb45a98d2de170fbbac7b7f0b93eceb3" + }, + { + "alg" : "SHA3-256", + "content" : "12151432b32e26bab903572023ea022757a31177e4a6315d8fcd15bbbf34731c" + }, + { + "alg" : "SHA3-512", + "content" : "911f109b63d07f20de51f8a2de8799e32fdff05a52def36d408cb1da72a3bb63ff0878f850a7ad1cc9e85393f24ac58c6b8dd4068f11d9e70bc1e130974db00f" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework.boot/spring-boot-starter-actuator@3.2.1?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-boot/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-boot" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-actuator@3.2.1?type=jar" + }, + { + "publisher" : "Spring IO", + "group" : "org.springframework", + "name" : "spring-beans", + "version" : "6.1.2", + "description" : "Spring Beans", + "hashes" : [ + { + "alg" : "MD5", + "content" : "5ee147f2234968eeab4b469af4d3b5f1" + }, + { + "alg" : "SHA-1", + "content" : "abf52f2254975a3b1e95b2b63fb8b01d891cdc51" + }, + { + "alg" : "SHA-256", + "content" : "742baa41c1b0282ef01b3d542dc1b1de71db2578bd9ddd9a7d57fb191234b194" + }, + { + "alg" : "SHA-512", + "content" : "efd0eb5a073c899515ae144a4fcb4fc97cc53cbd4236d0e6a30df8fa8873fcd9bc509bc3fa88d1bff86a94dc3dbc5106374d0117f64ec8df9e6affe8f98aaa07" + }, + { + "alg" : "SHA-384", + "content" : "6214558d1024fa3b5545079268b0b2fbeda93768a0665d617612ddf4e42e11b770c38c05cb86e3ae558025afa67beea5" + }, + { + "alg" : "SHA3-384", + "content" : "8170ccea30165f25c533e27c0de38b590ca72f285cfc365c60e97745e78532213d6c93bdbea56f561dd180297a8c5ab4" + }, + { + "alg" : "SHA3-256", + "content" : "2761e0814e167de13ed08ce748880006407eda2fa744a347f57684c2bc9bb6fe" + }, + { + "alg" : "SHA3-512", + "content" : "ecdeb4cd558af513ed381942f35bd2d8dfa9b0db446dbc8c5326656ade960682283c71fcaae5578ca431f705f1a86041b0764bd453f30e738be65c4f0bbf37d1" + } + ], + "licenses" : [ + { + "license" : { + "id" : "Apache-2.0" + } + } + ], + "purl" : "pkg:maven/org.springframework/spring-beans@6.1.2?type=jar", + "modified" : false, + "externalReferences" : [ + { + "type" : "website", + "url" : "https://spring.io/projects/spring-framework" + }, + { + "type" : "issue-tracker", + "url" : "https://github.com/spring-projects/spring-framework/issues" + }, + { + "type" : "vcs", + "url" : "https://github.com/spring-projects/spring-framework" + } + ], + "type" : "library", + "bom-ref" : "pkg:maven/org.springframework/spring-beans@6.1.2?type=jar" + } + ], + "dependencies" : [ + { + "ref" : "pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.15.3?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/ch.qos.logback/logback-classic@1.4.14?type=jar", + "dependsOn" : [ + "pkg:maven/ch.qos.logback/logback-core@1.4.14?type=jar", + "pkg:maven/org.slf4j/slf4j-api@2.0.9?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-test-autoconfigure@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot-test@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot@3.2.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/io.micrometer/micrometer-jakarta9@1.12.1?type=jar", + "dependsOn" : [ + "pkg:maven/io.micrometer/micrometer-core@1.12.1?type=jar", + "pkg:maven/io.micrometer/micrometer-observation@1.12.1?type=jar", + "pkg:maven/io.micrometer/micrometer-commons@1.12.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/com.fasterxml.jackson.module/jackson-module-parameter-names@2.15.3?type=jar", + "dependsOn" : [ + "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.3?type=jar", + "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.3?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-test@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot-starter@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-test-autoconfigure@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-test@3.2.1?type=jar", + "pkg:maven/com.jayway.jsonpath/json-path@2.8.0?type=jar", + "pkg:maven/jakarta.xml.bind/jakarta.xml.bind-api@4.0.1?type=jar", + "pkg:maven/net.minidev/json-smart@2.5.0?type=jar", + "pkg:maven/org.assertj/assertj-core@3.24.2?type=jar", + "pkg:maven/org.awaitility/awaitility@4.2.0?type=jar", + "pkg:maven/org.hamcrest/hamcrest@2.2?type=jar", + "pkg:maven/org.junit.jupiter/junit-jupiter@5.10.1?type=jar", + "pkg:maven/org.mockito/mockito-junit-jupiter@5.7.0?type=jar", + "pkg:maven/org.mockito/mockito-core@5.7.0?type=jar", + "pkg:maven/org.skyscreamer/jsonassert@1.5.1?type=jar", + "pkg:maven/org.springframework/spring-test@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar", + "pkg:maven/org.xmlunit/xmlunit-core@2.9.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.mockito/mockito-core@5.7.0?type=jar", + "dependsOn" : [ + "pkg:maven/net.bytebuddy/byte-buddy@1.14.10?type=jar", + "pkg:maven/net.bytebuddy/byte-buddy-agent@1.14.10?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-actuator-autoconfigure@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-actuator@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot@3.2.1?type=jar", + "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310@2.15.3?type=jar", + "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.3?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.yaml/snakeyaml@2.2?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/jakarta.activation/jakarta.activation-api@2.1.2?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-web@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot-starter-json@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-starter@3.2.1?type=jar", + "pkg:maven/org.springframework/spring-webmvc@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-web@6.1.2?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-starter-tomcat@3.2.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-test@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot@3.2.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework/spring-beans@6.1.2?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/net.minidev/accessors-smart@2.5.0?type=jar", + "dependsOn" : [ + "pkg:maven/org.ow2.asm/asm@9.3?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.xmlunit/xmlunit-core@2.9.1?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/io.micrometer/micrometer-core@1.12.1?type=jar", + "dependsOn" : [ + "pkg:maven/io.micrometer/micrometer-observation@1.12.1?type=jar", + "pkg:maven/io.micrometer/micrometer-commons@1.12.1?type=jar", + "pkg:maven/org.hdrhistogram/HdrHistogram@2.1.12?type=jar", + "pkg:maven/org.latencyutils/LatencyUtils@2.0.3?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.hdrhistogram/HdrHistogram@2.1.12?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.opentest4j/opentest4j@1.3.0?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/net.minidev/json-smart@2.5.0?type=jar", + "dependsOn" : [ + "pkg:maven/net.minidev/accessors-smart@2.5.0?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.apiguardian/apiguardian-api@1.1.2?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.junit.platform/junit-platform-commons@1.10.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.apiguardian/apiguardian-api@1.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework/spring-expression@6.1.2?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.apache.tomcat.embed/tomcat-embed-websocket@10.1.17?type=jar", + "dependsOn" : [ + "pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@10.1.17?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.apache.logging.log4j/log4j-to-slf4j@2.21.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.slf4j/slf4j-api@2.0.9?type=jar", + "pkg:maven/org.apache.logging.log4j/log4j-api@2.21.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-logging@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/ch.qos.logback/logback-classic@1.4.14?type=jar", + "pkg:maven/org.apache.logging.log4j/log4j-to-slf4j@2.21.1?type=jar", + "pkg:maven/org.slf4j/jul-to-slf4j@2.0.9?type=jar" + ] + }, + { + "ref" : "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.3?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.skyscreamer/jsonassert@1.5.1?type=jar", + "dependsOn" : [ + "pkg:maven/com.vaadin.external.google/android-json@0.0.20131108.vaadin1?type=jar" + ] + }, + { + "ref" : "pkg:maven/ch.qos.logback/logback-core@1.4.14?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jdk8@2.15.3?type=jar", + "dependsOn" : [ + "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.3?type=jar", + "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.3?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.slf4j/slf4j-api@2.0.9?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.springframework/spring-webmvc@6.1.2?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework/spring-web@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-context@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-aop@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-beans@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-expression@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@10.1.17?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.assertj/assertj-core@3.24.2?type=jar", + "dependsOn" : [ + "pkg:maven/net.bytebuddy/byte-buddy@1.14.10?type=jar" + ] + }, + { + "ref" : "pkg:maven/com.jayway.jsonpath/json-path@2.8.0?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.apache.tomcat.embed/tomcat-embed-el@10.1.17?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.springframework/spring-core@6.1.2?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework/spring-jcl@6.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/io.micrometer/micrometer-commons@1.12.1?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot@3.2.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.junit.jupiter/junit-jupiter-params@5.10.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.junit.jupiter/junit-jupiter-api@5.10.1?type=jar", + "pkg:maven/org.apiguardian/apiguardian-api@1.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.hamcrest/hamcrest@2.2?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.springframework/spring-context@6.1.2?type=jar", + "dependsOn" : [ + "pkg:maven/io.micrometer/micrometer-observation@1.12.1?type=jar", + "pkg:maven/org.springframework/spring-aop@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-beans@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-expression@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.slf4j/jul-to-slf4j@2.0.9?type=jar", + "dependsOn" : [ + "pkg:maven/org.slf4j/slf4j-api@2.0.9?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.apache.logging.log4j/log4j-api@2.21.1?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/net.bytebuddy/byte-buddy-agent@1.14.10?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.springframework/spring-web@6.1.2?type=jar", + "dependsOn" : [ + "pkg:maven/io.micrometer/micrometer-observation@1.12.1?type=jar", + "pkg:maven/org.springframework/spring-beans@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.junit.jupiter/junit-jupiter@5.10.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.junit.jupiter/junit-jupiter-params@5.10.1?type=jar", + "pkg:maven/org.junit.jupiter/junit-jupiter-api@5.10.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-tomcat@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/jakarta.annotation/jakarta.annotation-api@2.1.1?type=jar", + "pkg:maven/org.apache.tomcat.embed/tomcat-embed-websocket@10.1.17?type=jar", + "pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@10.1.17?type=jar", + "pkg:maven/org.apache.tomcat.embed/tomcat-embed-el@10.1.17?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.awaitility/awaitility@4.2.0?type=jar", + "dependsOn" : [ + "pkg:maven/org.hamcrest/hamcrest@2.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-actuator@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot@3.2.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework/spring-context@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/io.micrometer/micrometer-observation@1.12.1?type=jar", + "dependsOn" : [ + "pkg:maven/io.micrometer/micrometer-commons@1.12.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework/spring-jcl@6.1.2?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-json@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot-starter@3.2.1?type=jar", + "pkg:maven/org.springframework/spring-web@6.1.2?type=jar", + "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jdk8@2.15.3?type=jar", + "pkg:maven/com.fasterxml.jackson.module/jackson-module-parameter-names@2.15.3?type=jar", + "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310@2.15.3?type=jar", + "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.3?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.mockito/mockito-junit-jupiter@5.7.0?type=jar", + "dependsOn" : [ + "pkg:maven/org.mockito/mockito-core@5.7.0?type=jar" + ] + }, + { + "ref" : "pkg:maven/net.bytebuddy/byte-buddy@1.14.10?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.junit.jupiter/junit-jupiter-api@5.10.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.junit.platform/junit-platform-commons@1.10.1?type=jar", + "pkg:maven/org.opentest4j/opentest4j@1.3.0?type=jar", + "pkg:maven/org.apiguardian/apiguardian-api@1.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310@2.15.3?type=jar", + "dependsOn" : [ + "pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.15.3?type=jar", + "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.3?type=jar", + "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.3?type=jar" + ] + }, + { + "ref" : "pkg:maven/jakarta.xml.bind/jakarta.xml.bind-api@4.0.1?type=jar", + "dependsOn" : [ + "pkg:maven/jakarta.activation/jakarta.activation-api@2.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-starter@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-starter-logging@3.2.1?type=jar", + "pkg:maven/jakarta.annotation/jakarta.annotation-api@2.1.1?type=jar", + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar", + "pkg:maven/org.yaml/snakeyaml@2.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.latencyutils/LatencyUtils@2.0.3?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/com.vaadin.external.google/android-json@0.0.20131108.vaadin1?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.springframework/spring-test@6.1.2?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.springframework/spring-aop@6.1.2?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework/spring-beans@6.1.2?type=jar", + "pkg:maven/org.springframework/spring-core@6.1.2?type=jar" + ] + }, + { + "ref" : "pkg:maven/jakarta.annotation/jakarta.annotation-api@2.1.1?type=jar", + "dependsOn" : [ ] + }, + { + "ref" : "pkg:maven/org.springframework.boot/spring-boot-starter-actuator@3.2.1?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot-starter@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-actuator-autoconfigure@3.2.1?type=jar", + "pkg:maven/io.micrometer/micrometer-jakarta9@1.12.1?type=jar", + "pkg:maven/io.micrometer/micrometer-observation@1.12.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.3?type=jar", + "dependsOn" : [ + "pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.15.3?type=jar", + "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.3?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.example/cyclonedx@0.0.1-SNAPSHOT?type=jar", + "dependsOn" : [ + "pkg:maven/org.springframework.boot/spring-boot-starter-actuator@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-starter-web@3.2.1?type=jar", + "pkg:maven/org.springframework.boot/spring-boot-starter-test@3.2.1?type=jar" + ] + }, + { + "ref" : "pkg:maven/org.ow2.asm/asm@9.3?type=jar", + "dependsOn" : [ ] + } + ] +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-actuator/src/test/resources/sbom/spdx.json b/spring-boot-project/spring-boot-actuator/src/test/resources/sbom/spdx.json new file mode 100644 index 000000000000..37e278638766 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/resources/sbom/spdx.json @@ -0,0 +1,3909 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "documentNamespace": "https://anchore.com/syft/file/sbom-test-gradle-0.0.1-SNAPSHOT.jar-d1583014-0f58-4476-8f5f-dbbcd2df5102", + "creationInfo": { + "licenseListVersion": "3.23", + "creators": [ + "Organization: Anchore, Inc", + "Tool: syft-0.105.0" + ], + "created": "2024-02-15T12:39:33Z" + }, + "packages": [ + { + "name": "HdrHistogram", + "SPDXID": "SPDXRef-Package-java-archive-HdrHistogram-2c7953c2c68ec3bc", + "versionInfo": "2.1.12", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "6eb7552156e0d517ae80cc2247be1427c8d90452" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-d509473237fa971bc0a8ad7708f3cd561fcf86ef2e611701ed8eec621fd6575e", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:HdrHistogram:HdrHistogram:2.1.12:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:hdrhistogram:HdrHistogram:2.1.12:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.hdrhistogram/HdrHistogram@2.1.12" + } + ] + }, + { + "name": "LatencyUtils", + "SPDXID": "SPDXRef-Package-java-archive-LatencyUtils-f9418986cc24a153", + "versionInfo": "2.0.3", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "769c0b82cb2421c8256300e907298a9410a2a3d3" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-Public-Domain--per-Creative-Commons-CC0", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:LatencyUtils:LatencyUtils:2.0.3:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:latencyutils:LatencyUtils:2.0.3:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.latencyutils/LatencyUtils@2.0.3" + } + ] + }, + { + "name": "jackson-annotations", + "SPDXID": "SPDXRef-Package-java-archive-jackson-annotations-c1e7975b6f55f7e8", + "versionInfo": "2.16.1", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "fd441d574a71e7d10a4f73de6609f881d8cdfeec" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-https---www.apache.org-licenses-LICENSE-2.0.txt", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-annotations:jackson-annotations:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-annotations:jackson_annotations:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_annotations:jackson-annotations:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_annotations:jackson_annotations:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:jackson-annotations:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:jackson_annotations:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-annotations:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:jackson-annotations:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:jackson_annotations:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_annotations:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:core:jackson-annotations:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:core:jackson_annotations:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:core:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.16.1" + } + ] + }, + { + "name": "jackson-core", + "SPDXID": "SPDXRef-Package-java-archive-jackson-core-0408f25059f495c5", + "versionInfo": "2.16.1", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "9456bb3cdd0f79f91a5f730a1b1bb041a380c91f" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-https---www.apache.org-licenses-LICENSE-2.0.txt", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-core:jackson-core:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-core:jackson_core:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_core:jackson-core:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_core:jackson_core:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:jackson-core:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:jackson_core:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-core:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:jackson-core:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:jackson_core:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_core:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:core:jackson-core:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:core:jackson_core:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-core:core:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_core:core:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:core:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:core:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:core:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:core:core:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.16.1" + } + ] + }, + { + "name": "jackson-databind", + "SPDXID": "SPDXRef-Package-java-archive-jackson-databind-9ad3756f611d1ed2", + "versionInfo": "2.16.1", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "02a16efeb840c45af1e2f31753dfe76795278b73" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-https---www.apache.org-licenses-LICENSE-2.0.txt", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-databind:jackson-databind:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-databind:jackson_databind:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_databind:jackson-databind:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_databind:jackson_databind:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:jackson-databind:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:jackson_databind:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-databind:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:jackson-databind:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:jackson_databind:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_databind:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:core:jackson-databind:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:core:jackson_databind:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:core:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.16.1" + } + ] + }, + { + "name": "jackson-datatype-jdk8", + "SPDXID": "SPDXRef-Package-java-archive-jackson-datatype-jdk8-846731ed2e85561c", + "versionInfo": "2.16.1", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "695d9b8639cfc7a42a0507708cef2366fe492a44" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-http---www.apache.org-licenses-LICENSE-2.0.txt", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-datatype-jdk8:jackson-datatype-jdk8:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-datatype-jdk8:jackson_datatype_jdk8:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_datatype_jdk8:jackson-datatype-jdk8:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_datatype_jdk8:jackson_datatype_jdk8:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-datatype:jackson-datatype-jdk8:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-datatype:jackson_datatype_jdk8:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_datatype:jackson-datatype-jdk8:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_datatype:jackson_datatype_jdk8:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:jackson-datatype-jdk8:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:jackson_datatype_jdk8:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:datatype:jackson-datatype-jdk8:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:datatype:jackson_datatype_jdk8:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-datatype-jdk8:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:jackson-datatype-jdk8:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:jackson_datatype_jdk8:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_datatype_jdk8:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-datatype:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_datatype:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:datatype:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jdk8@2.16.1" + } + ] + }, + { + "name": "jackson-datatype-jsr310", + "SPDXID": "SPDXRef-Package-java-archive-jackson-datatype-jsr310-1347581c05f302c0", + "versionInfo": "2.16.1", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "36a418325c618e440e5ccb80b75c705d894f50bd" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-http---www.apache.org-licenses-LICENSE-2.0.txt", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-datatype-jsr310:jackson-datatype-jsr310:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-datatype-jsr310:jackson_datatype_jsr310:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_datatype_jsr310:jackson-datatype-jsr310:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_datatype_jsr310:jackson_datatype_jsr310:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-datatype:jackson-datatype-jsr310:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-datatype:jackson_datatype_jsr310:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_datatype:jackson-datatype-jsr310:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_datatype:jackson_datatype_jsr310:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:jackson-datatype-jsr310:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:jackson_datatype_jsr310:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:datatype:jackson-datatype-jsr310:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:datatype:jackson_datatype_jsr310:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-datatype-jsr310:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:jackson-datatype-jsr310:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:jackson_datatype_jsr310:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_datatype_jsr310:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-datatype:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_datatype:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:datatype:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310@2.16.1" + } + ] + }, + { + "name": "jackson-module-parameter-names", + "SPDXID": "SPDXRef-Package-java-archive-jackson-module-parameter-names-f5bca9d628ab321f", + "versionInfo": "2.16.1", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "9e167afd1596e6a6aa6fe4e1af17f4ce8be0676f" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-http---www.apache.org-licenses-LICENSE-2.0.txt", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-module-parameter-names:jackson-module-parameter-names:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-module-parameter-names:jackson_module_parameter_names:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_module_parameter_names:jackson-module-parameter-names:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_module_parameter_names:jackson_module_parameter_names:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-module-parameter:jackson-module-parameter-names:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-module-parameter:jackson_module_parameter_names:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_module_parameter:jackson-module-parameter-names:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_module_parameter:jackson_module_parameter_names:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-module:jackson-module-parameter-names:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-module:jackson_module_parameter_names:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_module:jackson-module-parameter-names:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_module:jackson_module_parameter_names:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:jackson-module-parameter-names:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:jackson_module_parameter_names:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-module-parameter-names:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:jackson-module-parameter-names:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:jackson_module_parameter_names:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_module_parameter_names:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:module:jackson-module-parameter-names:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:module:jackson_module_parameter_names:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-module-parameter:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_module_parameter:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson-module:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson_module:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:fasterxml:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jackson:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:module:jackson:2.16.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/com.fasterxml.jackson.module/jackson-module-parameter-names@2.16.1" + } + ] + }, + { + "name": "jakarta.annotation-api", + "SPDXID": "SPDXRef-Package-java-archive-jakarta.annotation-api-77a5bf527533d628", + "versionInfo": "2.1.1", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "48b9bda22b091b1f48b13af03fe36db3be6e1ae3" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-85a9a90b97292e5203565dd71a1a086ca3fe4d8ccea74453294fee37d5b0c7ae", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jakarta.annotation-api:jakarta.annotation-api:2.1.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jakarta.annotation-api:jakarta.annotation_api:2.1.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jakarta.annotation_api:jakarta.annotation-api:2.1.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jakarta.annotation_api:jakarta.annotation_api:2.1.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:eclipse-foundation:jakarta.annotation-api:2.1.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:eclipse-foundation:jakarta.annotation_api:2.1.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:eclipse_foundation:jakarta.annotation-api:2.1.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:eclipse_foundation:jakarta.annotation_api:2.1.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jakarta.annotation:jakarta.annotation-api:2.1.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jakarta.annotation:jakarta.annotation_api:2.1.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:glassfish:jakarta.annotation-api:2.1.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:glassfish:jakarta.annotation_api:2.1.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/jakarta.annotation/jakarta.annotation-api@2.1.1" + } + ] + }, + { + "name": "jul-to-slf4j", + "SPDXID": "SPDXRef-Package-java-archive-jul-to-slf4j-598311f4a5b2a501", + "versionInfo": "2.0.11", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "279356f8e873b1a26badd8bbb3284b5c3b22c770" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-http---www.opensource.org-licenses-mit-license.php", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jul-to-slf4j:jul-to-slf4j:2.0.11:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jul-to-slf4j:jul_to_slf4j:2.0.11:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jul_to_slf4j:jul-to-slf4j:2.0.11:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jul_to_slf4j:jul_to_slf4j:2.0.11:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jul-to:jul-to-slf4j:2.0.11:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jul-to:jul_to_slf4j:2.0.11:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jul_to:jul-to-slf4j:2.0.11:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jul_to:jul_to_slf4j:2.0.11:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:slf4j:jul-to-slf4j:2.0.11:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:slf4j:jul_to_slf4j:2.0.11:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jul:jul-to-slf4j:2.0.11:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:jul:jul_to_slf4j:2.0.11:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.slf4j/jul-to-slf4j@2.0.11" + } + ] + }, + { + "name": "log4j-api", + "SPDXID": "SPDXRef-Package-java-archive-log4j-api-c404b33d3a8ce0d8", + "versionInfo": "2.22.1", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "bea6fede6328fabafd7e68363161a7ea6605abd1" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-bc2074dd7e94ae9ffbcea3c53de6625b1b651c330895f46cf72d207c3025b98b", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:log4j-api:2.22.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:log4j_api:2.22.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:log4j:2.22.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:api:2.22.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.logging.log4j/log4j-api@2.22.1" + } + ] + }, + { + "name": "log4j-to-slf4j", + "SPDXID": "SPDXRef-Package-java-archive-log4j-to-slf4j-860f45be6a175d16", + "versionInfo": "2.22.1", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "b5e67b6acac768bfec1d1d6991504f45453abcad" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-bc2074dd7e94ae9ffbcea3c53de6625b1b651c330895f46cf72d207c3025b98b", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:log4j-to-slf4j:2.22.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:log4j_to_slf4j:2.22.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:log4j:2.22.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:slf4j:2.22.1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.logging.log4j/log4j-to-slf4j@2.22.1" + } + ] + }, + { + "name": "logback-classic", + "SPDXID": "SPDXRef-Package-java-archive-logback-classic-d91fe3ae6bb15cad", + "versionInfo": "1.4.14", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "d98bc162275134cdf1518774da4a2a17ef6fb94d" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-b997c307e688e15a53c7603c100d346cb7dc9726146cb5644d66bddc7ed1c8ca", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:logback-classic:logback-classic:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:logback-classic:logback_classic:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:logback_classic:logback-classic:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:logback_classic:logback_classic:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:logback:logback-classic:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:logback:logback_classic:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:qos-ch:logback-classic:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:qos-ch:logback_classic:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:qos_ch:logback-classic:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:qos_ch:logback_classic:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/ch.qos.logback/logback-classic@1.4.14" + } + ] + }, + { + "name": "logback-core", + "SPDXID": "SPDXRef-Package-java-archive-logback-core-3748310e1aac44ea", + "versionInfo": "1.4.14", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "4d3c2248219ac0effeb380ed4c5280a80bf395e8" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-b997c307e688e15a53c7603c100d346cb7dc9726146cb5644d66bddc7ed1c8ca", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:logback-core:logback-core:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:logback-core:logback_core:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:logback_core:logback-core:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:logback_core:logback_core:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:logback:logback-core:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:logback:logback_core:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:qos-ch:logback-core:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:qos-ch:logback_core:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:qos_ch:logback-core:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:qos_ch:logback_core:1.4.14:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/ch.qos.logback/logback-core@1.4.14" + } + ] + }, + { + "name": "micrometer-commons", + "SPDXID": "SPDXRef-Package-java-archive-micrometer-commons-c46f369578c77c43", + "versionInfo": "1.13.0-M1", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "e738daf6678eedf8e0c40a782bdb0df064a391e5" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "Apache-2.0", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer-commons:micrometer-commons:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer-commons:micrometer_commons:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer_commons:micrometer-commons:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer_commons:micrometer_commons:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer:micrometer-commons:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer:micrometer_commons:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/io.micrometer/micrometer-commons@1.13.0-M1" + } + ] + }, + { + "name": "micrometer-core", + "SPDXID": "SPDXRef-Package-java-archive-micrometer-core-3c0d8567351e2ae4", + "versionInfo": "1.13.0-M1", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "49d54a8ed6d3266b4f2691027d95144e946bbe36" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "Apache-2.0", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer-core:micrometer-core:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer-core:micrometer_core:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer_core:micrometer-core:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer_core:micrometer_core:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer:micrometer-core:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer:micrometer_core:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/io.micrometer/micrometer-core@1.13.0-M1" + } + ] + }, + { + "name": "micrometer-jakarta9", + "SPDXID": "SPDXRef-Package-java-archive-micrometer-jakarta9-f4ea2c844b65a026", + "versionInfo": "1.13.0-M1", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "74087b670cad9f9883228ee2aa871f51b53f827a" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "Apache-2.0", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer-jakarta9:micrometer-jakarta9:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer-jakarta9:micrometer_jakarta9:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer_jakarta9:micrometer-jakarta9:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer_jakarta9:micrometer_jakarta9:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer:micrometer-jakarta9:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer:micrometer_jakarta9:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/io.micrometer/micrometer-jakarta9@1.13.0-M1" + } + ] + }, + { + "name": "micrometer-observation", + "SPDXID": "SPDXRef-Package-java-archive-micrometer-observation-26b8a84479010ca8", + "versionInfo": "1.13.0-M1", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "c06e5e0f9b6edc9c0c0ac3dd46a2117ce6f16a9d" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "Apache-2.0", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer-observation:micrometer-observation:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer-observation:micrometer_observation:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer_observation:micrometer-observation:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer_observation:micrometer_observation:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer:micrometer-observation:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:micrometer:micrometer_observation:1.13.0-M1:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/io.micrometer/micrometer-observation@1.13.0-M1" + } + ] + }, + { + "name": "sbom-test-gradle", + "SPDXID": "SPDXRef-Package-java-archive-sbom-test-gradle-93ed082a147d9796", + "versionInfo": "0.0.1-SNAPSHOT", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "8ccd6688e9d8e15d18e0f10967867e5e30729a4c" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom-test-gradle:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom-test-gradle:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom_test_gradle:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom_test_gradle:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:JarLauncher:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:JarLauncher:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom-test-gradle:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom_test_gradle:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom-test:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom-test:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom_test:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom_test:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:JarLauncher:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:launch:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:launch:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:loader:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:loader:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom-test-gradle:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom-test-gradle:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom_test_gradle:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom_test_gradle:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom-test-gradle:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom-test:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom_test:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom_test_gradle:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:JarLauncher:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:JarLauncher:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:launch:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:loader:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:JarLauncher:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom-test:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom-test:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom_test:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom_test:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom-test:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom_test:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:launch:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:launch:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:loader:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:loader:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:launch:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:loader:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:sbom:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.springframework.boot.loader.launch.JarLauncher/sbom-test-gradle@0.0.1-SNAPSHOT" + } + ] + }, + { + "name": "slf4j-api", + "SPDXID": "SPDXRef-Package-java-archive-slf4j-api-44752cfa6770756d", + "versionInfo": "2.0.11", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "ad96c3f8cf895e696dd35c2bc8e8ebe710be9e6d" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-http---www.opensource.org-licenses-mit-license.php", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:slf4j-api:slf4j-api:2.0.11:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:slf4j-api:slf4j_api:2.0.11:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:slf4j_api:slf4j-api:2.0.11:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:slf4j_api:slf4j_api:2.0.11:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:slf4j:slf4j-api:2.0.11:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:slf4j:slf4j_api:2.0.11:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.slf4j/slf4j-api@2.0.11" + } + ] + }, + { + "name": "snakeyaml", + "SPDXID": "SPDXRef-Package-java-archive-snakeyaml-f4585c65c0a5b26a", + "versionInfo": "2.2", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "3af797a25458550a16bf89acc8e4ab2b7f2bfce0" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-http---www.apache.org-licenses-LICENSE-2.0.txt", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:snakeyaml:snakeyaml:2.2:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:yaml:snakeyaml:2.2:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.yaml/snakeyaml@2.2" + } + ] + }, + { + "name": "spring-aop", + "SPDXID": "SPDXRef-Package-java-archive-spring-aop-1e7758a78bbc15ee", + "versionInfo": "6.1.4-SNAPSHOT", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "b02165904562fc487cde57ca75e063561d905f74" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "Apache-2.0 AND BSD-3-Clause", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring-aop:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring_aop:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-aop:spring-aop:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-aop:spring_aop:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_aop:spring-aop:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_aop:spring_aop:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring-aop:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring_aop:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.springframework/spring-aop@6.1.4-SNAPSHOT" + } + ] + }, + { + "name": "spring-beans", + "SPDXID": "SPDXRef-Package-java-archive-spring-beans-bb7e773a923726bb", + "versionInfo": "6.1.4-SNAPSHOT", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "fa8be0f856958fdd33eef9e718b3a65f7130bbd2" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "Apache-2.0 AND BSD-3-Clause", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring-beans:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring_beans:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-beans:spring-beans:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-beans:spring_beans:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_beans:spring-beans:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_beans:spring_beans:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring-beans:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring_beans:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.springframework/spring-beans@6.1.4-SNAPSHOT" + } + ] + }, + { + "name": "spring-boot", + "SPDXID": "SPDXRef-Package-java-archive-spring-boot-a11948291446c2f5", + "versionInfo": "3.3.0-SNAPSHOT", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "d882660ea3deafe921faba8b17e7d94ef9556c47" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "Apache-2.0", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring-boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring_boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot:spring-boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot:spring_boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot:spring-boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot:spring_boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring-boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring_boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:spring-boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:spring_boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.springframework.boot/spring-boot@3.3.0-SNAPSHOT" + } + ] + }, + { + "name": "spring-boot-actuator", + "SPDXID": "SPDXRef-Package-java-archive-spring-boot-actuator-f83d629168e25cce", + "versionInfo": "3.3.0-SNAPSHOT", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "d0d018780795da57afa8edae7436646bccd55722" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "Apache-2.0", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot-actuator:spring-boot-actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot-actuator:spring_boot_actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot_actuator:spring-boot-actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot_actuator:spring_boot_actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring-boot-actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring_boot_actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot:spring-boot-actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot:spring_boot_actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot:spring-boot-actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot:spring_boot_actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring-boot-actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring_boot_actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:spring-boot-actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:spring_boot_actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot-actuator:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot_actuator:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.springframework.boot/spring-boot-actuator@3.3.0-SNAPSHOT" + } + ] + }, + { + "name": "spring-boot-actuator-autoconfigure", + "SPDXID": "SPDXRef-Package-java-archive-spring-boot-actuator-autoconfigure-b8eb893518786bb8", + "versionInfo": "3.3.0-SNAPSHOT", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "8b8f74be822e6f2ab120ea0687acf629ef114399" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "Apache-2.0", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot-actuator-autoconfigure:spring-boot-actuator-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot-actuator-autoconfigure:spring_boot_actuator_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot_actuator_autoconfigure:spring-boot-actuator-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot_actuator_autoconfigure:spring_boot_actuator_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot-actuator:spring-boot-actuator-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot-actuator:spring_boot_actuator_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot_actuator:spring-boot-actuator-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot_actuator:spring_boot_actuator_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring-boot-actuator-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring_boot_actuator_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot:spring-boot-actuator-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot:spring_boot_actuator_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot:spring-boot-actuator-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot:spring_boot_actuator_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring-boot-actuator-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring_boot_actuator_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:spring-boot-actuator-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:spring_boot_actuator_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot-actuator-autoconfigure:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot_actuator_autoconfigure:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot-actuator:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot_actuator:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.springframework.boot/spring-boot-actuator-autoconfigure@3.3.0-SNAPSHOT" + } + ] + }, + { + "name": "spring-boot-autoconfigure", + "SPDXID": "SPDXRef-Package-java-archive-spring-boot-autoconfigure-b40bdc90eb8832a3", + "versionInfo": "3.3.0-SNAPSHOT", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "31a960bb63af836f35760077af8ef58d24b548e3" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "Apache-2.0", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot-autoconfigure:spring-boot-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot-autoconfigure:spring_boot_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot_autoconfigure:spring-boot-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot_autoconfigure:spring_boot_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring-boot-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring_boot_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot:spring-boot-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot:spring_boot_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot:spring-boot-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot:spring_boot_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring-boot-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring_boot_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:spring-boot-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:spring_boot_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot-autoconfigure:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot_autoconfigure:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.3.0-SNAPSHOT" + } + ] + }, + { + "name": "spring-boot-jarmode-layertools", + "SPDXID": "SPDXRef-Package-java-archive-spring-boot-jarmode-layertools-8069f3f866b2e657", + "versionInfo": "3.3.0-SNAPSHOT", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "d86f1782ad3d9ee047863a5023aaa22f858cd9a4" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "Apache-2.0", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot-jarmode-layertools:spring-boot-jarmode-layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot-jarmode-layertools:spring_boot_jarmode_layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot_jarmode_layertools:spring-boot-jarmode-layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot_jarmode_layertools:spring_boot_jarmode_layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot-jarmode:spring-boot-jarmode-layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot-jarmode:spring_boot_jarmode_layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot_jarmode:spring-boot-jarmode-layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot_jarmode:spring_boot_jarmode_layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring-boot-jarmode-layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring_boot_jarmode_layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot:spring-boot-jarmode-layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot:spring_boot_jarmode_layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot:spring-boot-jarmode-layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot:spring_boot_jarmode_layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring-boot-jarmode-layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring_boot_jarmode_layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:spring-boot-jarmode-layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:spring_boot_jarmode_layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot-jarmode-layertools:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot_jarmode_layertools:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot-jarmode:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot_jarmode:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.springframework.boot/spring-boot-jarmode-layertools@3.3.0-SNAPSHOT" + } + ] + }, + { + "name": "spring-context", + "SPDXID": "SPDXRef-Package-java-archive-spring-context-3d5d71e0e85398af", + "versionInfo": "6.1.4-SNAPSHOT", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "75440f70a649ca15948af5923ebdef345848a856" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "Apache-2.0 AND BSD-3-Clause", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring-context:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring_context:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-context:spring-context:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-context:spring_context:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_context:spring-context:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_context:spring_context:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring-context:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring_context:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.springframework/spring-context@6.1.4-SNAPSHOT" + } + ] + }, + { + "name": "spring-core", + "SPDXID": "SPDXRef-Package-java-archive-spring-core-519fe54307d2d43d", + "versionInfo": "6.1.4-SNAPSHOT", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "27d0900a14e240a7311c979e7b30cf65f9de9074" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "Apache-2.0 AND BSD-3-Clause", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource-spring-framework:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource_spring_framework:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource-spring:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource_spring:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:pivotal_software:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-framework:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_framework:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource-spring-framework:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource_spring_framework:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-core:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_core:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource-spring-framework:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource-spring-framework:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource_spring_framework:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource_spring_framework:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource-spring:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource_spring:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:vmware:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:pivotal_software:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-framework:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_framework:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource-spring:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource-spring:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource_spring:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource_spring:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:pivotal_software:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:pivotal_software:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-core:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-framework:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-framework:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_core:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_framework:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_framework:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springsource:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-core:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-core:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_core:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_core:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:vmware:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:vmware:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:vmware:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.springframework/spring-core@6.1.4-SNAPSHOT" + } + ] + }, + { + "name": "spring-expression", + "SPDXID": "SPDXRef-Package-java-archive-spring-expression-546794e924e39088", + "versionInfo": "6.1.4-SNAPSHOT", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "a5d7041ca11fd188e9d17ac8a795eabed8be55e4" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "Apache-2.0 AND BSD-3-Clause", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-expression:spring-expression:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-expression:spring_expression:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_expression:spring-expression:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_expression:spring_expression:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring-expression:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring_expression:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring-expression:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring_expression:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.springframework/spring-expression@6.1.4-SNAPSHOT" + } + ] + }, + { + "name": "spring-jcl", + "SPDXID": "SPDXRef-Package-java-archive-spring-jcl-173ea637a5756944", + "versionInfo": "6.1.4-SNAPSHOT", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "604cea28d23d8027a31c35f372d2b8d0fdec211d" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "Apache-2.0 AND BSD-3-Clause", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring-jcl:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring_jcl:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-jcl:spring-jcl:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-jcl:spring_jcl:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_jcl:spring-jcl:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_jcl:spring_jcl:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring-jcl:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring_jcl:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.springframework/spring-jcl@6.1.4-SNAPSHOT" + } + ] + }, + { + "name": "spring-web", + "SPDXID": "SPDXRef-Package-java-archive-spring-web-adc63cefcede34fc", + "versionInfo": "6.1.4-SNAPSHOT", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "c0600dcd73db226c3d121af16d6a155ecee08d30" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "Apache-2.0 AND BSD-3-Clause", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring-web:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring_web:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-web:spring-web:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-web:spring_web:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_web:spring-web:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_web:spring_web:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring-web:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring_web:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.springframework/spring-web@6.1.4-SNAPSHOT" + } + ] + }, + { + "name": "spring-webmvc", + "SPDXID": "SPDXRef-Package-java-archive-spring-webmvc-940aed7082581b67", + "versionInfo": "6.1.4-SNAPSHOT", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "34a510cf565bec1c2f74f049b1730b22f877bd37" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "Apache-2.0 AND BSD-3-Clause", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring-webmvc:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:springframework:spring_webmvc:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-webmvc:spring-webmvc:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring-webmvc:spring_webmvc:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_webmvc:spring-webmvc:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring_webmvc:spring_webmvc:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring-webmvc:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:spring:spring_webmvc:6.1.4-SNAPSHOT:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.springframework/spring-webmvc@6.1.4-SNAPSHOT" + } + ] + }, + { + "name": "tomcat-embed-core", + "SPDXID": "SPDXRef-Package-java-archive-tomcat-embed-core-a753aca6ee68c738", + "versionInfo": "10.1.18", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "bff6c34649d1dd7b509e819794d73ba795947dcf" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-https---www.apache.org-licenses-LICENSE-2.0.txt", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:tomcat-embed-core:10.1.18:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:tomcat_embed_core:10.1.18:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:tomcat:10.1.18:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:embed:10.1.18:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@10.1.18" + } + ] + }, + { + "name": "tomcat-embed-el", + "SPDXID": "SPDXRef-Package-java-archive-tomcat-embed-el-7a59d22722f7701b", + "versionInfo": "10.1.18", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "b2c4dc05abd363c63b245523bb071727aa2f1046" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-https---www.apache.org-licenses-LICENSE-2.0.txt", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:tomcat-embed-el:10.1.18:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:tomcat_embed_el:10.1.18:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:tomcat:10.1.18:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:embed:10.1.18:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.tomcat.embed/tomcat-embed-el@10.1.18" + } + ] + }, + { + "name": "tomcat-embed-websocket", + "SPDXID": "SPDXRef-Package-java-archive-tomcat-embed-websocket-6c04f8ee22f9157e", + "versionInfo": "10.1.18", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "83a3bc6898f2ceed2357ba231a5e83dc2016d454" + } + ], + "sourceInfo": "acquired package info from installed java archive: /sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "LicenseRef-https---www.apache.org-licenses-LICENSE-2.0.txt", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:tomcat-embed-websocket:10.1.18:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:tomcat_embed_websocket:10.1.18:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:tomcat:10.1.18:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:apache:embed:10.1.18:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:maven/org.apache.tomcat.embed/tomcat-embed-websocket@10.1.18" + } + ] + }, + { + "name": "sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "SPDXID": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "versionInfo": "sha256:f1802eb27e84114cfd7213ec83534a4b3219da6c4b2dcc827e0130b69ffa63b9", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "f1802eb27e84114cfd7213ec83534a4b3219da6c4b2dcc827e0130b69ffa63b9" + } + ], + "primaryPackagePurpose": "FILE" + } + ], + "files": [ + { + "fileName": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "SPDXID": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "0000000000000000000000000000000000000000" + } + ], + "licenseConcluded": "NOASSERTION", + "copyrightText": "" + } + ], + "hasExtractedLicensingInfos": [ + { + "licenseId": "LicenseRef-85a9a90b97292e5203565dd71a1a086ca3fe4d8ccea74453294fee37d5b0c7ae", + "extractedText": "http://www.eclipse.org/legal/epl-2.0, https://www.gnu.org/software/classpath/license.html" + }, + { + "licenseId": "LicenseRef-Public-Domain--per-Creative-Commons-CC0", + "extractedText": "Public Domain, per Creative Commons CC0" + }, + { + "licenseId": "LicenseRef-b997c307e688e15a53c7603c100d346cb7dc9726146cb5644d66bddc7ed1c8ca", + "extractedText": "http://www.eclipse.org/legal/epl-v10.html, http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" + }, + { + "licenseId": "LicenseRef-bc2074dd7e94ae9ffbcea3c53de6625b1b651c330895f46cf72d207c3025b98b", + "extractedText": "\"Apache-2.0\";link=\"https://www.apache.org/licenses/LICENSE-2.0.txt\"" + }, + { + "licenseId": "LicenseRef-d509473237fa971bc0a8ad7708f3cd561fcf86ef2e611701ed8eec621fd6575e", + "extractedText": "http://creativecommons.org/publicdomain/zero/1.0/, https://opensource.org/licenses/BSD-2-Clause" + }, + { + "licenseId": "LicenseRef-http---www.apache.org-licenses-LICENSE-2.0.txt", + "extractedText": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, + { + "licenseId": "LicenseRef-http---www.opensource.org-licenses-mit-license.php", + "extractedText": "http://www.opensource.org/licenses/mit-license.php" + }, + { + "licenseId": "LicenseRef-https---www.apache.org-licenses-LICENSE-2.0.txt", + "extractedText": "https://www.apache.org/licenses/LICENSE-2.0.txt" + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Package-java-archive-jackson-core-0408f25059f495c5", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-jackson-datatype-jsr310-1347581c05f302c0", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-spring-jcl-173ea637a5756944", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-spring-aop-1e7758a78bbc15ee", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-micrometer-observation-26b8a84479010ca8", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-HdrHistogram-2c7953c2c68ec3bc", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-logback-core-3748310e1aac44ea", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-micrometer-core-3c0d8567351e2ae4", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-spring-context-3d5d71e0e85398af", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-slf4j-api-44752cfa6770756d", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-spring-core-519fe54307d2d43d", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-spring-expression-546794e924e39088", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-jul-to-slf4j-598311f4a5b2a501", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-tomcat-embed-websocket-6c04f8ee22f9157e", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-jakarta.annotation-api-77a5bf527533d628", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-tomcat-embed-el-7a59d22722f7701b", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-spring-boot-jarmode-layertools-8069f3f866b2e657", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-jackson-datatype-jdk8-846731ed2e85561c", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-log4j-to-slf4j-860f45be6a175d16", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-sbom-test-gradle-93ed082a147d9796", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-spring-webmvc-940aed7082581b67", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-jackson-databind-9ad3756f611d1ed2", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-spring-boot-a11948291446c2f5", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-tomcat-embed-core-a753aca6ee68c738", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-spring-web-adc63cefcede34fc", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-spring-boot-autoconfigure-b40bdc90eb8832a3", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-spring-boot-actuator-autoconfigure-b8eb893518786bb8", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-spring-beans-bb7e773a923726bb", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-jackson-annotations-c1e7975b6f55f7e8", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-log4j-api-c404b33d3a8ce0d8", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-micrometer-commons-c46f369578c77c43", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-logback-classic-d91fe3ae6bb15cad", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-snakeyaml-f4585c65c0a5b26a", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-micrometer-jakarta9-f4ea2c844b65a026", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-jackson-module-parameter-names-f5bca9d628ab321f", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-spring-boot-actuator-f83d629168e25cce", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-java-archive-LatencyUtils-f9418986cc24a153", + "relatedSpdxElement": "SPDXRef-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar-af7261c65fbd5345", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-HdrHistogram-2c7953c2c68ec3bc", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-LatencyUtils-f9418986cc24a153", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-jackson-annotations-c1e7975b6f55f7e8", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-jackson-core-0408f25059f495c5", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-jackson-databind-9ad3756f611d1ed2", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-jackson-datatype-jdk8-846731ed2e85561c", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-jackson-datatype-jsr310-1347581c05f302c0", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-jackson-module-parameter-names-f5bca9d628ab321f", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-jakarta.annotation-api-77a5bf527533d628", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-jul-to-slf4j-598311f4a5b2a501", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-log4j-api-c404b33d3a8ce0d8", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-log4j-to-slf4j-860f45be6a175d16", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-logback-classic-d91fe3ae6bb15cad", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-logback-core-3748310e1aac44ea", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-micrometer-commons-c46f369578c77c43", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-micrometer-core-3c0d8567351e2ae4", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-micrometer-jakarta9-f4ea2c844b65a026", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-micrometer-observation-26b8a84479010ca8", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-sbom-test-gradle-93ed082a147d9796", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-slf4j-api-44752cfa6770756d", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-snakeyaml-f4585c65c0a5b26a", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-spring-aop-1e7758a78bbc15ee", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-spring-beans-bb7e773a923726bb", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-spring-boot-a11948291446c2f5", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-spring-boot-actuator-f83d629168e25cce", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-spring-boot-actuator-autoconfigure-b8eb893518786bb8", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-spring-boot-autoconfigure-b40bdc90eb8832a3", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-spring-boot-jarmode-layertools-8069f3f866b2e657", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-spring-context-3d5d71e0e85398af", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-spring-core-519fe54307d2d43d", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-spring-expression-546794e924e39088", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-spring-jcl-173ea637a5756944", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-spring-web-adc63cefcede34fc", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-spring-webmvc-940aed7082581b67", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-tomcat-embed-core-a753aca6ee68c738", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-tomcat-embed-el-7a59d22722f7701b", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relatedSpdxElement": "SPDXRef-Package-java-archive-tomcat-embed-websocket-6c04f8ee22f9157e", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-DocumentRoot-File-sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "relationshipType": "DESCRIBES" + } + ] +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/resources/sbom/syft.json b/spring-boot-project/spring-boot-actuator/src/test/resources/sbom/syft.json new file mode 100644 index 000000000000..526964e360a0 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/resources/sbom/syft.json @@ -0,0 +1,7525 @@ +{ + "artifacts": [ + { + "id": "2c7953c2c68ec3bc", + "name": "HdrHistogram", + "version": "2.1.12", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/HdrHistogram-2.1.12.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "http://creativecommons.org/publicdomain/zero/1.0/, https://opensource.org/licenses/BSD-2-Clause", + "spdxExpression": "", + "type": "declared", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/HdrHistogram-2.1.12.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:HdrHistogram:HdrHistogram:2.1.12:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:hdrhistogram:HdrHistogram:2.1.12:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.hdrhistogram/HdrHistogram@2.1.12", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/HdrHistogram-2.1.12.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Bnd-LastModified", + "value": "1575980548657" + }, + { + "key": "Build-Jdk", + "value": "1.8.0_232" + }, + { + "key": "Built-By", + "value": "gil" + }, + { + "key": "Bundle-Description", + "value": "HdrHistogram supports the recording and analyzing sampled data value counts across a configurable integer value range with configurable value precision within the range. Value precision is expressed as the number of significant digits in the value recording, and provides control over value quantization behavior across the value range and the subsequent value resolutionat any given level." + }, + { + "key": "Bundle-License", + "value": "http://creativecommons.org/publicdomain/zero/1.0/, https://opensource.org/licenses/BSD-2-Clause" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Bundle-Name", + "value": "HdrHistogram" + }, + { + "key": "Bundle-SymbolicName", + "value": "org.hdrhistogram.HdrHistogram" + }, + { + "key": "Bundle-Version", + "value": "2.1.12" + }, + { + "key": "Created-By", + "value": "Apache Maven Bundle Plugin" + }, + { + "key": "Export-Package", + "value": "org.HdrHistogram;version=\"2.1.12\",org.HdrHistogram.packedarray;version=\"2.1.12\"" + }, + { + "key": "Implementation-Title", + "value": "HdrHistogram" + }, + { + "key": "Implementation-Vendor-Id", + "value": "org.hdrhistogram" + }, + { + "key": "Implementation-Version", + "value": "2.1.12" + }, + { + "key": "Require-Capability", + "value": "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.7))\"" + }, + { + "key": "Specification-Title", + "value": "HdrHistogram" + }, + { + "key": "Specification-Version", + "value": "2.1.12" + }, + { + "key": "Tool", + "value": "Bnd-2.3.0.201405100607" + } + ] + }, + "pomProperties": { + "path": "META-INF/maven/org.hdrhistogram/HdrHistogram/pom.properties", + "name": "", + "groupId": "org.hdrhistogram", + "artifactId": "HdrHistogram", + "version": "2.1.12" + }, + "digest": [ + { + "algorithm": "sha1", + "value": "6eb7552156e0d517ae80cc2247be1427c8d90452" + } + ] + } + }, + { + "id": "f9418986cc24a153", + "name": "LatencyUtils", + "version": "2.0.3", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/LatencyUtils-2.0.3.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "Public Domain, per Creative Commons CC0", + "spdxExpression": "", + "type": "declared", + "urls": [ + "http://creativecommons.org/publicdomain/zero/1.0/" + ], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/LatencyUtils-2.0.3.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:LatencyUtils:LatencyUtils:2.0.3:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:latencyutils:LatencyUtils:2.0.3:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.latencyutils/LatencyUtils@2.0.3", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/LatencyUtils-2.0.3.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Archiver-Version", + "value": "Plexus Archiver" + }, + { + "key": "Built-By", + "value": "gil" + }, + { + "key": "Created-By", + "value": "Apache Maven 3.2.3" + }, + { + "key": "Build-Jdk", + "value": "1.8.0_45" + } + ] + }, + "pomProperties": { + "path": "META-INF/maven/org.latencyutils/LatencyUtils/pom.properties", + "name": "", + "groupId": "org.latencyutils", + "artifactId": "LatencyUtils", + "version": "2.0.3" + }, + "digest": [ + { + "algorithm": "sha1", + "value": "769c0b82cb2421c8256300e907298a9410a2a3d3" + } + ] + } + }, + { + "id": "c1e7975b6f55f7e8", + "name": "jackson-annotations", + "version": "2.16.1", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jackson-annotations-2.16.1.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "https://www.apache.org/licenses/LICENSE-2.0.txt", + "spdxExpression": "", + "type": "declared", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jackson-annotations-2.16.1.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:jackson-annotations:jackson-annotations:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-annotations:jackson_annotations:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_annotations:jackson-annotations:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_annotations:jackson_annotations:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:jackson-annotations:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:jackson_annotations:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-annotations:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:jackson-annotations:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:jackson_annotations:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_annotations:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:core:jackson-annotations:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:core:jackson_annotations:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:core:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.16.1", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jackson-annotations-2.16.1.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Bundle-Description", + "value": "Core annotations used for value types, used by Jackson data binding package." + }, + { + "key": "Implementation-Title", + "value": "Jackson-annotations" + }, + { + "key": "Bundle-License", + "value": "https://www.apache.org/licenses/LICENSE-2.0.txt" + }, + { + "key": "Bundle-SymbolicName", + "value": "com.fasterxml.jackson.core.jackson-annotations" + }, + { + "key": "Implementation-Version", + "value": "2.16.1" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Specification-Vendor", + "value": "FasterXML" + }, + { + "key": "Implementation-Vendor-Id", + "value": "com.fasterxml.jackson.core" + }, + { + "key": "Specification-Title", + "value": "Jackson-annotations" + }, + { + "key": "Bundle-DocURL", + "value": "https://github.com/FasterXML/jackson" + }, + { + "key": "Bundle-Vendor", + "value": "FasterXML" + }, + { + "key": "Require-Capability", + "value": "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.6))\"" + }, + { + "key": "Tool", + "value": "Bnd-6.3.1.202206071316" + }, + { + "key": "Implementation-Vendor", + "value": "FasterXML" + }, + { + "key": "Export-Package", + "value": "com.fasterxml.jackson.annotation;version=\"2.16.1\"" + }, + { + "key": "Bundle-Name", + "value": "Jackson-annotations" + }, + { + "key": "Bundle-Version", + "value": "2.16.1" + }, + { + "key": "X-Compile-Target-JDK", + "value": "1.6" + }, + { + "key": "X-Compile-Source-JDK", + "value": "1.6" + }, + { + "key": "Build-Jdk-Spec", + "value": "1.8" + }, + { + "key": "Created-By", + "value": "Apache Maven Bundle Plugin 5.1.9" + }, + { + "key": "Specification-Version", + "value": "2.16.1" + } + ] + }, + "pomProperties": { + "path": "META-INF/maven/com.fasterxml.jackson.core/jackson-annotations/pom.properties", + "name": "", + "groupId": "com.fasterxml.jackson.core", + "artifactId": "jackson-annotations", + "version": "2.16.1" + }, + "digest": [ + { + "algorithm": "sha1", + "value": "fd441d574a71e7d10a4f73de6609f881d8cdfeec" + } + ] + } + }, + { + "id": "0408f25059f495c5", + "name": "jackson-core", + "version": "2.16.1", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jackson-core-2.16.1.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "https://www.apache.org/licenses/LICENSE-2.0.txt", + "spdxExpression": "", + "type": "declared", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jackson-core-2.16.1.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:jackson-core:jackson-core:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-core:jackson_core:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_core:jackson-core:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_core:jackson_core:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:jackson-core:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:jackson_core:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-core:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:jackson-core:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:jackson_core:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_core:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:core:jackson-core:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:core:jackson_core:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-core:core:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_core:core:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:core:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:core:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:core:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:core:core:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.16.1", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jackson-core-2.16.1.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Bundle-License", + "value": "https://www.apache.org/licenses/LICENSE-2.0.txt" + }, + { + "key": "Bundle-SymbolicName", + "value": "com.fasterxml.jackson.core.jackson-core" + }, + { + "key": "Implementation-Vendor-Id", + "value": "com.fasterxml.jackson.core" + }, + { + "key": "Specification-Title", + "value": "Jackson-core" + }, + { + "key": "Bundle-DocURL", + "value": "https://github.com/FasterXML/jackson-core" + }, + { + "key": "Import-Package", + "value": "com.fasterxml.jackson.core;version=\"[2.16,3)\",com.fasterxml.jackson.core.async;version=\"[2.16,3)\",com.fasterxml.jackson.core.base;version=\"[2.16,3)\",com.fasterxml.jackson.core.exc;version=\"[2.16,3)\",com.fasterxml.jackson.core.format;version=\"[2.16,3)\",com.fasterxml.jackson.core.io;version=\"[2.16,3)\",com.fasterxml.jackson.core.io.schubfach;version=\"[2.16,3)\",com.fasterxml.jackson.core.json;version=\"[2.16,3)\",com.fasterxml.jackson.core.json.async;version=\"[2.16,3)\",com.fasterxml.jackson.core.sym;version=\"[2.16,3)\",com.fasterxml.jackson.core.type;version=\"[2.16,3)\",com.fasterxml.jackson.core.util;version=\"[2.16,3)\"" + }, + { + "key": "Require-Capability", + "value": "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\"" + }, + { + "key": "Export-Package", + "value": "com.fasterxml.jackson.core;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core.async,com.fasterxml.jackson.core.exc,com.fasterxml.jackson.core.format,com.fasterxml.jackson.core.io,com.fasterxml.jackson.core.json,com.fasterxml.jackson.core.sym,com.fasterxml.jackson.core.type,com.fasterxml.jackson.core.util\",com.fasterxml.jackson.core.async;version=\"2.16.1\",com.fasterxml.jackson.core.base;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core,com.fasterxml.jackson.core.exc,com.fasterxml.jackson.core.io,com.fasterxml.jackson.core.json,com.fasterxml.jackson.core.util\",com.fasterxml.jackson.core.exc;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core,com.fasterxml.jackson.core.util\",com.fasterxml.jackson.core.filter;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core,com.fasterxml.jackson.core.util\",com.fasterxml.jackson.core.format;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core\",com.fasterxml.jackson.core.io;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core,com.fasterxml.jackson.core.util\",com.fasterxml.jackson.core.io.schubfach;version=\"2.16.1\",com.fasterxml.jackson.core.json;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core,com.fasterxml.jackson.core.base,com.fasterxml.jackson.core.format,com.fasterxml.jackson.core.io,com.fasterxml.jackson.core.sym,com.fasterxml.jackson.core.util\",com.fasterxml.jackson.core.json.async;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core,com.fasterxml.jackson.core.async,com.fasterxml.jackson.core.base,com.fasterxml.jackson.core.exc,com.fasterxml.jackson.core.io,com.fasterxml.jackson.core.sym,com.fasterxml.jackson.core.util\",com.fasterxml.jackson.core.sym;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core,com.fasterxml.jackson.core.exc,com.fasterxml.jackson.core.util\",com.fasterxml.jackson.core.type;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core\",com.fasterxml.jackson.core.util;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core,com.fasterxml.jackson.core.async,com.fasterxml.jackson.core.exc,com.fasterxml.jackson.core.io\"" + }, + { + "key": "Bundle-Name", + "value": "Jackson-core" + }, + { + "key": "Multi-Release", + "value": "true" + }, + { + "key": "Build-Jdk-Spec", + "value": "1.8" + }, + { + "key": "Bundle-Description", + "value": "Core Jackson processing abstractions (aka Streaming API), implementation for JSON" + }, + { + "key": "Implementation-Title", + "value": "Jackson-core" + }, + { + "key": "Implementation-Version", + "value": "2.16.1" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Specification-Vendor", + "value": "FasterXML" + }, + { + "key": "Bundle-Vendor", + "value": "FasterXML" + }, + { + "key": "Tool", + "value": "Bnd-6.3.1.202206071316" + }, + { + "key": "Implementation-Vendor", + "value": "FasterXML" + }, + { + "key": "Bundle-Version", + "value": "2.16.1" + }, + { + "key": "X-Compile-Target-JDK", + "value": "1.8" + }, + { + "key": "X-Compile-Source-JDK", + "value": "1.8" + }, + { + "key": "Created-By", + "value": "Apache Maven Bundle Plugin 5.1.9" + }, + { + "key": "Specification-Version", + "value": "2.16.1" + } + ] + }, + "pomProperties": { + "path": "META-INF/maven/com.fasterxml.jackson.core/jackson-core/pom.properties", + "name": "", + "groupId": "com.fasterxml.jackson.core", + "artifactId": "jackson-core", + "version": "2.16.1" + }, + "digest": [ + { + "algorithm": "sha1", + "value": "9456bb3cdd0f79f91a5f730a1b1bb041a380c91f" + } + ] + } + }, + { + "id": "9ad3756f611d1ed2", + "name": "jackson-databind", + "version": "2.16.1", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jackson-databind-2.16.1.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "https://www.apache.org/licenses/LICENSE-2.0.txt", + "spdxExpression": "", + "type": "declared", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jackson-databind-2.16.1.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:jackson-databind:jackson-databind:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-databind:jackson_databind:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_databind:jackson-databind:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_databind:jackson_databind:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:jackson-databind:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:jackson_databind:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-databind:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:jackson-databind:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:jackson_databind:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_databind:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:core:jackson-databind:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:core:jackson_databind:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:core:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.16.1", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jackson-databind-2.16.1.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Bundle-License", + "value": "https://www.apache.org/licenses/LICENSE-2.0.txt" + }, + { + "key": "Bundle-SymbolicName", + "value": "com.fasterxml.jackson.core.jackson-databind" + }, + { + "key": "Implementation-Vendor-Id", + "value": "com.fasterxml.jackson.core" + }, + { + "key": "Specification-Title", + "value": "jackson-databind" + }, + { + "key": "Bundle-DocURL", + "value": "https://github.com/FasterXML/jackson" + }, + { + "key": "Import-Package", + "value": "com.fasterxml.jackson.annotation;version=\"[2.16,3)\",com.fasterxml.jackson.core;version=\"[2.16,3)\",com.fasterxml.jackson.core.base;version=\"[2.16,3)\",com.fasterxml.jackson.core.exc;version=\"[2.16,3)\",com.fasterxml.jackson.core.filter;version=\"[2.16,3)\",com.fasterxml.jackson.core.format;version=\"[2.16,3)\",com.fasterxml.jackson.core.io;version=\"[2.16,3)\",com.fasterxml.jackson.core.json;version=\"[2.16,3)\",com.fasterxml.jackson.core.type;version=\"[2.16,3)\",com.fasterxml.jackson.core.util;version=\"[2.16,3)\",com.fasterxml.jackson.databind;version=\"[2.16,3)\",com.fasterxml.jackson.databind.annotation;version=\"[2.16,3)\",com.fasterxml.jackson.databind.cfg;version=\"[2.16,3)\",com.fasterxml.jackson.databind.deser;version=\"[2.16,3)\",com.fasterxml.jackson.databind.deser.impl;version=\"[2.16,3)\",com.fasterxml.jackson.databind.deser.std;version=\"[2.16,3)\",com.fasterxml.jackson.databind.exc;version=\"[2.16,3)\",com.fasterxml.jackson.databind.ext;version=\"[2.16,3)\",com.fasterxml.jackson.databind.introspect;version=\"[2.16,3)\",com.fasterxml.jackson.databind.jdk14;version=\"[2.16,3)\",com.fasterxml.jackson.databind.json;version=\"[2.16,3)\",com.fasterxml.jackson.databind.jsonFormatVisitors;version=\"[2.16,3)\",com.fasterxml.jackson.databind.jsonschema;version=\"[2.16,3)\",com.fasterxml.jackson.databind.jsontype;version=\"[2.16,3)\",com.fasterxml.jackson.databind.jsontype.impl;version=\"[2.16,3)\",com.fasterxml.jackson.databind.node;version=\"[2.16,3)\",com.fasterxml.jackson.databind.ser;version=\"[2.16,3)\",com.fasterxml.jackson.databind.ser.impl;version=\"[2.16,3)\",com.fasterxml.jackson.databind.ser.std;version=\"[2.16,3)\",com.fasterxml.jackson.databind.type;version=\"[2.16,3)\",com.fasterxml.jackson.databind.util;version=\"[2.16,3)\",com.fasterxml.jackson.databind.util.internal;version=\"[2.16,3)\",javax.xml.datatype,javax.xml.namespace,javax.xml.parsers,javax.xml.transform,javax.xml.transform.dom,javax.xml.transform.stream,org.w3c.dom,org.xml.sax,org.w3c.dom.bootstrap;resolution:=optional" + }, + { + "key": "Require-Capability", + "value": "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\"" + }, + { + "key": "Export-Package", + "value": "com.fasterxml.jackson.databind;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.annotation,com.fasterxml.jackson.core,com.fasterxml.jackson.core.exc,com.fasterxml.jackson.core.filter,com.fasterxml.jackson.core.format,com.fasterxml.jackson.core.io,com.fasterxml.jackson.core.type,com.fasterxml.jackson.core.util,com.fasterxml.jackson.databind.annotation,com.fasterxml.jackson.databind.cfg,com.fasterxml.jackson.databind.deser,com.fasterxml.jackson.databind.deser.impl,com.fasterxml.jackson.databind.introspect,com.fasterxml.jackson.databind.jsonFormatVisitors,com.fasterxml.jackson.databind.jsonschema,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.jsontype.impl,com.fasterxml.jackson.databind.node,com.fasterxml.jackson.databind.ser,com.fasterxml.jackson.databind.ser.impl,com.fasterxml.jackson.databind.type,com.fasterxml.jackson.databind.util\",com.fasterxml.jackson.databind.annotation;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.annotation,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.deser,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.ser,com.fasterxml.jackson.databind.util\",com.fasterxml.jackson.databind.cfg;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.annotation,com.fasterxml.jackson.core,com.fasterxml.jackson.core.type,com.fasterxml.jackson.core.util,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.deser,com.fasterxml.jackson.databind.introspect,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.node,com.fasterxml.jackson.databind.ser,com.fasterxml.jackson.databind.type,com.fasterxml.jackson.databind.util\",com.fasterxml.jackson.databind.deser;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.annotation,com.fasterxml.jackson.core,com.fasterxml.jackson.core.format,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.annotation,com.fasterxml.jackson.databind.cfg,com.fasterxml.jackson.databind.deser.impl,com.fasterxml.jackson.databind.deser.std,com.fasterxml.jackson.databind.introspect,com.fasterxml.jackson.databind.jsonFormatVisitors,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.node,com.fasterxml.jackson.databind.type,com.fasterxml.jackson.databind.util\",com.fasterxml.jackson.databind.deser.impl;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.annotation,com.fasterxml.jackson.core,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.cfg,com.fasterxml.jackson.databind.deser,com.fasterxml.jackson.databind.deser.std,com.fasterxml.jackson.databind.introspect,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.type,com.fasterxml.jackson.databind.util\",com.fasterxml.jackson.databind.deser.std;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.annotation,com.fasterxml.jackson.core,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.annotation,com.fasterxml.jackson.databind.cfg,com.fasterxml.jackson.databind.deser,com.fasterxml.jackson.databind.deser.impl,com.fasterxml.jackson.databind.introspect,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.type,com.fasterxml.jackson.databind.util\",com.fasterxml.jackson.databind.exc;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.introspect\",com.fasterxml.jackson.databind.ext;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.annotation,com.fasterxml.jackson.databind.deser,com.fasterxml.jackson.databind.deser.std,com.fasterxml.jackson.databind.introspect,com.fasterxml.jackson.databind.jsonFormatVisitors,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.ser,com.fasterxml.jackson.databind.ser.std,javax.xml.datatype,javax.xml.parsers,javax.xml.transform,org.w3c.dom\",com.fasterxml.jackson.databind.introspect;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.annotation,com.fasterxml.jackson.core,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.annotation,com.fasterxml.jackson.databind.cfg,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.jsontype.impl,com.fasterxml.jackson.databind.ser,com.fasterxml.jackson.databind.type,com.fasterxml.jackson.databind.util\",com.fasterxml.jackson.databind.jdk14;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.cfg,com.fasterxml.jackson.databind.introspect\",com.fasterxml.jackson.databind.json;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core,com.fasterxml.jackson.core.json,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.cfg\",com.fasterxml.jackson.databind.jsonFormatVisitors;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.annotation,com.fasterxml.jackson.core,com.fasterxml.jackson.databind\",com.fasterxml.jackson.databind.jsonschema;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.annotation,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.node\",com.fasterxml.jackson.databind.jsontype;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.annotation,com.fasterxml.jackson.core,com.fasterxml.jackson.core.type,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.cfg,com.fasterxml.jackson.databind.introspect\",com.fasterxml.jackson.databind.jsontype.impl;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.annotation,com.fasterxml.jackson.core,com.fasterxml.jackson.core.type,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.cfg,com.fasterxml.jackson.databind.introspect,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.type,com.fasterxml.jackson.databind.util\",com.fasterxml.jackson.databind.module;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.deser,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.ser,com.fasterxml.jackson.databind.type\",com.fasterxml.jackson.databind.node;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core,com.fasterxml.jackson.core.base,com.fasterxml.jackson.core.util,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.util\",com.fasterxml.jackson.databind.ser;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.annotation,com.fasterxml.jackson.core,com.fasterxml.jackson.core.io,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.annotation,com.fasterxml.jackson.databind.cfg,com.fasterxml.jackson.databind.introspect,com.fasterxml.jackson.databind.jsonFormatVisitors,com.fasterxml.jackson.databind.jsonschema,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.node,com.fasterxml.jackson.databind.ser.impl,com.fasterxml.jackson.databind.ser.std,com.fasterxml.jackson.databind.type,com.fasterxml.jackson.databind.util\",com.fasterxml.jackson.databind.ser.impl;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.annotation,com.fasterxml.jackson.core,com.fasterxml.jackson.core.io,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.annotation,com.fasterxml.jackson.databind.cfg,com.fasterxml.jackson.databind.introspect,com.fasterxml.jackson.databind.jsonFormatVisitors,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.node,com.fasterxml.jackson.databind.ser,com.fasterxml.jackson.databind.ser.std,com.fasterxml.jackson.databind.util\",com.fasterxml.jackson.databind.ser.std;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.annotation,com.fasterxml.jackson.core,com.fasterxml.jackson.core.type,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.annotation,com.fasterxml.jackson.databind.introspect,com.fasterxml.jackson.databind.jsonFormatVisitors,com.fasterxml.jackson.databind.jsonschema,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.node,com.fasterxml.jackson.databind.ser,com.fasterxml.jackson.databind.ser.impl,com.fasterxml.jackson.databind.type,com.fasterxml.jackson.databind.util\",com.fasterxml.jackson.databind.type;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core,com.fasterxml.jackson.core.type,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.util\",com.fasterxml.jackson.databind.util;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.annotation,com.fasterxml.jackson.core,com.fasterxml.jackson.core.base,com.fasterxml.jackson.core.io,com.fasterxml.jackson.core.json,com.fasterxml.jackson.core.util,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.cfg,com.fasterxml.jackson.databind.introspect,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.type,com.fasterxml.jackson.databind.util.internal\",com.fasterxml.jackson.databind.util.internal;version=\"2.16.1\"" + }, + { + "key": "Bundle-Name", + "value": "jackson-databind" + }, + { + "key": "Multi-Release", + "value": "true" + }, + { + "key": "Build-Jdk-Spec", + "value": "1.8" + }, + { + "key": "Bundle-Description", + "value": "General data-binding functionality for Jackson: works on core streaming API" + }, + { + "key": "Implementation-Title", + "value": "jackson-databind" + }, + { + "key": "Implementation-Version", + "value": "2.16.1" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Specification-Vendor", + "value": "FasterXML" + }, + { + "key": "Bundle-Vendor", + "value": "FasterXML" + }, + { + "key": "Tool", + "value": "Bnd-6.3.1.202206071316" + }, + { + "key": "Implementation-Vendor", + "value": "FasterXML" + }, + { + "key": "Bundle-Version", + "value": "2.16.1" + }, + { + "key": "X-Compile-Target-JDK", + "value": "1.8" + }, + { + "key": "X-Compile-Source-JDK", + "value": "1.8" + }, + { + "key": "Created-By", + "value": "Apache Maven Bundle Plugin 5.1.9" + }, + { + "key": "Specification-Version", + "value": "2.16.1" + } + ] + }, + "pomProperties": { + "path": "META-INF/maven/com.fasterxml.jackson.core/jackson-databind/pom.properties", + "name": "", + "groupId": "com.fasterxml.jackson.core", + "artifactId": "jackson-databind", + "version": "2.16.1" + }, + "digest": [ + { + "algorithm": "sha1", + "value": "02a16efeb840c45af1e2f31753dfe76795278b73" + } + ] + } + }, + { + "id": "846731ed2e85561c", + "name": "jackson-datatype-jdk8", + "version": "2.16.1", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jackson-datatype-jdk8-2.16.1.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "http://www.apache.org/licenses/LICENSE-2.0.txt", + "spdxExpression": "", + "type": "declared", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jackson-datatype-jdk8-2.16.1.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:jackson-datatype-jdk8:jackson-datatype-jdk8:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-datatype-jdk8:jackson_datatype_jdk8:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_datatype_jdk8:jackson-datatype-jdk8:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_datatype_jdk8:jackson_datatype_jdk8:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-datatype:jackson-datatype-jdk8:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-datatype:jackson_datatype_jdk8:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_datatype:jackson-datatype-jdk8:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_datatype:jackson_datatype_jdk8:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:jackson-datatype-jdk8:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:jackson_datatype_jdk8:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:datatype:jackson-datatype-jdk8:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:datatype:jackson_datatype_jdk8:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-datatype-jdk8:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:jackson-datatype-jdk8:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:jackson_datatype_jdk8:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_datatype_jdk8:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-datatype:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_datatype:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:datatype:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jdk8@2.16.1", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jackson-datatype-jdk8-2.16.1.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Bundle-License", + "value": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, + { + "key": "Bundle-SymbolicName", + "value": "com.fasterxml.jackson.datatype.jackson-datatype-jdk8" + }, + { + "key": "Implementation-Vendor-Id", + "value": "com.fasterxml.jackson.datatype" + }, + { + "key": "Specification-Title", + "value": "Jackson datatype: jdk8" + }, + { + "key": "Bundle-DocURL", + "value": "https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8" + }, + { + "key": "Import-Package", + "value": "com.fasterxml.jackson.core;version=\"[2.16,3)\",com.fasterxml.jackson.core.io;version=\"[2.16,3)\",com.fasterxml.jackson.core.util;version=\"[2.16,3)\",com.fasterxml.jackson.databind;version=\"[2.16,3)\",com.fasterxml.jackson.databind.cfg;version=\"[2.16,3)\",com.fasterxml.jackson.databind.deser;version=\"[2.16,3)\",com.fasterxml.jackson.databind.deser.std;version=\"[2.16,3)\",com.fasterxml.jackson.databind.jsonFormatVisitors;version=\"[2.16,3)\",com.fasterxml.jackson.databind.jsontype;version=\"[2.16,3)\",com.fasterxml.jackson.databind.ser;version=\"[2.16,3)\",com.fasterxml.jackson.databind.ser.impl;version=\"[2.16,3)\",com.fasterxml.jackson.databind.ser.std;version=\"[2.16,3)\",com.fasterxml.jackson.databind.type;version=\"[2.16,3)\",com.fasterxml.jackson.databind.util;version=\"[2.16,3)\"" + }, + { + "key": "Require-Capability", + "value": "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\"" + }, + { + "key": "Export-Package", + "value": "com.fasterxml.jackson.datatype.jdk8;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core,com.fasterxml.jackson.core.io,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.deser,com.fasterxml.jackson.databind.deser.std,com.fasterxml.jackson.databind.jsonFormatVisitors,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.ser,com.fasterxml.jackson.databind.ser.impl,com.fasterxml.jackson.databind.ser.std,com.fasterxml.jackson.databind.type,com.fasterxml.jackson.databind.util\"" + }, + { + "key": "Bundle-Name", + "value": "Jackson datatype: jdk8" + }, + { + "key": "Multi-Release", + "value": "true" + }, + { + "key": "Build-Jdk-Spec", + "value": "1.8" + }, + { + "key": "Bundle-Description", + "value": "Add-on module for Jackson (http://jackson.codehaus.org) to supportJDK 8 data types." + }, + { + "key": "Implementation-Title", + "value": "Jackson datatype: jdk8" + }, + { + "key": "Implementation-Version", + "value": "2.16.1" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Specification-Vendor", + "value": "FasterXML" + }, + { + "key": "Bundle-Vendor", + "value": "FasterXML" + }, + { + "key": "Tool", + "value": "Bnd-6.3.1.202206071316" + }, + { + "key": "Implementation-Vendor", + "value": "FasterXML" + }, + { + "key": "Bundle-Version", + "value": "2.16.1" + }, + { + "key": "X-Compile-Target-JDK", + "value": "1.8" + }, + { + "key": "X-Compile-Source-JDK", + "value": "1.8" + }, + { + "key": "Created-By", + "value": "Apache Maven Bundle Plugin 5.1.9" + }, + { + "key": "Specification-Version", + "value": "2.16.1" + } + ] + }, + "pomProperties": { + "path": "META-INF/maven/com.fasterxml.jackson.datatype/jackson-datatype-jdk8/pom.properties", + "name": "", + "groupId": "com.fasterxml.jackson.datatype", + "artifactId": "jackson-datatype-jdk8", + "version": "2.16.1" + }, + "digest": [ + { + "algorithm": "sha1", + "value": "695d9b8639cfc7a42a0507708cef2366fe492a44" + } + ] + } + }, + { + "id": "1347581c05f302c0", + "name": "jackson-datatype-jsr310", + "version": "2.16.1", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jackson-datatype-jsr310-2.16.1.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "http://www.apache.org/licenses/LICENSE-2.0.txt", + "spdxExpression": "", + "type": "declared", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jackson-datatype-jsr310-2.16.1.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:jackson-datatype-jsr310:jackson-datatype-jsr310:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-datatype-jsr310:jackson_datatype_jsr310:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_datatype_jsr310:jackson-datatype-jsr310:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_datatype_jsr310:jackson_datatype_jsr310:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-datatype:jackson-datatype-jsr310:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-datatype:jackson_datatype_jsr310:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_datatype:jackson-datatype-jsr310:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_datatype:jackson_datatype_jsr310:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:jackson-datatype-jsr310:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:jackson_datatype_jsr310:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:datatype:jackson-datatype-jsr310:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:datatype:jackson_datatype_jsr310:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-datatype-jsr310:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:jackson-datatype-jsr310:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:jackson_datatype_jsr310:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_datatype_jsr310:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-datatype:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_datatype:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:datatype:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310@2.16.1", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jackson-datatype-jsr310-2.16.1.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Bundle-License", + "value": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, + { + "key": "Bundle-SymbolicName", + "value": "com.fasterxml.jackson.datatype.jackson-datatype-jsr310" + }, + { + "key": "Implementation-Vendor-Id", + "value": "com.fasterxml.jackson.datatype" + }, + { + "key": "Specification-Title", + "value": "Jackson datatype: JSR310" + }, + { + "key": "Bundle-DocURL", + "value": "https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310" + }, + { + "key": "Import-Package", + "value": "com.fasterxml.jackson.annotation;version=\"[2.16,3)\",com.fasterxml.jackson.core;version=\"[2.16,3)\",com.fasterxml.jackson.core.io;version=\"[2.16,3)\",com.fasterxml.jackson.core.type;version=\"[2.16,3)\",com.fasterxml.jackson.core.util;version=\"[2.16,3)\",com.fasterxml.jackson.databind;version=\"[2.16,3)\",com.fasterxml.jackson.databind.cfg;version=\"[2.16,3)\",com.fasterxml.jackson.databind.deser;version=\"[2.16,3)\",com.fasterxml.jackson.databind.deser.std;version=\"[2.16,3)\",com.fasterxml.jackson.databind.introspect;version=\"[2.16,3)\",com.fasterxml.jackson.databind.jsonFormatVisitors;version=\"[2.16,3)\",com.fasterxml.jackson.databind.jsontype;version=\"[2.16,3)\",com.fasterxml.jackson.databind.module;version=\"[2.16,3)\",com.fasterxml.jackson.databind.node;version=\"[2.16,3)\",com.fasterxml.jackson.databind.ser;version=\"[2.16,3)\",com.fasterxml.jackson.databind.ser.std;version=\"[2.16,3)\",com.fasterxml.jackson.databind.type;version=\"[2.16,3)\",com.fasterxml.jackson.databind.util;version=\"[2.16,3)\",com.fasterxml.jackson.datatype.jsr310;version=\"[2.16,3)\",com.fasterxml.jackson.datatype.jsr310.deser;version=\"[2.16,3)\",com.fasterxml.jackson.datatype.jsr310.deser.key;version=\"[2.16,3)\",com.fasterxml.jackson.datatype.jsr310.ser;version=\"[2.16,3)\",com.fasterxml.jackson.datatype.jsr310.ser.key;version=\"[2.16,3)\",com.fasterxml.jackson.datatype.jsr310.util;version=\"[2.16,3)\"" + }, + { + "key": "Require-Capability", + "value": "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\"" + }, + { + "key": "Export-Package", + "value": "com.fasterxml.jackson.datatype.jsr310;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core,com.fasterxml.jackson.core.util,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.introspect,com.fasterxml.jackson.databind.module\",com.fasterxml.jackson.datatype.jsr310.deser;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.annotation,com.fasterxml.jackson.core,com.fasterxml.jackson.core.util,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.deser,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.type,com.fasterxml.jackson.datatype.jsr310,com.fasterxml.jackson.datatype.jsr310.util\",com.fasterxml.jackson.datatype.jsr310.deser.key;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.databind\",com.fasterxml.jackson.datatype.jsr310.ser;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.annotation,com.fasterxml.jackson.core,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.jsonFormatVisitors,com.fasterxml.jackson.databind.jsontype,com.fasterxml.jackson.databind.ser.std,com.fasterxml.jackson.datatype.jsr310.util\",com.fasterxml.jackson.datatype.jsr310.ser.key;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.core,com.fasterxml.jackson.databind\",com.fasterxml.jackson.datatype.jsr310.util;version=\"2.16.1\"" + }, + { + "key": "Bundle-Name", + "value": "Jackson datatype: JSR310" + }, + { + "key": "Multi-Release", + "value": "true" + }, + { + "key": "Build-Jdk-Spec", + "value": "1.8" + }, + { + "key": "Bundle-Description", + "value": "Add-on module to support JSR-310 (Java 8 Date & Time API) data types." + }, + { + "key": "Implementation-Title", + "value": "Jackson datatype: JSR310" + }, + { + "key": "Implementation-Version", + "value": "2.16.1" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Specification-Vendor", + "value": "FasterXML" + }, + { + "key": "Bundle-Vendor", + "value": "FasterXML" + }, + { + "key": "Tool", + "value": "Bnd-6.3.1.202206071316" + }, + { + "key": "Implementation-Vendor", + "value": "FasterXML" + }, + { + "key": "Bundle-Version", + "value": "2.16.1" + }, + { + "key": "X-Compile-Target-JDK", + "value": "1.8" + }, + { + "key": "X-Compile-Source-JDK", + "value": "1.8" + }, + { + "key": "Created-By", + "value": "Apache Maven Bundle Plugin 5.1.9" + }, + { + "key": "Specification-Version", + "value": "2.16.1" + } + ] + }, + "pomProperties": { + "path": "META-INF/maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/pom.properties", + "name": "", + "groupId": "com.fasterxml.jackson.datatype", + "artifactId": "jackson-datatype-jsr310", + "version": "2.16.1" + }, + "digest": [ + { + "algorithm": "sha1", + "value": "36a418325c618e440e5ccb80b75c705d894f50bd" + } + ] + } + }, + { + "id": "f5bca9d628ab321f", + "name": "jackson-module-parameter-names", + "version": "2.16.1", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jackson-module-parameter-names-2.16.1.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "http://www.apache.org/licenses/LICENSE-2.0.txt", + "spdxExpression": "", + "type": "declared", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jackson-module-parameter-names-2.16.1.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:jackson-module-parameter-names:jackson-module-parameter-names:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-module-parameter-names:jackson_module_parameter_names:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_module_parameter_names:jackson-module-parameter-names:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_module_parameter_names:jackson_module_parameter_names:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-module-parameter:jackson-module-parameter-names:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-module-parameter:jackson_module_parameter_names:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_module_parameter:jackson-module-parameter-names:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_module_parameter:jackson_module_parameter_names:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-module:jackson-module-parameter-names:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-module:jackson_module_parameter_names:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_module:jackson-module-parameter-names:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_module:jackson_module_parameter_names:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:jackson-module-parameter-names:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:jackson_module_parameter_names:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-module-parameter-names:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:jackson-module-parameter-names:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:jackson_module_parameter_names:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_module_parameter_names:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:module:jackson-module-parameter-names:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:module:jackson_module_parameter_names:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-module-parameter:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_module_parameter:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson-module:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson_module:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:fasterxml:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jackson:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:module:jackson:2.16.1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/com.fasterxml.jackson.module/jackson-module-parameter-names@2.16.1", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jackson-module-parameter-names-2.16.1.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Bundle-License", + "value": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, + { + "key": "Bundle-SymbolicName", + "value": "com.fasterxml.jackson.module.jackson-module-parameter-names" + }, + { + "key": "Implementation-Vendor-Id", + "value": "com.fasterxml.jackson.module" + }, + { + "key": "Specification-Title", + "value": "Jackson-module-parameter-names" + }, + { + "key": "Bundle-DocURL", + "value": "https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names" + }, + { + "key": "Import-Package", + "value": "com.fasterxml.jackson.annotation;version=\"[2.16,3)\",com.fasterxml.jackson.core;version=\"[2.16,3)\",com.fasterxml.jackson.core.util;version=\"[2.16,3)\",com.fasterxml.jackson.databind;version=\"[2.16,3)\",com.fasterxml.jackson.databind.cfg;version=\"[2.16,3)\",com.fasterxml.jackson.databind.introspect;version=\"[2.16,3)\",com.fasterxml.jackson.databind.module;version=\"[2.16,3)\"" + }, + { + "key": "Require-Capability", + "value": "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\"" + }, + { + "key": "Export-Package", + "value": "com.fasterxml.jackson.module.paramnames;version=\"2.16.1\";uses:=\"com.fasterxml.jackson.annotation,com.fasterxml.jackson.core,com.fasterxml.jackson.databind,com.fasterxml.jackson.databind.cfg,com.fasterxml.jackson.databind.introspect,com.fasterxml.jackson.databind.module\"" + }, + { + "key": "Bundle-Name", + "value": "Jackson-module-parameter-names" + }, + { + "key": "Multi-Release", + "value": "true" + }, + { + "key": "Build-Jdk-Spec", + "value": "1.8" + }, + { + "key": "Bundle-Description", + "value": "Add-on module for Jackson (http://jackson.codehaus.org) to supportintrospection of method/constructor parameter names,without having to add explicit property name annotation." + }, + { + "key": "Implementation-Title", + "value": "Jackson-module-parameter-names" + }, + { + "key": "Implementation-Version", + "value": "2.16.1" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Specification-Vendor", + "value": "FasterXML" + }, + { + "key": "Bundle-Vendor", + "value": "FasterXML" + }, + { + "key": "Tool", + "value": "Bnd-6.3.1.202206071316" + }, + { + "key": "Implementation-Vendor", + "value": "FasterXML" + }, + { + "key": "Bundle-Version", + "value": "2.16.1" + }, + { + "key": "X-Compile-Target-JDK", + "value": "1.8" + }, + { + "key": "X-Compile-Source-JDK", + "value": "1.8" + }, + { + "key": "Created-By", + "value": "Apache Maven Bundle Plugin 5.1.9" + }, + { + "key": "Specification-Version", + "value": "2.16.1" + } + ] + }, + "pomProperties": { + "path": "META-INF/maven/com.fasterxml.jackson.module/jackson-module-parameter-names/pom.properties", + "name": "", + "groupId": "com.fasterxml.jackson.module", + "artifactId": "jackson-module-parameter-names", + "version": "2.16.1" + }, + "digest": [ + { + "algorithm": "sha1", + "value": "9e167afd1596e6a6aa6fe4e1af17f4ce8be0676f" + } + ] + } + }, + { + "id": "77a5bf527533d628", + "name": "jakarta.annotation-api", + "version": "2.1.1", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jakarta.annotation-api-2.1.1.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "http://www.eclipse.org/legal/epl-2.0, https://www.gnu.org/software/classpath/license.html", + "spdxExpression": "", + "type": "declared", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jakarta.annotation-api-2.1.1.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:jakarta.annotation-api:jakarta.annotation-api:2.1.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jakarta.annotation-api:jakarta.annotation_api:2.1.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jakarta.annotation_api:jakarta.annotation-api:2.1.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jakarta.annotation_api:jakarta.annotation_api:2.1.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:eclipse-foundation:jakarta.annotation-api:2.1.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:eclipse-foundation:jakarta.annotation_api:2.1.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:eclipse_foundation:jakarta.annotation-api:2.1.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:eclipse_foundation:jakarta.annotation_api:2.1.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jakarta.annotation:jakarta.annotation-api:2.1.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jakarta.annotation:jakarta.annotation_api:2.1.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:glassfish:jakarta.annotation-api:2.1.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:glassfish:jakarta.annotation_api:2.1.1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/jakarta.annotation/jakarta.annotation-api@2.1.1", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jakarta.annotation-api-2.1.1.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Created-By", + "value": "Apache Maven Bundle Plugin" + }, + { + "key": "Build-Jdk-Spec", + "value": "11" + }, + { + "key": "Bundle-Description", + "value": "Jakarta Annotations API" + }, + { + "key": "Bundle-DocURL", + "value": "https://www.eclipse.org" + }, + { + "key": "Bundle-License", + "value": "http://www.eclipse.org/legal/epl-2.0, https://www.gnu.org/software/classpath/license.html" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Bundle-Name", + "value": "Jakarta Annotations API" + }, + { + "key": "Bundle-SymbolicName", + "value": "jakarta.annotation-api" + }, + { + "key": "Bundle-Vendor", + "value": "Eclipse Foundation" + }, + { + "key": "Bundle-Version", + "value": "2.1.1" + }, + { + "key": "Export-Package", + "value": "jakarta.annotation;version=\"2.1.1\",jakarta.annotation.security;version=\"2.1.1\",jakarta.annotation.sql;version=\"2.1.1\"" + }, + { + "key": "Extension-Name", + "value": "jakarta.annotation" + }, + { + "key": "Implementation-Vendor", + "value": "Eclipse Foundation" + }, + { + "key": "Implementation-Vendor-Id", + "value": "org.glassfish" + }, + { + "key": "Implementation-Version", + "value": "2.1.1" + }, + { + "key": "Require-Capability", + "value": "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\"" + }, + { + "key": "Specification-Vendor", + "value": "Eclipse Foundation" + }, + { + "key": "Specification-Version", + "value": "2.1" + } + ] + }, + "pomProperties": { + "path": "META-INF/maven/jakarta.annotation/jakarta.annotation-api/pom.properties", + "name": "", + "groupId": "jakarta.annotation", + "artifactId": "jakarta.annotation-api", + "version": "2.1.1" + }, + "digest": [ + { + "algorithm": "sha1", + "value": "48b9bda22b091b1f48b13af03fe36db3be6e1ae3" + } + ] + } + }, + { + "id": "598311f4a5b2a501", + "name": "jul-to-slf4j", + "version": "2.0.11", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jul-to-slf4j-2.0.11.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "http://www.opensource.org/licenses/mit-license.php", + "spdxExpression": "", + "type": "declared", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jul-to-slf4j-2.0.11.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:jul-to-slf4j:jul-to-slf4j:2.0.11:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jul-to-slf4j:jul_to_slf4j:2.0.11:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jul_to_slf4j:jul-to-slf4j:2.0.11:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jul_to_slf4j:jul_to_slf4j:2.0.11:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jul-to:jul-to-slf4j:2.0.11:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jul-to:jul_to_slf4j:2.0.11:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jul_to:jul-to-slf4j:2.0.11:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jul_to:jul_to_slf4j:2.0.11:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:slf4j:jul-to-slf4j:2.0.11:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:slf4j:jul_to_slf4j:2.0.11:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jul:jul-to-slf4j:2.0.11:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jul:jul_to_slf4j:2.0.11:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.slf4j/jul-to-slf4j@2.0.11", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/jul-to-slf4j-2.0.11.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Created-By", + "value": "Apache Maven Bundle Plugin 5.1.9" + }, + { + "key": "Build-Jdk-Spec", + "value": "21" + }, + { + "key": "Bundle-Description", + "value": "JUL to SLF4J bridge" + }, + { + "key": "Bundle-DocURL", + "value": "http://www.slf4j.org" + }, + { + "key": "Bundle-License", + "value": "http://www.opensource.org/licenses/mit-license.php" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Bundle-Name", + "value": "JUL to SLF4J bridge" + }, + { + "key": "Bundle-SymbolicName", + "value": "jul.to.slf4j" + }, + { + "key": "Bundle-Vendor", + "value": "SLF4J.ORG" + }, + { + "key": "Bundle-Version", + "value": "2.0.11" + }, + { + "key": "Export-Package", + "value": "org.slf4j.bridge;uses:=\"org.slf4j,org.slf4j.spi\";version=\"2.0.11\"" + }, + { + "key": "Implementation-Title", + "value": "jul-to-slf4j" + }, + { + "key": "Implementation-Version", + "value": "2.0.11" + }, + { + "key": "Import-Package", + "value": "org.slf4j;version=\"[2.0,3)\",org.slf4j.spi;version=\"[2.0,3)\"" + }, + { + "key": "Multi-Release", + "value": "true" + }, + { + "key": "Require-Capability", + "value": "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\"" + }, + { + "key": "Tool", + "value": "Bnd-6.3.1.202206071316" + }, + { + "key": "X-Compile-Source-JDK", + "value": "8" + }, + { + "key": "X-Compile-Target-JDK", + "value": "8" + } + ] + }, + "pomProperties": { + "path": "META-INF/maven/org.slf4j/jul-to-slf4j/pom.properties", + "name": "", + "groupId": "org.slf4j", + "artifactId": "jul-to-slf4j", + "version": "2.0.11" + }, + "digest": [ + { + "algorithm": "sha1", + "value": "279356f8e873b1a26badd8bbb3284b5c3b22c770" + } + ] + } + }, + { + "id": "c404b33d3a8ce0d8", + "name": "log4j-api", + "version": "2.22.1", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/log4j-api-2.22.1.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "\"Apache-2.0\";link=\"https://www.apache.org/licenses/LICENSE-2.0.txt\"", + "spdxExpression": "", + "type": "declared", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/log4j-api-2.22.1.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:apache:log4j-api:2.22.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:apache:log4j_api:2.22.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:apache:log4j:2.22.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:apache:api:2.22.1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.apache.logging.log4j/log4j-api@2.22.1", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/log4j-api-2.22.1.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Created-By", + "value": "Maven JAR Plugin 3.3.0" + }, + { + "key": "Build-Jdk-Spec", + "value": "17" + }, + { + "key": "Specification-Title", + "value": "Apache Log4j API" + }, + { + "key": "Specification-Version", + "value": "2.22" + }, + { + "key": "Specification-Vendor", + "value": "The Apache Software Foundation" + }, + { + "key": "Implementation-Title", + "value": "Apache Log4j API" + }, + { + "key": "Implementation-Version", + "value": "2.22.1" + }, + { + "key": "Implementation-Vendor", + "value": "The Apache Software Foundation" + }, + { + "key": "Bundle-ActivationPolicy", + "value": "lazy" + }, + { + "key": "Bundle-Activator", + "value": "org.apache.logging.log4j.util.Activator" + }, + { + "key": "Bundle-Description", + "value": "The Apache Log4j API" + }, + { + "key": "Bundle-License", + "value": "\"Apache-2.0\";link=\"https://www.apache.org/licenses/LICENSE-2.0.txt\"" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Bundle-Name", + "value": "Apache Log4j API" + }, + { + "key": "Bundle-SymbolicName", + "value": "org.apache.logging.log4j.api" + }, + { + "key": "Bundle-Vendor", + "value": "The Apache Software Foundation" + }, + { + "key": "Bundle-Version", + "value": "2.22.1" + }, + { + "key": "Export-Package", + "value": "org.apache.logging.log4j;version=\"2.20.2\";uses:=\"org.apache.logging.log4j.message,org.apache.logging.log4j.spi,org.apache.logging.log4j.util\",org.apache.logging.log4j.message;version=\"2.22.0\";uses:=\"org.apache.logging.log4j.util\",org.apache.logging.log4j.simple;version=\"2.20.2\";uses:=\"org.apache.logging.log4j,org.apache.logging.log4j.message,org.apache.logging.log4j.spi,org.apache.logging.log4j.util\",org.apache.logging.log4j.spi;version=\"2.20.1\";uses:=\"org.apache.logging.log4j,org.apache.logging.log4j.message,org.apache.logging.log4j.util\",org.apache.logging.log4j.status;version=\"2.20.2\";uses:=\"org.apache.logging.log4j,org.apache.logging.log4j.message,org.apache.logging.log4j.spi\",org.apache.logging.log4j.util;version=\"2.22.0\";uses:=\"org.apache.logging.log4j.message,org.apache.logging.log4j.spi,org.osgi.framework\"" + }, + { + "key": "Import-Package", + "value": "org.apache.logging.log4j.simple;version=\"[2.20,3)\",org.apache.logging.log4j.status;version=\"[2.20,3)\",org.osgi.framework;version=\"[1.8,2)\",org.osgi.framework.wiring;version=\"[1.2,2)\"" + }, + { + "key": "Multi-Release", + "value": "true" + }, + { + "key": "Private-Package", + "value": "org.apache.logging.log4j.internal,org.apache.logging.log4j.util.internal" + }, + { + "key": "Provide-Capability", + "value": "osgi.service;objectClass:List=\"org.apache.logging.log4j.util.PropertySource\";effective:=active,osgi.serviceloader;osgi.serviceloader=\"org.apache.logging.log4j.util.PropertySource\";register:=\"org.apache.logging.log4j.util.EnvironmentPropertySource\",osgi.serviceloader;osgi.serviceloader=\"org.apache.logging.log4j.util.PropertySource\";register:=\"org.apache.logging.log4j.util.SystemPropertiesPropertySource\"" + }, + { + "key": "Require-Capability", + "value": "osgi.extender;filter:=\"(&(osgi.extender=osgi.serviceloader.processor)(version>=1.0.0)(!(version>=2.0.0)))\";resolution:=optional,osgi.extender;filter:=\"(&(osgi.extender=osgi.serviceloader.registrar)(version>=1.0.0)(!(version>=2.0.0)))\";resolution:=optional,osgi.serviceloader;filter:=\"(osgi.serviceloader=org.apache.logging.log4j.message.ThreadDumpMessage$ThreadInfoFactory)\";osgi.serviceloader=\"org.apache.logging.log4j.message.ThreadDumpMessage$ThreadInfoFactory\";cardinality:=single;resolution:=optional,osgi.serviceloader;filter:=\"(osgi.serviceloader=org.apache.logging.log4j.spi.Provider)\";osgi.serviceloader=\"org.apache.logging.log4j.spi.Provider\";cardinality:=multiple;resolution:=optional,osgi.serviceloader;filter:=\"(osgi.serviceloader=org.apache.logging.log4j.util.PropertySource)\";osgi.serviceloader=\"org.apache.logging.log4j.util.PropertySource\";cardinality:=multiple;resolution:=optional,osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\"" + } + ] + }, + "pomProperties": { + "path": "META-INF/maven/org.apache.logging.log4j/log4j-api/pom.properties", + "name": "", + "groupId": "org.apache.logging.log4j", + "artifactId": "log4j-api", + "version": "2.22.1" + }, + "digest": [ + { + "algorithm": "sha1", + "value": "bea6fede6328fabafd7e68363161a7ea6605abd1" + } + ] + } + }, + { + "id": "860f45be6a175d16", + "name": "log4j-to-slf4j", + "version": "2.22.1", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/log4j-to-slf4j-2.22.1.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "\"Apache-2.0\";link=\"https://www.apache.org/licenses/LICENSE-2.0.txt\"", + "spdxExpression": "", + "type": "declared", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/log4j-to-slf4j-2.22.1.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:apache:log4j-to-slf4j:2.22.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:apache:log4j_to_slf4j:2.22.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:apache:log4j:2.22.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:apache:slf4j:2.22.1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.apache.logging.log4j/log4j-to-slf4j@2.22.1", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/log4j-to-slf4j-2.22.1.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Created-By", + "value": "Maven JAR Plugin 3.3.0" + }, + { + "key": "Build-Jdk-Spec", + "value": "17" + }, + { + "key": "Specification-Title", + "value": "Apache Log4j to SLF4J Adapter" + }, + { + "key": "Specification-Version", + "value": "2.22" + }, + { + "key": "Specification-Vendor", + "value": "The Apache Software Foundation" + }, + { + "key": "Implementation-Title", + "value": "Apache Log4j to SLF4J Adapter" + }, + { + "key": "Implementation-Version", + "value": "2.22.1" + }, + { + "key": "Implementation-Vendor", + "value": "The Apache Software Foundation" + }, + { + "key": "Bundle-ActivationPolicy", + "value": "lazy" + }, + { + "key": "Bundle-Activator", + "value": "org.apache.logging.slf4j.Activator" + }, + { + "key": "Bundle-Description", + "value": "The Apache Log4j binding between Log4j 2 API and SLF4J." + }, + { + "key": "Bundle-License", + "value": "\"Apache-2.0\";link=\"https://www.apache.org/licenses/LICENSE-2.0.txt\"" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Bundle-Name", + "value": "Apache Log4j to SLF4J Adapter" + }, + { + "key": "Bundle-SymbolicName", + "value": "org.apache.logging.log4j.to.slf4j" + }, + { + "key": "Bundle-Vendor", + "value": "The Apache Software Foundation" + }, + { + "key": "Bundle-Version", + "value": "2.22.1" + }, + { + "key": "Export-Package", + "value": "org.apache.logging.slf4j;version=\"2.20.1\";uses:=\"org.apache.logging.log4j,org.apache.logging.log4j.message,org.apache.logging.log4j.spi,org.apache.logging.log4j.util,org.slf4j\"" + }, + { + "key": "Import-Package", + "value": "org.slf4j;version=\"[1.7,3)\",org.slf4j.spi;version=\"[1.7,3)\",org.apache.logging.log4j;version=\"[2.20,3)\",org.apache.logging.log4j.message;version=\"[2.22,3)\",org.apache.logging.log4j.spi;version=\"[2.20,3)\",org.apache.logging.log4j.status;version=\"[2.20,3)\",org.apache.logging.log4j.util;version=\"[2.22,3)\"" + }, + { + "key": "Multi-Release", + "value": "false" + }, + { + "key": "Provide-Capability", + "value": "osgi.service;objectClass:List=\"org.apache.logging.log4j.spi.Provider\";effective:=active,osgi.serviceloader;osgi.serviceloader=\"org.apache.logging.log4j.spi.Provider\";register:=\"org.apache.logging.slf4j.SLF4JProvider\"" + }, + { + "key": "Require-Capability", + "value": "osgi.extender;filter:=\"(&(osgi.extender=osgi.serviceloader.registrar)(version>=1.0.0)(!(version>=2.0.0)))\";resolution:=optional,osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\"" + } + ] + }, + "pomProperties": { + "path": "META-INF/maven/org.apache.logging.log4j/log4j-to-slf4j/pom.properties", + "name": "", + "groupId": "org.apache.logging.log4j", + "artifactId": "log4j-to-slf4j", + "version": "2.22.1" + }, + "digest": [ + { + "algorithm": "sha1", + "value": "b5e67b6acac768bfec1d1d6991504f45453abcad" + } + ] + } + }, + { + "id": "d91fe3ae6bb15cad", + "name": "logback-classic", + "version": "1.4.14", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/logback-classic-1.4.14.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "http://www.eclipse.org/legal/epl-v10.html, http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html", + "spdxExpression": "", + "type": "declared", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/logback-classic-1.4.14.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:logback-classic:logback-classic:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:logback-classic:logback_classic:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:logback_classic:logback-classic:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:logback_classic:logback_classic:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:logback:logback-classic:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:logback:logback_classic:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:qos-ch:logback-classic:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:qos-ch:logback_classic:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:qos_ch:logback-classic:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:qos_ch:logback_classic:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/ch.qos.logback/logback-classic@1.4.14", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/logback-classic-1.4.14.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Created-By", + "value": "Apache Maven Bundle Plugin 5.1.8" + }, + { + "key": "Build-Jdk-Spec", + "value": "21" + }, + { + "key": "Specification-Title", + "value": "Logback Classic Module" + }, + { + "key": "Specification-Version", + "value": "1.4" + }, + { + "key": "Specification-Vendor", + "value": "QOS.ch" + }, + { + "key": "Implementation-Title", + "value": "Logback Classic Module" + }, + { + "key": "Implementation-Version", + "value": "1.4.14" + }, + { + "key": "Implementation-Vendor", + "value": "QOS.ch" + }, + { + "key": "Bundle-Description", + "value": "logback-classic module" + }, + { + "key": "Bundle-DocURL", + "value": "http://www.qos.ch" + }, + { + "key": "Bundle-License", + "value": "http://www.eclipse.org/legal/epl-v10.html, http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Bundle-Name", + "value": "Logback Classic Module" + }, + { + "key": "Bundle-SymbolicName", + "value": "ch.qos.logback.classic" + }, + { + "key": "Bundle-Vendor", + "value": "QOS.ch" + }, + { + "key": "Bundle-Version", + "value": "1.4.14" + }, + { + "key": "Export-Package", + "value": "ch.qos.logback.classic;version=\"1.4.14\";uses:=\"ch.qos.logback.classic.spi,ch.qos.logback.classic.turbo,ch.qos.logback.core,ch.qos.logback.core.pattern,ch.qos.logback.core.spi,ch.qos.logback.core.status,jakarta.servlet.http,org.slf4j,org.slf4j.event,org.slf4j.spi\",ch.qos.logback.classic.boolex;version=\"1.4.14\";uses:=\"ch.qos.logback.classic.spi,ch.qos.logback.core.boolex\",ch.qos.logback.classic.db.script;version=\"1.4.14\",ch.qos.logback.classic.encoder;version=\"1.4.14\";uses:=\"ch.qos.logback.classic.spi,ch.qos.logback.core.encoder,ch.qos.logback.core.pattern\",ch.qos.logback.classic.filter;version=\"1.4.14\";uses:=\"ch.qos.logback.classic,ch.qos.logback.classic.spi,ch.qos.logback.core.filter,ch.qos.logback.core.spi\",ch.qos.logback.classic.helpers;version=\"1.4.14\";uses:=\"ch.qos.logback.classic.spi,ch.qos.logback.core,jakarta.servlet\",ch.qos.logback.classic.html;version=\"1.4.14\";uses:=\"ch.qos.logback.classic.spi,ch.qos.logback.core.html,ch.qos.logback.core.pattern\",ch.qos.logback.classic.joran;version=\"1.4.14\";uses:=\"ch.qos.logback.classic,ch.qos.logback.classic.spi,ch.qos.logback.core,ch.qos.logback.core.joran,ch.qos.logback.core.joran.spi,ch.qos.logback.core.model,ch.qos.logback.core.model.processor,ch.qos.logback.core.spi\",ch.qos.logback.classic.joran.action;version=\"1.4.14\";uses:=\"ch.qos.logback.core.joran.action,ch.qos.logback.core.joran.spi,ch.qos.logback.core.model,org.xml.sax\",ch.qos.logback.classic.joran.sanity;version=\"1.4.14\";uses:=\"ch.qos.logback.core.joran.sanity,ch.qos.logback.core.model,ch.qos.logback.core.spi\",ch.qos.logback.classic.joran.serializedModel;version=\"1.4.14\";uses:=\"ch.qos.logback.core.net\",ch.qos.logback.classic.jul;version=\"1.4.14\";uses:=\"ch.qos.logback.classic,ch.qos.logback.classic.spi,ch.qos.logback.core.spi\",ch.qos.logback.classic.layout;version=\"1.4.14\";uses:=\"ch.qos.logback.classic.spi,ch.qos.logback.core\",ch.qos.logback.classic.log4j;version=\"1.4.14\";uses:=\"ch.qos.logback.classic.spi,ch.qos.logback.core\",ch.qos.logback.classic.model;version=\"1.4.14\";uses:=\"ch.qos.logback.core.model,ch.qos.logback.core.model.processor\",ch.qos.logback.classic.model.processor;version=\"1.4.14\";uses:=\"ch.qos.logback.classic.model,ch.qos.logback.core,ch.qos.logback.core.joran.spi,ch.qos.logback.core.joran.util,ch.qos.logback.core.model,ch.qos.logback.core.model.processor\",ch.qos.logback.classic.model.util;version=\"1.4.14\";uses:=\"ch.qos.logback.core.model\",ch.qos.logback.classic.net;version=\"1.4.14\";uses:=\"ch.qos.logback.classic,ch.qos.logback.classic.spi,ch.qos.logback.core,ch.qos.logback.core.boolex,ch.qos.logback.core.helpers,ch.qos.logback.core.joran.spi,ch.qos.logback.core.net,ch.qos.logback.core.net.ssl,ch.qos.logback.core.pattern,ch.qos.logback.core.spi,javax.net,javax.net.ssl\",ch.qos.logback.classic.net.server;version=\"1.4.14\";uses:=\"ch.qos.logback.classic.net,ch.qos.logback.classic.spi,ch.qos.logback.core.net,ch.qos.logback.core.net.server,ch.qos.logback.core.net.ssl,ch.qos.logback.core.spi,javax.net\",ch.qos.logback.classic.pattern;version=\"1.4.14\";uses:=\"ch.qos.logback.classic.spi,ch.qos.logback.core,ch.qos.logback.core.pattern,org.slf4j\",ch.qos.logback.classic.pattern.color;version=\"1.4.14\";uses:=\"ch.qos.logback.classic.spi,ch.qos.logback.core.pattern.color\",ch.qos.logback.classic.selector;version=\"1.4.14\";uses:=\"ch.qos.logback.classic\",ch.qos.logback.classic.selector.servlet;version=\"1.4.14\";uses:=\"jakarta.servlet\",ch.qos.logback.classic.servlet;version=\"1.4.14\";uses:=\"jakarta.servlet\",ch.qos.logback.classic.sift;version=\"1.4.14\";uses:=\"ch.qos.logback.classic.spi,ch.qos.logback.core.joran.spi,ch.qos.logback.core.sift\",ch.qos.logback.classic.spi;version=\"1.4.14\";uses:=\"ch.qos.logback.classic,ch.qos.logback.classic.turbo,ch.qos.logback.core,ch.qos.logback.core.spi,org.slf4j,org.slf4j.event,org.slf4j.spi\",ch.qos.logback.classic.turbo;version=\"1.4.14\";uses:=\"ch.qos.logback.classic,ch.qos.logback.core.spi,org.slf4j\",ch.qos.logback.classic.util;version=\"1.4.14\";uses:=\"ch.qos.logback.classic,ch.qos.logback.classic.selector,ch.qos.logback.classic.spi,ch.qos.logback.core.joran.spi,ch.qos.logback.core.spi,ch.qos.logback.core.status,org.slf4j.spi\"" + }, + { + "key": "Import-Package", + "value": "ch.qos.logback.classic;version=\"[1.4,2)\",ch.qos.logback.classic.boolex;version=\"[1.4,2)\",ch.qos.logback.classic.encoder;version=\"[1.4,2)\",ch.qos.logback.classic.joran;version=\"[1.4,2)\",ch.qos.logback.classic.joran.action;version=\"[1.4,2)\",ch.qos.logback.classic.joran.sanity;version=\"[1.4,2)\",ch.qos.logback.classic.joran.serializedModel;version=\"[1.4,2)\",ch.qos.logback.classic.layout;version=\"[1.4,2)\",ch.qos.logback.classic.model;version=\"[1.4,2)\",ch.qos.logback.classic.model.processor;version=\"[1.4,2)\",ch.qos.logback.classic.net;version=\"[1.4,2)\",ch.qos.logback.classic.net.server;version=\"[1.4,2)\",ch.qos.logback.classic.pattern;version=\"[1.4,2)\",ch.qos.logback.classic.pattern.color;version=\"[1.4,2)\",ch.qos.logback.classic.selector;version=\"[1.4,2)\",ch.qos.logback.classic.spi;version=\"[1.4,2)\",ch.qos.logback.classic.turbo;version=\"[1.4,2)\",ch.qos.logback.classic.util;version=\"[1.4,2)\",jakarta.servlet;resolution:=optional;version=\"[5.0,6)\",jakarta.servlet.http;resolution:=optional;version=\"[5.0,6)\",org.xml.sax;resolution:=optional,ch.qos.logback.core;version=\"[1.4,2)\",ch.qos.logback.core.boolex;version=\"[1.4,2)\",ch.qos.logback.core.encoder;version=\"[1.4,2)\",ch.qos.logback.core.filter;version=\"[1.4,2)\",ch.qos.logback.core.helpers;version=\"[1.4,2)\",ch.qos.logback.core.html;version=\"[1.4,2)\",ch.qos.logback.core.joran;version=\"[1.4,2)\",ch.qos.logback.core.joran.action;version=\"[1.4,2)\",ch.qos.logback.core.joran.sanity;version=\"[1.4,2)\",ch.qos.logback.core.joran.spi;version=\"[1.4,2)\",ch.qos.logback.core.joran.util;version=\"[1.4,2)\",ch.qos.logback.core.model;version=\"[1.4,2)\",ch.qos.logback.core.model.conditional;version=\"[1.4,2)\",ch.qos.logback.core.model.processor;version=\"[1.4,2)\",ch.qos.logback.core.model.util;version=\"[1.4,2)\",ch.qos.logback.core.net;version=\"[1.4,2)\",ch.qos.logback.core.net.server;version=\"[1.4,2)\",ch.qos.logback.core.net.ssl;version=\"[1.4,2)\",ch.qos.logback.core.pattern;version=\"[1.4,2)\",ch.qos.logback.core.pattern.color;version=\"[1.4,2)\",ch.qos.logback.core.pattern.parser;version=\"[1.4,2)\",ch.qos.logback.core.sift;version=\"[1.4,2)\",ch.qos.logback.core.spi;version=\"[1.4,2)\",ch.qos.logback.core.status;version=\"[1.4,2)\",ch.qos.logback.core.util;version=\"[1.4,2)\",java.io,java.lang,java.lang.annotation,java.lang.invoke,java.lang.reflect,java.net,java.nio.charset,java.security,java.text,java.time,java.util,java.util.concurrent,java.util.concurrent.atomic,java.util.function,java.util.logging,java.util.regex,javax.management,javax.naming,javax.net,javax.net.ssl,org.slf4j;version=\"[2.0,3)\",org.slf4j.event;version=\"[2.0,3)\",org.slf4j.helpers;version=\"[2.0,3)\",org.slf4j.spi;version=\"[2.0,3)\",sun.reflect;resolution:=optional,ch.qos.logback.core.rolling;version=\"[1.4,2)\",ch.qos.logback.core.rolling.helper;version=\"[1.4,2)\",ch.qos.logback.core.read;version=\"[1.4,2)\"" + }, + { + "key": "Originally-Created-By", + "value": "Apache Maven Bundle Plugin 5.1.8" + }, + { + "key": "Provide-Capability", + "value": "osgi.service;objectClass:List=\"jakarta.servlet.ServletContainerInitializer\";effective:=active,osgi.service;objectClass:List=\"org.slf4j.spi.SLF4JServiceProvider\";effective:=active,osgi.serviceloader;osgi.serviceloader=\"jakarta.servlet.ServletContainerInitializer\";register:=\"ch.qos.logback.classic.servlet.LogbackServletContainerInitializer\",osgi.serviceloader;osgi.serviceloader=\"org.slf4j.spi.SLF4JServiceProvider\";register:=\"ch.qos.logback.classic.spi.LogbackServiceProvider\"" + }, + { + "key": "Require-Capability", + "value": "osgi.extender;filter:=\"(&(osgi.extender=osgi.serviceloader.processor)(version>=1.0.0)(!(version>=2.0.0)))\";resolution:=optional,osgi.extender;filter:=\"(&(osgi.extender=osgi.serviceloader.registrar)(version>=1.0.0)(!(version>=2.0.0)))\",osgi.serviceloader;filter:=\"(osgi.serviceloader=ch.qos.logback.classic.spi.Configurator)\";osgi.serviceloader=\"ch.qos.logback.classic.spi.Configurator\";resolution:=optional;cardinality:=multiple,osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=11))\"" + }, + { + "key": "Tool", + "value": "Bnd-6.3.1.202206071316" + } + ] + }, + "pomProperties": { + "path": "META-INF/maven/ch.qos.logback/logback-classic/pom.properties", + "name": "", + "groupId": "ch.qos.logback", + "artifactId": "logback-classic", + "version": "1.4.14" + }, + "digest": [ + { + "algorithm": "sha1", + "value": "d98bc162275134cdf1518774da4a2a17ef6fb94d" + } + ] + } + }, + { + "id": "3748310e1aac44ea", + "name": "logback-core", + "version": "1.4.14", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/logback-core-1.4.14.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "http://www.eclipse.org/legal/epl-v10.html, http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html", + "spdxExpression": "", + "type": "declared", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/logback-core-1.4.14.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:logback-core:logback-core:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:logback-core:logback_core:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:logback_core:logback-core:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:logback_core:logback_core:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:logback:logback-core:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:logback:logback_core:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:qos-ch:logback-core:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:qos-ch:logback_core:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:qos_ch:logback-core:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:qos_ch:logback_core:1.4.14:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/ch.qos.logback/logback-core@1.4.14", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/logback-core-1.4.14.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Created-By", + "value": "Apache Maven Bundle Plugin 5.1.8" + }, + { + "key": "Build-Jdk-Spec", + "value": "21" + }, + { + "key": "Specification-Title", + "value": "Logback Core Module" + }, + { + "key": "Specification-Version", + "value": "1.4" + }, + { + "key": "Specification-Vendor", + "value": "QOS.ch" + }, + { + "key": "Implementation-Title", + "value": "Logback Core Module" + }, + { + "key": "Implementation-Version", + "value": "1.4.14" + }, + { + "key": "Implementation-Vendor", + "value": "QOS.ch" + }, + { + "key": "Bundle-Description", + "value": "logback-core module" + }, + { + "key": "Bundle-DocURL", + "value": "http://www.qos.ch" + }, + { + "key": "Bundle-License", + "value": "http://www.eclipse.org/legal/epl-v10.html, http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Bundle-Name", + "value": "Logback Core Module" + }, + { + "key": "Bundle-SymbolicName", + "value": "ch.qos.logback.core" + }, + { + "key": "Bundle-Vendor", + "value": "QOS.ch" + }, + { + "key": "Bundle-Version", + "value": "1.4.14" + }, + { + "key": "Export-Package", + "value": "ch.qos.logback.core;version=\"1.4.14\";uses:=\"ch.qos.logback.core.encoder,ch.qos.logback.core.filter,ch.qos.logback.core.helpers,ch.qos.logback.core.joran.spi,ch.qos.logback.core.spi,ch.qos.logback.core.status,ch.qos.logback.core.util\",ch.qos.logback.core.boolex;version=\"1.4.14\";uses:=\"ch.qos.logback.core.spi\",ch.qos.logback.core.encoder;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.spi\",ch.qos.logback.core.filter;version=\"1.4.14\";uses:=\"ch.qos.logback.core.boolex,ch.qos.logback.core.spi\",ch.qos.logback.core.helpers;version=\"1.4.14\";uses:=\"ch.qos.logback.core\",ch.qos.logback.core.hook;version=\"1.4.14\";uses:=\"ch.qos.logback.core.spi,ch.qos.logback.core.util\",ch.qos.logback.core.html;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.pattern\",ch.qos.logback.core.joran;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.joran.event,ch.qos.logback.core.joran.sanity,ch.qos.logback.core.joran.spi,ch.qos.logback.core.joran.util.beans,ch.qos.logback.core.model,ch.qos.logback.core.model.processor,ch.qos.logback.core.spi,org.xml.sax\",ch.qos.logback.core.joran.action;version=\"1.4.14\";uses:=\"ch.qos.logback.core.joran.spi,ch.qos.logback.core.joran.util,ch.qos.logback.core.model,ch.qos.logback.core.model.processor,ch.qos.logback.core.spi,ch.qos.logback.core.util,org.xml.sax\",ch.qos.logback.core.joran.conditional;version=\"1.4.14\";uses:=\"ch.qos.logback.core.joran.action,ch.qos.logback.core.joran.spi,ch.qos.logback.core.model,ch.qos.logback.core.spi,org.codehaus.commons.compiler,org.xml.sax\",ch.qos.logback.core.joran.event;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.joran.spi,ch.qos.logback.core.spi,ch.qos.logback.core.status,org.xml.sax,org.xml.sax.helpers\",ch.qos.logback.core.joran.event.stax;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.joran.spi,ch.qos.logback.core.spi,javax.xml.stream,javax.xml.stream.events\",ch.qos.logback.core.joran.node;version=\"1.4.14\",ch.qos.logback.core.joran.sanity;version=\"1.4.14\";uses:=\"ch.qos.logback.core.model,ch.qos.logback.core.spi\",ch.qos.logback.core.joran.spi;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.joran.action,ch.qos.logback.core.joran.event,ch.qos.logback.core.model,ch.qos.logback.core.model.processor,ch.qos.logback.core.spi,ch.qos.logback.core.status,org.xml.sax\",ch.qos.logback.core.joran.util;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.joran.spi,ch.qos.logback.core.joran.util.beans,ch.qos.logback.core.spi,ch.qos.logback.core.util\",ch.qos.logback.core.joran.util.beans;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.spi\",ch.qos.logback.core.layout;version=\"1.4.14\";uses:=\"ch.qos.logback.core\",ch.qos.logback.core.model;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.joran.action,ch.qos.logback.core.model.processor\",ch.qos.logback.core.model.conditional;version=\"1.4.14\";uses:=\"ch.qos.logback.core.model\",ch.qos.logback.core.model.processor;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.joran.action,ch.qos.logback.core.joran.spi,ch.qos.logback.core.joran.util.beans,ch.qos.logback.core.model,ch.qos.logback.core.spi\",ch.qos.logback.core.model.processor.conditional;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.model,ch.qos.logback.core.model.conditional,ch.qos.logback.core.model.processor\",ch.qos.logback.core.model.util;version=\"1.4.14\";uses:=\"ch.qos.logback.core.model\",ch.qos.logback.core.net;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.boolex,ch.qos.logback.core.helpers,ch.qos.logback.core.net.ssl,ch.qos.logback.core.pattern,ch.qos.logback.core.sift,ch.qos.logback.core.spi,ch.qos.logback.core.util,jakarta.mail,jakarta.mail.internet,javax.net\",ch.qos.logback.core.net.server;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.net.ssl,ch.qos.logback.core.spi,javax.net\",ch.qos.logback.core.net.ssl;version=\"1.4.14\";uses:=\"ch.qos.logback.core.joran.spi,ch.qos.logback.core.spi,javax.net,javax.net.ssl\",ch.qos.logback.core.pattern;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.encoder,ch.qos.logback.core.spi,ch.qos.logback.core.status\",ch.qos.logback.core.pattern.color;version=\"1.4.14\";uses:=\"ch.qos.logback.core.pattern\",ch.qos.logback.core.pattern.parser;version=\"1.4.14\";uses:=\"ch.qos.logback.core.pattern,ch.qos.logback.core.pattern.util,ch.qos.logback.core.spi\",ch.qos.logback.core.pattern.util;version=\"1.4.14\",ch.qos.logback.core.property;version=\"1.4.14\";uses:=\"ch.qos.logback.core\",ch.qos.logback.core.read;version=\"1.4.14\";uses:=\"ch.qos.logback.core\",ch.qos.logback.core.recovery;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.status\",ch.qos.logback.core.rolling;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.joran.spi,ch.qos.logback.core.rolling.helper,ch.qos.logback.core.spi,ch.qos.logback.core.util\",ch.qos.logback.core.rolling.helper;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.pattern,ch.qos.logback.core.rolling,ch.qos.logback.core.spi\",ch.qos.logback.core.sift;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.joran.spi,ch.qos.logback.core.model,ch.qos.logback.core.model.processor,ch.qos.logback.core.spi,ch.qos.logback.core.util\",ch.qos.logback.core.spi;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.filter,ch.qos.logback.core.helpers,ch.qos.logback.core.status\",ch.qos.logback.core.status;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.spi,jakarta.servlet,jakarta.servlet.http\",ch.qos.logback.core.subst;version=\"1.4.14\";uses:=\"ch.qos.logback.core.spi\",ch.qos.logback.core.testUtil;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.encoder,ch.qos.logback.core.read,ch.qos.logback.core.spi,ch.qos.logback.core.status,javax.naming,javax.naming.spi\",ch.qos.logback.core.util;version=\"1.4.14\";uses:=\"ch.qos.logback.core,ch.qos.logback.core.rolling,ch.qos.logback.core.rolling.helper,ch.qos.logback.core.spi,ch.qos.logback.core.status,javax.naming\"" + }, + { + "key": "Import-Package", + "value": "ch.qos.logback.core;version=\"[1.4,2)\",ch.qos.logback.core.boolex;version=\"[1.4,2)\",ch.qos.logback.core.encoder;version=\"[1.4,2)\",ch.qos.logback.core.filter;version=\"[1.4,2)\",ch.qos.logback.core.helpers;version=\"[1.4,2)\",ch.qos.logback.core.hook;version=\"[1.4,2)\",ch.qos.logback.core.joran;version=\"[1.4,2)\",ch.qos.logback.core.joran.action;version=\"[1.4,2)\",ch.qos.logback.core.joran.conditional;version=\"[1.4,2)\",ch.qos.logback.core.joran.event;version=\"[1.4,2)\",ch.qos.logback.core.joran.sanity;version=\"[1.4,2)\",ch.qos.logback.core.joran.spi;version=\"[1.4,2)\",ch.qos.logback.core.joran.util;version=\"[1.4,2)\",ch.qos.logback.core.joran.util.beans;version=\"[1.4,2)\",ch.qos.logback.core.model;version=\"[1.4,2)\",ch.qos.logback.core.model.conditional;version=\"[1.4,2)\",ch.qos.logback.core.model.processor;version=\"[1.4,2)\",ch.qos.logback.core.model.processor.conditional;version=\"[1.4,2)\",ch.qos.logback.core.net;version=\"[1.4,2)\",ch.qos.logback.core.net.ssl;version=\"[1.4,2)\",ch.qos.logback.core.pattern;version=\"[1.4,2)\",ch.qos.logback.core.pattern.parser;version=\"[1.4,2)\",ch.qos.logback.core.pattern.util;version=\"[1.4,2)\",ch.qos.logback.core.read;version=\"[1.4,2)\",ch.qos.logback.core.recovery;version=\"[1.4,2)\",ch.qos.logback.core.rolling;version=\"[1.4,2)\",ch.qos.logback.core.rolling.helper;version=\"[1.4,2)\",ch.qos.logback.core.sift;version=\"[1.4,2)\",ch.qos.logback.core.spi;version=\"[1.4,2)\",ch.qos.logback.core.status;version=\"[1.4,2)\",ch.qos.logback.core.subst;version=\"[1.4,2)\",ch.qos.logback.core.util;version=\"[1.4,2)\",jakarta.mail;resolution:=optional;version=\"[2.1,3)\",jakarta.mail.internet;resolution:=optional;version=\"[2.1,3)\",jakarta.servlet;resolution:=optional;version=\"[5.0,6)\",jakarta.servlet.http;resolution:=optional;version=\"[5.0,6)\",org.xml.sax;resolution:=optional,org.xml.sax.helpers;resolution:=optional,org.codehaus.janino;resolution:=optional;version=\"[3.1,4)\",org.codehaus.commons.compiler;resolution:=optional;version=\"[3.1,4)\",java.io,java.lang,java.lang.annotation,java.lang.invoke,java.lang.module,java.lang.reflect,java.math,java.net,java.nio,java.nio.channels,java.nio.charset,java.nio.file,java.security,java.security.cert,java.text,java.time,java.time.format,java.time.temporal,java.util,java.util.concurrent,java.util.concurrent.atomic,java.util.concurrent.locks,java.util.function,java.util.regex,java.util.stream,java.util.zip,javax.naming,javax.naming.spi,javax.net,javax.net.ssl,javax.xml.namespace,javax.xml.parsers,javax.xml.stream,javax.xml.stream.events,org.fusesource.jansi;resolution:=optional;version=\"[2.4,3)\"" + }, + { + "key": "Originally-Created-By", + "value": "Apache Maven Bundle Plugin 5.1.8" + }, + { + "key": "Require-Capability", + "value": "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=11))\"" + }, + { + "key": "Tool", + "value": "Bnd-6.3.1.202206071316" + } + ] + }, + "pomProperties": { + "path": "META-INF/maven/ch.qos.logback/logback-core/pom.properties", + "name": "", + "groupId": "ch.qos.logback", + "artifactId": "logback-core", + "version": "1.4.14" + }, + "digest": [ + { + "algorithm": "sha1", + "value": "4d3c2248219ac0effeb380ed4c5280a80bf395e8" + } + ] + } + }, + { + "id": "c46f369578c77c43", + "name": "micrometer-commons", + "version": "1.13.0-M1", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/micrometer-commons-1.13.0-M1.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "Apache-2.0", + "spdxExpression": "Apache-2.0", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/micrometer-commons-1.13.0-M1.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:micrometer-commons:micrometer-commons:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer-commons:micrometer_commons:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer_commons:micrometer-commons:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer_commons:micrometer_commons:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer:micrometer-commons:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer:micrometer_commons:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/io.micrometer/micrometer-commons@1.13.0-M1", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/micrometer-commons-1.13.0-M1.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Automatic-Module-Name", + "value": "micrometer.commons" + }, + { + "key": "Bnd-LastModified", + "value": "1707769856136" + }, + { + "key": "Branch", + "value": "HEAD" + }, + { + "key": "Build-Date", + "value": "2024-02-12_20:30:25" + }, + { + "key": "Build-Date-UTC", + "value": "2024-02-12T20:30:25.169807141Z" + }, + { + "key": "Build-Host", + "value": "bea640c5c9a6" + }, + { + "key": "Build-Id", + "value": "30241" + }, + { + "key": "Build-Java-Version", + "value": "21" + }, + { + "key": "Build-Job", + "value": "deploy" + }, + { + "key": "Build-Number", + "value": "30241" + }, + { + "key": "Build-Timezone", + "value": "Etc/UTC" + }, + { + "key": "Build-Url", + "value": "https://circleci.com/gh/micrometer-metrics/micrometer/30241" + }, + { + "key": "Built-By", + "value": "circleci" + }, + { + "key": "Built-OS", + "value": "Linux" + }, + { + "key": "Built-Status", + "value": "candidate" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Bundle-Name", + "value": "micrometer-commons" + }, + { + "key": "Bundle-SymbolicName", + "value": "micrometer-commons" + }, + { + "key": "Bundle-Version", + "value": "1.13.0.M1" + }, + { + "key": "Change", + "value": "639c93a" + }, + { + "key": "Created-By", + "value": "21.0.2 (Eclipse Adoptium)" + }, + { + "key": "DynamicImport-Package", + "value": "org.aspectj.lang,org.aspectj.lang.reflect" + }, + { + "key": "Export-Package", + "value": "io.micrometer.common;uses:=\"io.micrometer.common.docs,io.micrometer.common.lang\";version=\"1.13.0\",io.micrometer.common.annotation;uses:=\"io.micrometer.common,io.micrometer.common.lang,org.aspectj.lang\";version=\"1.13.0\",io.micrometer.common.docs;uses:=\"io.micrometer.common\";version=\"1.13.0\",io.micrometer.common.lang;uses:=\"javax.annotation,javax.annotation.meta\";version=\"1.13.0\",io.micrometer.common.util;uses:=\"io.micrometer.common.lang\";version=\"1.13.0\",io.micrometer.common.util.internal.logging;version=\"1.13.0\"" + }, + { + "key": "Full-Change", + "value": "639c93af0d0507b4cfa0e0581146719863b691b1" + }, + { + "key": "Gradle-Version", + "value": "8.6" + }, + { + "key": "Implementation-Title", + "value": "io.micrometer#micrometer-commons;1.13.0-M1" + }, + { + "key": "Implementation-Version", + "value": "1.13.0-M1" + }, + { + "key": "Import-Package", + "value": "io.micrometer.common,io.micrometer.common.docs,io.micrometer.common.lang,io.micrometer.common.util.internal.logging,javax.annotation;version=\"[3.0,4)\",javax.annotation.meta;version=\"[3.0,4)\",org.slf4j;version=\"[1.7,2)\",org.slf4j.helpers;version=\"[1.7,2)\",org.slf4j.spi;version=\"[1.7,2)\"" + }, + { + "key": "Module-Email", + "value": "tludwig@vmware.com" + }, + { + "key": "Module-Origin", + "value": "git@github.com:micrometer-metrics/micrometer.git" + }, + { + "key": "Module-Owner", + "value": "tludwig@vmware.com" + }, + { + "key": "Module-Source", + "value": "/micrometer-commons" + }, + { + "key": "Require-Capability", + "value": "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\"" + }, + { + "key": "Tool", + "value": "Bnd-6.4.0.202211291949" + }, + { + "key": "X-Compile-Source-JDK", + "value": "1.8" + }, + { + "key": "X-Compile-Target-JDK", + "value": "1.8" + } + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "e738daf6678eedf8e0c40a782bdb0df064a391e5" + } + ] + } + }, + { + "id": "3c0d8567351e2ae4", + "name": "micrometer-core", + "version": "1.13.0-M1", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/micrometer-core-1.13.0-M1.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "Apache-2.0", + "spdxExpression": "Apache-2.0", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/micrometer-core-1.13.0-M1.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:micrometer-core:micrometer-core:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer-core:micrometer_core:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer_core:micrometer-core:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer_core:micrometer_core:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer:micrometer-core:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer:micrometer_core:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/io.micrometer/micrometer-core@1.13.0-M1", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/micrometer-core-1.13.0-M1.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Automatic-Module-Name", + "value": "micrometer.core" + }, + { + "key": "Bnd-LastModified", + "value": "1707769876578" + }, + { + "key": "Branch", + "value": "HEAD" + }, + { + "key": "Build-Date", + "value": "2024-02-12_20:30:25" + }, + { + "key": "Build-Date-UTC", + "value": "2024-02-12T20:30:25.236904273Z" + }, + { + "key": "Build-Host", + "value": "bea640c5c9a6" + }, + { + "key": "Build-Id", + "value": "30241" + }, + { + "key": "Build-Java-Version", + "value": "21" + }, + { + "key": "Build-Job", + "value": "deploy" + }, + { + "key": "Build-Number", + "value": "30241" + }, + { + "key": "Build-Timezone", + "value": "Etc/UTC" + }, + { + "key": "Build-Url", + "value": "https://circleci.com/gh/micrometer-metrics/micrometer/30241" + }, + { + "key": "Built-By", + "value": "circleci" + }, + { + "key": "Built-OS", + "value": "Linux" + }, + { + "key": "Built-Status", + "value": "candidate" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Bundle-Name", + "value": "micrometer-core" + }, + { + "key": "Bundle-SymbolicName", + "value": "micrometer-core" + }, + { + "key": "Bundle-Version", + "value": "1.13.0.M1" + }, + { + "key": "Change", + "value": "639c93a" + }, + { + "key": "Created-By", + "value": "21.0.2 (Eclipse Adoptium)" + }, + { + "key": "DynamicImport-Package", + "value": "org.aspectj.lang,org.aspectj.lang.annotation,org.aspectj.lang.reflect,com.github.benmanes.caffeine.cache;version=\"2.9.3\",com.github.benmanes.caffeine.cache.stats;version=\"2.9.3\",net.sf.ehcache;version=\"2.10.9\",net.sf.ehcache.statistics;version=\"2.10.9\",javax.cache;version=\"1.1.1\",org.hibernate;version=\"5.6.15.Final\",org.hibernate.engine.spi;version=\"5.6.15.Final\",org.hibernate.event.service.spi;version=\"5.6.15.Final\",org.hibernate.event.spi;version=\"5.6.15.Final\",org.hibernate.service;version=\"5.6.15.Final\",org.hibernate.service.spi;version=\"5.6.15.Final\",org.hibernate.stat;version=\"5.6.15.Final\",org.hibernate.stat.spi;version=\"5.6.15.Final\",org.eclipse.jetty.client.api;version=\"9.4.53\",org.eclipse.jetty.http;version=\"9.4.53\",org.eclipse.jetty.io;version=\"9.4.53\",org.eclipse.jetty.io.ssl;version=\"9.4.53\",org.eclipse.jetty.server;version=\"9.4.53\",org.eclipse.jetty.server.handler;version=\"9.4.53\",org.eclipse.jetty.util;version=\"9.4.53\",org.eclipse.jetty.util.component;version=\"9.4.53\",org.eclipse.jetty.util.thread;version=\"9.4.53\",org.glassfish.jersey.server;version=\"2.41\",org.glassfish.jersey.server.model;version=\"2.41\",org.glassfish.jersey.server.monitoring;version=\"2.41\",org.glassfish.jersey.uri;version=\"2.41\",io.grpc,io.grpc.kotlin,org.apache.hc.client5.http,org.apache.hc.client5.http.async,org.apache.hc.client5.http.classic,org.apache.hc.client5.http.protocol,org.apache.hc.core5.concurrent,org.apache.hc.core5.http,org.apache.hc.core5.http.impl,org.apache.hc.core5.http.impl.io,org.apache.hc.core5.http.io,org.apache.hc.core5.http.nio,org.apache.hc.core5.http.protocol,org.apache.hc.core5.pool,org.apache.hc.core5.util,org.apache.http,org.apache.http.conn.routing,org.apache.http.pool,org.apache.http.protocol,com.netflix.hystrix;version=\"1.5.12\",com.netflix.hystrix.metric;version=\"1.5.12\",com.netflix.hystrix.strategy;version=\"1.5.12\",com.netflix.hystrix.strategy.concurrency;version=\"1.5.12\",com.netflix.hystrix.strategy.eventnotifier;version=\"1.5.12\",com.netflix.hystrix.strategy.executionhook;version=\"1.5.12\",com.netflix.hystrix.strategy.metrics;version=\"1.5.12\",com.netflix.hystrix.strategy.properties;version=\"1.5.12\",ch.qos.logback.classic;version=\"1.2.13\",ch.qos.logback.classic.spi;version=\"1.2.13\",ch.qos.logback.classic.turbo;version=\"1.2.13\",ch.qos.logback.core.spi;version=\"1.2.13\",org.apache.logging.log4j;version=\"2.20.2\",org.apache.logging.log4j.core;version=\"2.20.2\",org.apache.logging.log4j.core.config;version=\"2.21.0\",org.apache.logging.log4j.core.filter;version=\"2.21.0\",org.apache.logging.log4j.spi;version=\"2.20.1\",okhttp3,com.mongodb;version=\"4.11.1\",com.mongodb.connection;version=\"4.11.1\",com.mongodb.event;version=\"4.11.1\",org.jooq;version=\"3.14.16\",org.jooq.exception;version=\"3.14.16\",org.jooq.impl;version=\"3.14.16\",org.apache.kafka.clients.admin,org.apache.kafka.clients.consumer,org.apache.kafka.clients.producer,org.apache.kafka.common,org.apache.kafka.common.metrics,org.apache.kafka.streams,com.codahale.metrics;version=\"4.2.25\",com.google.common.cache;version=\"32.1.2\",jakarta.servlet.http;version=\"5.0.0\",javax.servlet;version=\"4.0.0\",javax.servlet.http;version=\"4.0.0\",io.micrometer.context,io.micrometer.observation;version=\"1.13.0\",io.micrometer.observation.docs;version=\"1.13.0\",io.micrometer.observation.transport;version=\"1.13.0\",kotlin,kotlin.coroutines,kotlin.jvm.functions,kotlin.jvm.internal,kotlinx.coroutines,org.LatencyUtils,org.HdrHistogram;version=\"2.1.12\",org.apache.catalina,org.bson;version=\"4.11.1\",rx;version=\"1.2.0\",rx.functions;version=\"1.2.0\",javax.persistence;version=\"2.2.0\",io.netty.buffer;version=\"4.1.106\",io.netty.util.concurrent;version=\"4.1.106\"" + }, + { + "key": "Export-Package", + "value": "io.micrometer.core.annotation;version=\"1.13.0\",io.micrometer.core.aop;uses:=\"io.micrometer.common.annotation,io.micrometer.common.lang,io.micrometer.core.annotation,io.micrometer.core.instrument,org.aspectj.lang,org.aspectj.lang.annotation\";version=\"1.13.0\",io.micrometer.core.instrument;uses:=\"io.micrometer.common.lang,io.micrometer.core.annotation,io.micrometer.core.instrument.composite,io.micrometer.core.instrument.config,io.micrometer.core.instrument.distribution,io.micrometer.core.instrument.distribution.pause,io.micrometer.core.instrument.search\";version=\"1.13.0\",io.micrometer.core.instrument.binder;uses:=\"io.micrometer.common.lang,io.micrometer.core.instrument\";version=\"1.13.0\",io.micrometer.core.instrument.binder.cache;uses:=\"com.github.benmanes.caffeine.cache,com.github.benmanes.caffeine.cache.stats,com.google.common.cache,io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.binder,javax.cache,net.sf.ehcache\";version=\"1.13.0\",io.micrometer.core.instrument.binder.commonspool2;uses:=\"io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.binder,javax.management\";version=\"1.13.0\",io.micrometer.core.instrument.binder.db;uses:=\"io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.binder,javax.sql,org.jooq,org.jooq.impl\";version=\"1.13.0\",io.micrometer.core.instrument.binder.grpc;uses:=\"io.grpc,io.micrometer.common,io.micrometer.common.docs,io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.observation,io.micrometer.observation.docs,io.micrometer.observation.transport\";version=\"1.13.0\",io.micrometer.core.instrument.binder.http;uses:=\"io.micrometer.common,io.micrometer.common.lang,io.micrometer.core.instrument,jakarta.servlet.http,javax.servlet.http\";version=\"1.13.0\",io.micrometer.core.instrument.binder.httpcomponents;uses:=\"io.micrometer.common,io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.binder,io.micrometer.observation,io.micrometer.observation.docs,io.micrometer.observation.transport,org.apache.http,org.apache.http.conn.routing,org.apache.http.pool,org.apache.http.protocol\";version=\"1.13.0\",io.micrometer.core.instrument.binder.httpcomponents.hc5;uses:=\"io.micrometer.common,io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.binder,io.micrometer.observation,io.micrometer.observation.docs,io.micrometer.observation.transport,org.apache.hc.client5.http,org.apache.hc.client5.http.async,org.apache.hc.client5.http.classic,org.apache.hc.client5.http.protocol,org.apache.hc.core5.http,org.apache.hc.core5.http.impl.io,org.apache.hc.core5.http.io,org.apache.hc.core5.http.nio,org.apache.hc.core5.http.protocol,org.apache.hc.core5.pool,org.apache.hc.core5.util\";version=\"1.13.0\",io.micrometer.core.instrument.binder.hystrix;uses:=\"com.netflix.hystrix,com.netflix.hystrix.strategy.metrics,io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.binder\";version=\"1.13.0\",io.micrometer.core.instrument.binder.jersey.server;uses:=\"io.micrometer.common,io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.observation,io.micrometer.observation.docs,io.micrometer.observation.transport,org.glassfish.jersey.server,org.glassfish.jersey.server.monitoring\";version=\"1.13.0\",io.micrometer.core.instrument.binder.jetty;uses:=\"io.micrometer.common,io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.binder,io.micrometer.core.instrument.binder.http,io.micrometer.observation,io.micrometer.observation.docs,io.micrometer.observation.transport,javax.servlet,javax.servlet.http,org.eclipse.jetty.client.api,org.eclipse.jetty.io,org.eclipse.jetty.io.ssl,org.eclipse.jetty.server,org.eclipse.jetty.server.handler,org.eclipse.jetty.util.component,org.eclipse.jetty.util.thread\";version=\"1.13.0\",io.micrometer.core.instrument.binder.jpa;uses:=\"io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.binder,javax.persistence,org.hibernate\";version=\"1.13.0\",io.micrometer.core.instrument.binder.jvm;uses:=\"io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.binder\";version=\"1.13.0\",io.micrometer.core.instrument.binder.kafka;uses:=\"io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.binder,javax.management,org.apache.kafka.clients.admin,org.apache.kafka.clients.consumer,org.apache.kafka.clients.producer,org.apache.kafka.streams\";version=\"1.13.0\",io.micrometer.core.instrument.binder.logging;uses:=\"ch.qos.logback.classic,io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.binder,org.apache.logging.log4j.core\";version=\"1.13.0\",io.micrometer.core.instrument.binder.mongodb;uses:=\"com.mongodb.event,io.micrometer.common.lang,io.micrometer.core.instrument,org.bson\";version=\"1.13.0\",io.micrometer.core.instrument.binder.netty4;uses:=\"io.micrometer.core.instrument,io.micrometer.core.instrument.binder,io.micrometer.core.instrument.docs,io.netty.buffer,io.netty.util.concurrent\";version=\"1.13.0\",io.micrometer.core.instrument.binder.okhttp3;uses:=\"io.micrometer.common,io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.binder,io.micrometer.observation,io.micrometer.observation.docs,io.micrometer.observation.transport,okhttp3\";version=\"1.13.0\",io.micrometer.core.instrument.binder.system;uses:=\"io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.binder\";version=\"1.13.0\",io.micrometer.core.instrument.binder.tomcat;uses:=\"io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.binder,javax.management,org.apache.catalina\";version=\"1.13.0\",io.micrometer.core.instrument.composite;uses:=\"io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.distribution,io.micrometer.core.instrument.distribution.pause\";version=\"1.13.0\",io.micrometer.core.instrument.config;uses:=\"io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.config.validate,io.micrometer.core.instrument.distribution\";version=\"1.13.0\",io.micrometer.core.instrument.config.validate;uses:=\"io.micrometer.common.lang,io.micrometer.core.instrument.config\";version=\"1.13.0\",io.micrometer.core.instrument.cumulative;uses:=\"io.micrometer.core.instrument,io.micrometer.core.instrument.distribution,io.micrometer.core.instrument.distribution.pause\";version=\"1.13.0\",io.micrometer.core.instrument.distribution;uses:=\"io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.internal,io.micrometer.core.instrument.step,org.HdrHistogram\";version=\"1.13.0\",io.micrometer.core.instrument.distribution.pause;version=\"1.13.0\",io.micrometer.core.instrument.docs;uses:=\"io.micrometer.common.docs,io.micrometer.common.lang,io.micrometer.core.instrument\";version=\"1.13.0\",io.micrometer.core.instrument.dropwizard;uses:=\"com.codahale.metrics,io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.config,io.micrometer.core.instrument.config.validate,io.micrometer.core.instrument.distribution,io.micrometer.core.instrument.distribution.pause,io.micrometer.core.instrument.util\";version=\"1.13.0\",io.micrometer.core.instrument.internal;uses:=\"io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.config,io.micrometer.core.instrument.distribution\";version=\"1.13.0\",io.micrometer.core.instrument.kotlin;uses:=\"io.grpc,io.grpc.kotlin,io.micrometer.observation,kotlin,kotlin.coroutines\";version=\"1.13.0\",io.micrometer.core.instrument.logging;uses:=\"io.micrometer.core.instrument,io.micrometer.core.instrument.distribution,io.micrometer.core.instrument.distribution.pause,io.micrometer.core.instrument.step\";version=\"1.13.0\",io.micrometer.core.instrument.noop;uses:=\"io.micrometer.core.instrument,io.micrometer.core.instrument.distribution\";version=\"1.13.0\",io.micrometer.core.instrument.observation;uses:=\"io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.observation\";version=\"1.13.0\",io.micrometer.core.instrument.push;uses:=\"io.micrometer.core.instrument,io.micrometer.core.instrument.config,io.micrometer.core.instrument.config.validate\";version=\"1.13.0\",io.micrometer.core.instrument.search;uses:=\"io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.config\";version=\"1.13.0\",io.micrometer.core.instrument.simple;uses:=\"io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.config,io.micrometer.core.instrument.config.validate,io.micrometer.core.instrument.distribution,io.micrometer.core.instrument.distribution.pause\";version=\"1.13.0\",io.micrometer.core.instrument.step;uses:=\"io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.config.validate,io.micrometer.core.instrument.distribution,io.micrometer.core.instrument.distribution.pause,io.micrometer.core.instrument.push\";version=\"1.13.0\",io.micrometer.core.instrument.util;uses:=\"io.micrometer.common.lang,io.micrometer.core.instrument,io.micrometer.core.instrument.config\";version=\"1.13.0\",io.micrometer.core.ipc.http;uses:=\"io.micrometer.common.lang,okhttp3\";version=\"1.13.0\",io.micrometer.core.lang;uses:=\"javax.annotation,javax.annotation.meta\";version=\"1.13.0\",io.micrometer.core.util.internal.logging;version=\"1.13.0\"" + }, + { + "key": "Full-Change", + "value": "639c93af0d0507b4cfa0e0581146719863b691b1" + }, + { + "key": "Gradle-Version", + "value": "8.6" + }, + { + "key": "Implementation-Title", + "value": "io.micrometer#micrometer-core;1.13.0-M1" + }, + { + "key": "Implementation-Version", + "value": "1.13.0-M1" + }, + { + "key": "Import-Package", + "value": "com.sun.management,io.micrometer.common;version=\"[1.13,2)\",io.micrometer.common.annotation;version=\"[1.13,2)\",io.micrometer.common.docs;version=\"[1.13,2)\",io.micrometer.common.lang;version=\"[1.13,2)\",io.micrometer.common.util;version=\"[1.13,2)\",io.micrometer.common.util.internal.logging;version=\"[1.13,2)\",io.micrometer.core.annotation,io.micrometer.core.instrument,io.micrometer.core.instrument.binder,io.micrometer.core.instrument.binder.http,io.micrometer.core.instrument.composite,io.micrometer.core.instrument.config,io.micrometer.core.instrument.config.validate,io.micrometer.core.instrument.cumulative,io.micrometer.core.instrument.distribution,io.micrometer.core.instrument.distribution.pause,io.micrometer.core.instrument.docs,io.micrometer.core.instrument.internal,io.micrometer.core.instrument.noop,io.micrometer.core.instrument.observation,io.micrometer.core.instrument.push,io.micrometer.core.instrument.search,io.micrometer.core.instrument.step,io.micrometer.core.instrument.util,javax.annotation;version=\"[3.0,4)\",javax.annotation.meta;version=\"[3.0,4)\",javax.management,javax.management.openmbean,javax.net.ssl,javax.sql,org.slf4j;version=\"[1.7,2)\",org.slf4j.helpers;version=\"[1.7,2)\",org.slf4j.spi;version=\"[1.7,2)\"" + }, + { + "key": "Module-Email", + "value": "tludwig@vmware.com" + }, + { + "key": "Module-Origin", + "value": "git@github.com:micrometer-metrics/micrometer.git" + }, + { + "key": "Module-Owner", + "value": "tludwig@vmware.com" + }, + { + "key": "Module-Source", + "value": "/micrometer-core" + }, + { + "key": "Multi-Release", + "value": "true" + }, + { + "key": "Require-Capability", + "value": "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\"" + }, + { + "key": "Tool", + "value": "Bnd-6.4.0.202211291949" + }, + { + "key": "X-Compile-Source-JDK", + "value": "1.8" + }, + { + "key": "X-Compile-Target-JDK", + "value": "1.8" + } + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "49d54a8ed6d3266b4f2691027d95144e946bbe36" + } + ] + } + }, + { + "id": "f4ea2c844b65a026", + "name": "micrometer-jakarta9", + "version": "1.13.0-M1", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/micrometer-jakarta9-1.13.0-M1.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "Apache-2.0", + "spdxExpression": "Apache-2.0", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/micrometer-jakarta9-1.13.0-M1.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:micrometer-jakarta9:micrometer-jakarta9:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer-jakarta9:micrometer_jakarta9:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer_jakarta9:micrometer-jakarta9:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer_jakarta9:micrometer_jakarta9:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer:micrometer-jakarta9:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer:micrometer_jakarta9:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/io.micrometer/micrometer-jakarta9@1.13.0-M1", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/micrometer-jakarta9-1.13.0-M1.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Automatic-Module-Name", + "value": "micrometer.jakarta9" + }, + { + "key": "Bnd-LastModified", + "value": "1707769878958" + }, + { + "key": "Branch", + "value": "HEAD" + }, + { + "key": "Build-Date", + "value": "2024-02-12_20:30:25" + }, + { + "key": "Build-Date-UTC", + "value": "2024-02-12T20:30:25.305566010Z" + }, + { + "key": "Build-Host", + "value": "bea640c5c9a6" + }, + { + "key": "Build-Id", + "value": "30241" + }, + { + "key": "Build-Java-Version", + "value": "21" + }, + { + "key": "Build-Job", + "value": "deploy" + }, + { + "key": "Build-Number", + "value": "30241" + }, + { + "key": "Build-Timezone", + "value": "Etc/UTC" + }, + { + "key": "Build-Url", + "value": "https://circleci.com/gh/micrometer-metrics/micrometer/30241" + }, + { + "key": "Built-By", + "value": "circleci" + }, + { + "key": "Built-OS", + "value": "Linux" + }, + { + "key": "Built-Status", + "value": "candidate" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Bundle-Name", + "value": "micrometer-jakarta9" + }, + { + "key": "Bundle-SymbolicName", + "value": "micrometer-jakarta9" + }, + { + "key": "Bundle-Version", + "value": "1.13.0.M1" + }, + { + "key": "Change", + "value": "639c93a" + }, + { + "key": "Created-By", + "value": "21.0.2 (Eclipse Adoptium)" + }, + { + "key": "DynamicImport-Package", + "value": "jakarta.jms;version=\"3.0.0\",io.micrometer.observation;version=\"1.13.0\",io.micrometer.observation.docs;version=\"1.13.0\",io.micrometer.observation.transport;version=\"1.13.0\"" + }, + { + "key": "Export-Package", + "value": "io.micrometer.jakarta9.instrument.jms;uses:=\"io.micrometer.common,io.micrometer.common.docs,io.micrometer.common.lang,io.micrometer.observation,io.micrometer.observation.docs,io.micrometer.observation.transport,jakarta.jms\";version=\"1.13.0\"" + }, + { + "key": "Full-Change", + "value": "639c93af0d0507b4cfa0e0581146719863b691b1" + }, + { + "key": "Gradle-Version", + "value": "8.6" + }, + { + "key": "Implementation-Title", + "value": "io.micrometer#micrometer-jakarta9;1.13.0-M1" + }, + { + "key": "Implementation-Version", + "value": "1.13.0-M1" + }, + { + "key": "Import-Package", + "value": "io.micrometer.common;version=\"[1.13,2)\",io.micrometer.common.docs;version=\"[1.13,2)\",io.micrometer.common.lang;version=\"[1.13,2)\"" + }, + { + "key": "Module-Email", + "value": "tludwig@vmware.com" + }, + { + "key": "Module-Origin", + "value": "git@github.com:micrometer-metrics/micrometer.git" + }, + { + "key": "Module-Owner", + "value": "tludwig@vmware.com" + }, + { + "key": "Module-Source", + "value": "/micrometer-jakarta9" + }, + { + "key": "Require-Capability", + "value": "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\"" + }, + { + "key": "Tool", + "value": "Bnd-6.4.0.202211291949" + }, + { + "key": "X-Compile-Source-JDK", + "value": "1.8" + }, + { + "key": "X-Compile-Target-JDK", + "value": "1.8" + } + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "74087b670cad9f9883228ee2aa871f51b53f827a" + } + ] + } + }, + { + "id": "26b8a84479010ca8", + "name": "micrometer-observation", + "version": "1.13.0-M1", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/micrometer-observation-1.13.0-M1.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "Apache-2.0", + "spdxExpression": "Apache-2.0", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/micrometer-observation-1.13.0-M1.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:micrometer-observation:micrometer-observation:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer-observation:micrometer_observation:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer_observation:micrometer-observation:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer_observation:micrometer_observation:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer:micrometer-observation:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:micrometer:micrometer_observation:1.13.0-M1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/io.micrometer/micrometer-observation@1.13.0-M1", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/micrometer-observation-1.13.0-M1.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Automatic-Module-Name", + "value": "micrometer.observation" + }, + { + "key": "Bnd-LastModified", + "value": "1707769856490" + }, + { + "key": "Branch", + "value": "HEAD" + }, + { + "key": "Build-Date", + "value": "2024-02-12_20:30:25" + }, + { + "key": "Build-Date-UTC", + "value": "2024-02-12T20:30:25.426326246Z" + }, + { + "key": "Build-Host", + "value": "bea640c5c9a6" + }, + { + "key": "Build-Id", + "value": "30241" + }, + { + "key": "Build-Java-Version", + "value": "21" + }, + { + "key": "Build-Job", + "value": "deploy" + }, + { + "key": "Build-Number", + "value": "30241" + }, + { + "key": "Build-Timezone", + "value": "Etc/UTC" + }, + { + "key": "Build-Url", + "value": "https://circleci.com/gh/micrometer-metrics/micrometer/30241" + }, + { + "key": "Built-By", + "value": "circleci" + }, + { + "key": "Built-OS", + "value": "Linux" + }, + { + "key": "Built-Status", + "value": "candidate" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Bundle-Name", + "value": "micrometer-observation" + }, + { + "key": "Bundle-SymbolicName", + "value": "micrometer-observation" + }, + { + "key": "Bundle-Version", + "value": "1.13.0.M1" + }, + { + "key": "Change", + "value": "639c93a" + }, + { + "key": "Created-By", + "value": "21.0.2 (Eclipse Adoptium)" + }, + { + "key": "Export-Package", + "value": "io.micrometer.observation;uses:=\"io.micrometer.common,io.micrometer.common.lang\";version=\"1.13.0\",io.micrometer.observation.annotation;version=\"1.13.0\",io.micrometer.observation.aop;uses:=\"io.micrometer.common.lang,io.micrometer.observation,org.aspectj.lang,org.aspectj.lang.annotation\";version=\"1.13.0\",io.micrometer.observation.contextpropagation;uses:=\"io.micrometer.context,io.micrometer.observation\";version=\"1.13.0\",io.micrometer.observation.docs;uses:=\"io.micrometer.common.docs,io.micrometer.common.lang,io.micrometer.observation\";version=\"1.13.0\",io.micrometer.observation.transport;uses:=\"io.micrometer.common.lang,io.micrometer.observation\";version=\"1.13.0\"" + }, + { + "key": "Full-Change", + "value": "639c93af0d0507b4cfa0e0581146719863b691b1" + }, + { + "key": "Gradle-Version", + "value": "8.6" + }, + { + "key": "Implementation-Title", + "value": "io.micrometer#micrometer-observation;1.13.0-M1" + }, + { + "key": "Implementation-Version", + "value": "1.13.0-M1" + }, + { + "key": "Import-Package", + "value": "io.micrometer.context;resolution:=optional,org.aspectj.lang;resolution:=optional,org.aspectj.lang.annotation;resolution:=optional,org.aspectj.lang.reflect;resolution:=optional,io.micrometer.common;version=\"[1.13,2)\",io.micrometer.common.docs;version=\"[1.13,2)\",io.micrometer.common.lang;version=\"[1.13,2)\",io.micrometer.common.util;version=\"[1.13,2)\",io.micrometer.common.util.internal.logging;version=\"[1.13,2)\",io.micrometer.observation,io.micrometer.observation.annotation,io.micrometer.observation.docs" + }, + { + "key": "Module-Email", + "value": "tludwig@vmware.com" + }, + { + "key": "Module-Origin", + "value": "git@github.com:micrometer-metrics/micrometer.git" + }, + { + "key": "Module-Owner", + "value": "tludwig@vmware.com" + }, + { + "key": "Module-Source", + "value": "/micrometer-observation" + }, + { + "key": "Require-Capability", + "value": "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\"" + }, + { + "key": "Tool", + "value": "Bnd-6.4.0.202211291949" + }, + { + "key": "X-Compile-Source-JDK", + "value": "1.8" + }, + { + "key": "X-Compile-Target-JDK", + "value": "1.8" + } + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "c06e5e0f9b6edc9c0c0ac3dd46a2117ce6f16a9d" + } + ] + } + }, + { + "id": "93ed082a147d9796", + "name": "sbom-test-gradle", + "version": "0.0.1-SNAPSHOT", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:sbom-test-gradle:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom-test-gradle:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom_test_gradle:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom_test_gradle:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:JarLauncher:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:JarLauncher:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom-test-gradle:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom_test_gradle:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom-test:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom-test:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom_test:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom_test:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:JarLauncher:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:launch:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:launch:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:loader:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:loader:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom-test-gradle:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom-test-gradle:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom_test_gradle:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom_test_gradle:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom-test-gradle:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom-test:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom:sbom-test-gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom:sbom_test_gradle:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom_test:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom_test_gradle:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:JarLauncher:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:JarLauncher:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:launch:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:loader:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:JarLauncher:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom-test:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom-test:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom:JarLauncher:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom_test:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom_test:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom-test:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom_test:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:launch:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:launch:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:loader:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:loader:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:launch:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:loader:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom:launch:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom:loader:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sbom:boot:0.0.1-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.springframework.boot.loader.launch.JarLauncher/sbom-test-gradle@0.0.1-SNAPSHOT", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Main-Class", + "value": "org.springframework.boot.loader.launch.JarLauncher" + }, + { + "key": "Start-Class", + "value": "com.example.sbomtestgradle.SbomTestGradleApplication" + }, + { + "key": "Spring-Boot-Version", + "value": "3.3.0-SNAPSHOT" + }, + { + "key": "Spring-Boot-Classes", + "value": "BOOT-INF/classes/" + }, + { + "key": "Spring-Boot-Lib", + "value": "BOOT-INF/lib/" + }, + { + "key": "Spring-Boot-Classpath-Index", + "value": "BOOT-INF/classpath.idx" + }, + { + "key": "Spring-Boot-Layers-Index", + "value": "BOOT-INF/layers.idx" + }, + { + "key": "Build-Jdk-Spec", + "value": "17" + }, + { + "key": "Implementation-Title", + "value": "sbom-test-gradle" + }, + { + "key": "Implementation-Version", + "value": "0.0.1-SNAPSHOT" + }, + { + "key": "Sbom-Location", + "value": "META-INF/sbom/bom.json" + }, + { + "key": "Sbom-Format", + "value": "CycloneDX" + } + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "8ccd6688e9d8e15d18e0f10967867e5e30729a4c" + } + ] + } + }, + { + "id": "44752cfa6770756d", + "name": "slf4j-api", + "version": "2.0.11", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/slf4j-api-2.0.11.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "http://www.opensource.org/licenses/mit-license.php", + "spdxExpression": "", + "type": "declared", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/slf4j-api-2.0.11.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:slf4j-api:slf4j-api:2.0.11:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:slf4j-api:slf4j_api:2.0.11:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:slf4j_api:slf4j-api:2.0.11:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:slf4j_api:slf4j_api:2.0.11:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:slf4j:slf4j-api:2.0.11:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:slf4j:slf4j_api:2.0.11:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.slf4j/slf4j-api@2.0.11", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/slf4j-api-2.0.11.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Created-By", + "value": "Apache Maven Bundle Plugin 5.1.9" + }, + { + "key": "Build-Jdk-Spec", + "value": "21" + }, + { + "key": "Bundle-Description", + "value": "The slf4j API" + }, + { + "key": "Bundle-DocURL", + "value": "http://www.slf4j.org" + }, + { + "key": "Bundle-License", + "value": "http://www.opensource.org/licenses/mit-license.php" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Bundle-Name", + "value": "SLF4J API Module" + }, + { + "key": "Bundle-SymbolicName", + "value": "slf4j.api" + }, + { + "key": "Bundle-Vendor", + "value": "SLF4J.ORG" + }, + { + "key": "Bundle-Version", + "value": "2.0.11" + }, + { + "key": "Export-Package", + "value": "org.slf4j;uses:=\"org.slf4j.event,org.slf4j.helpers,org.slf4j.spi\";version=\"2.0.11\",org.slf4j.event;uses:=\"org.slf4j,org.slf4j.helpers\";version=\"2.0.11\",org.slf4j.helpers;uses:=\"org.slf4j,org.slf4j.event,org.slf4j.spi\";version=\"2.0.11\",org.slf4j.spi;uses:=\"org.slf4j,org.slf4j.event,org.slf4j.helpers\";version=\"2.0.11\",org.slf4j;version=\"1.7.36\",org.slf4j.helpers;version=\"1.7.36\"" + }, + { + "key": "Implementation-Title", + "value": "slf4j-api" + }, + { + "key": "Implementation-Version", + "value": "2.0.11" + }, + { + "key": "Import-Package", + "value": "org.slf4j.spi;version=\"[2.0.11,3)\"" + }, + { + "key": "Multi-Release", + "value": "true" + }, + { + "key": "Require-Capability", + "value": "osgi.extender;filter:=\"(&(osgi.extender=osgi.serviceloader.processor)(version>=1.0.0)(!(version>=2.0.0)))\",osgi.serviceloader;filter:=\"(osgi.serviceloader=org.slf4j.spi.SLF4JServiceProvider)\";osgi.serviceloader=\"org.slf4j.spi.SLF4JServiceProvider\",osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\"" + }, + { + "key": "Tool", + "value": "Bnd-6.3.1.202206071316" + }, + { + "key": "X-Compile-Source-JDK", + "value": "8" + }, + { + "key": "X-Compile-Target-JDK", + "value": "8" + } + ] + }, + "pomProperties": { + "path": "META-INF/maven/org.slf4j/slf4j-api/pom.properties", + "name": "", + "groupId": "org.slf4j", + "artifactId": "slf4j-api", + "version": "2.0.11" + }, + "digest": [ + { + "algorithm": "sha1", + "value": "ad96c3f8cf895e696dd35c2bc8e8ebe710be9e6d" + } + ] + } + }, + { + "id": "f4585c65c0a5b26a", + "name": "snakeyaml", + "version": "2.2", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/snakeyaml-2.2.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "http://www.apache.org/licenses/LICENSE-2.0.txt", + "spdxExpression": "", + "type": "declared", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/snakeyaml-2.2.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:snakeyaml:snakeyaml:2.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:yaml:snakeyaml:2.2:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.yaml/snakeyaml@2.2", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/snakeyaml-2.2.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Bnd-LastModified", + "value": "1693124775469" + }, + { + "key": "Build-Jdk-Spec", + "value": "11" + }, + { + "key": "Bundle-Description", + "value": "YAML 1.1 parser and emitter for Java" + }, + { + "key": "Bundle-License", + "value": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Bundle-Name", + "value": "SnakeYAML" + }, + { + "key": "Bundle-SymbolicName", + "value": "org.yaml.snakeyaml" + }, + { + "key": "Bundle-Version", + "value": "2.2.0" + }, + { + "key": "Created-By", + "value": "Apache Maven Bundle Plugin 5.1.8" + }, + { + "key": "Export-Package", + "value": "org.yaml.snakeyaml;version=\"2.2\",org.yaml.snakeyaml.comments;version=\"2.2\",org.yaml.snakeyaml.composer;version=\"2.2\",org.yaml.snakeyaml.constructor;version=\"2.2\",org.yaml.snakeyaml.emitter;version=\"2.2\",org.yaml.snakeyaml.env;version=\"2.2\",org.yaml.snakeyaml.error;version=\"2.2\",org.yaml.snakeyaml.events;version=\"2.2\",org.yaml.snakeyaml.extensions.compactnotation;version=\"2.2\",org.yaml.snakeyaml.inspector;version=\"2.2\",org.yaml.snakeyaml.internal;version=\"2.2\",org.yaml.snakeyaml.introspector;version=\"2.2\",org.yaml.snakeyaml.nodes;version=\"2.2\",org.yaml.snakeyaml.parser;version=\"2.2\",org.yaml.snakeyaml.reader;version=\"2.2\",org.yaml.snakeyaml.representer;version=\"2.2\",org.yaml.snakeyaml.resolver;version=\"2.2\",org.yaml.snakeyaml.scanner;version=\"2.2\",org.yaml.snakeyaml.serializer;version=\"2.2\",org.yaml.snakeyaml.tokens;version=\"2.2\",org.yaml.snakeyaml.util;version=\"2.2\"" + }, + { + "key": "Import-Package", + "value": "org.yaml.snakeyaml;version=\"[2.2,3)\",org.yaml.snakeyaml.comments;version=\"[2.2,3)\",org.yaml.snakeyaml.composer;version=\"[2.2,3)\",org.yaml.snakeyaml.emitter;version=\"[2.2,3)\",org.yaml.snakeyaml.error;version=\"[2.2,3)\",org.yaml.snakeyaml.events;version=\"[2.2,3)\",org.yaml.snakeyaml.inspector;version=\"[2.2,3)\",org.yaml.snakeyaml.internal;version=\"[2.2,3)\",org.yaml.snakeyaml.introspector;version=\"[2.2,3)\",org.yaml.snakeyaml.nodes;version=\"[2.2,3)\",org.yaml.snakeyaml.parser;version=\"[2.2,3)\",org.yaml.snakeyaml.reader;version=\"[2.2,3)\",org.yaml.snakeyaml.resolver;version=\"[2.2,3)\",org.yaml.snakeyaml.scanner;version=\"[2.2,3)\",org.yaml.snakeyaml.serializer;version=\"[2.2,3)\",org.yaml.snakeyaml.tokens;version=\"[2.2,3)\"" + }, + { + "key": "Multi-Release", + "value": "true" + }, + { + "key": "Require-Capability", + "value": "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.7))\"" + }, + { + "key": "Tool", + "value": "Bnd-6.3.1.202206071316" + } + ] + }, + "pomProperties": { + "path": "META-INF/maven/org.yaml/snakeyaml/pom.properties", + "name": "", + "groupId": "org.yaml", + "artifactId": "snakeyaml", + "version": "2.2" + }, + "digest": [ + { + "algorithm": "sha1", + "value": "3af797a25458550a16bf89acc8e4ab2b7f2bfce0" + } + ] + } + }, + { + "id": "1e7758a78bbc15ee", + "name": "spring-aop", + "version": "6.1.4-SNAPSHOT", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-aop-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "Apache-2.0", + "spdxExpression": "Apache-2.0", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-aop-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + }, + { + "value": "BSD-3-Clause", + "spdxExpression": "BSD-3-Clause", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-aop-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:springframework:spring-aop:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring_aop:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-aop:spring-aop:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-aop:spring_aop:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_aop:spring-aop:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_aop:spring_aop:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring-aop:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring_aop:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.springframework/spring-aop@6.1.4-SNAPSHOT", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-aop-6.1.4-SNAPSHOT.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Implementation-Title", + "value": "spring-aop" + }, + { + "key": "Implementation-Version", + "value": "6.1.4-SNAPSHOT" + }, + { + "key": "Automatic-Module-Name", + "value": "spring.aop" + }, + { + "key": "Created-By", + "value": "17.0.10 (Oracle Corporation)" + } + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "b02165904562fc487cde57ca75e063561d905f74" + } + ] + } + }, + { + "id": "bb7e773a923726bb", + "name": "spring-beans", + "version": "6.1.4-SNAPSHOT", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-beans-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "Apache-2.0", + "spdxExpression": "Apache-2.0", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-beans-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + }, + { + "value": "BSD-3-Clause", + "spdxExpression": "BSD-3-Clause", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-beans-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:springframework:spring-beans:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring_beans:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-beans:spring-beans:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-beans:spring_beans:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_beans:spring-beans:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_beans:spring_beans:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring-beans:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring_beans:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.springframework/spring-beans@6.1.4-SNAPSHOT", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-beans-6.1.4-SNAPSHOT.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Implementation-Title", + "value": "spring-beans" + }, + { + "key": "Implementation-Version", + "value": "6.1.4-SNAPSHOT" + }, + { + "key": "Automatic-Module-Name", + "value": "spring.beans" + }, + { + "key": "Created-By", + "value": "17.0.10 (Oracle Corporation)" + } + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "fa8be0f856958fdd33eef9e718b3a65f7130bbd2" + } + ] + } + }, + { + "id": "a11948291446c2f5", + "name": "spring-boot", + "version": "3.3.0-SNAPSHOT", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-boot-3.3.0-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "Apache-2.0", + "spdxExpression": "Apache-2.0", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-boot-3.3.0-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:springframework:spring-boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring_boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot:spring-boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot:spring_boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot:spring-boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot:spring_boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring-boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring_boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:spring-boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:spring_boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.springframework.boot/spring-boot@3.3.0-SNAPSHOT", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-boot-3.3.0-SNAPSHOT.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Automatic-Module-Name", + "value": "spring.boot" + }, + { + "key": "Build-Jdk-Spec", + "value": "17" + }, + { + "key": "Built-By", + "value": "Spring" + }, + { + "key": "Implementation-Title", + "value": "Spring Boot" + }, + { + "key": "Implementation-Version", + "value": "3.3.0-SNAPSHOT" + } + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "d882660ea3deafe921faba8b17e7d94ef9556c47" + } + ] + } + }, + { + "id": "f83d629168e25cce", + "name": "spring-boot-actuator", + "version": "3.3.0-SNAPSHOT", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-boot-actuator-3.3.0-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "Apache-2.0", + "spdxExpression": "Apache-2.0", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-boot-actuator-3.3.0-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:spring-boot-actuator:spring-boot-actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot-actuator:spring_boot_actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot_actuator:spring-boot-actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot_actuator:spring_boot_actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring-boot-actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring_boot_actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot:spring-boot-actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot:spring_boot_actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot:spring-boot-actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot:spring_boot_actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring-boot-actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring_boot_actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:spring-boot-actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:spring_boot_actuator:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot-actuator:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot_actuator:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.springframework.boot/spring-boot-actuator@3.3.0-SNAPSHOT", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-boot-actuator-3.3.0-SNAPSHOT.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Automatic-Module-Name", + "value": "spring.boot.actuator" + }, + { + "key": "Build-Jdk-Spec", + "value": "17" + }, + { + "key": "Built-By", + "value": "Spring" + }, + { + "key": "Implementation-Title", + "value": "Spring Boot Actuator" + }, + { + "key": "Implementation-Version", + "value": "3.3.0-SNAPSHOT" + } + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "d0d018780795da57afa8edae7436646bccd55722" + } + ] + } + }, + { + "id": "b8eb893518786bb8", + "name": "spring-boot-actuator-autoconfigure", + "version": "3.3.0-SNAPSHOT", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-boot-actuator-autoconfigure-3.3.0-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "Apache-2.0", + "spdxExpression": "Apache-2.0", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-boot-actuator-autoconfigure-3.3.0-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:spring-boot-actuator-autoconfigure:spring-boot-actuator-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot-actuator-autoconfigure:spring_boot_actuator_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot_actuator_autoconfigure:spring-boot-actuator-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot_actuator_autoconfigure:spring_boot_actuator_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot-actuator:spring-boot-actuator-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot-actuator:spring_boot_actuator_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot_actuator:spring-boot-actuator-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot_actuator:spring_boot_actuator_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring-boot-actuator-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring_boot_actuator_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot:spring-boot-actuator-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot:spring_boot_actuator_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot:spring-boot-actuator-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot:spring_boot_actuator_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring-boot-actuator-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring_boot_actuator_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:spring-boot-actuator-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:spring_boot_actuator_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot-actuator-autoconfigure:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot_actuator_autoconfigure:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot-actuator:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot_actuator:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.springframework.boot/spring-boot-actuator-autoconfigure@3.3.0-SNAPSHOT", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-boot-actuator-autoconfigure-3.3.0-SNAPSHOT.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Automatic-Module-Name", + "value": "spring.boot.actuator.autoconfigure" + }, + { + "key": "Build-Jdk-Spec", + "value": "17" + }, + { + "key": "Built-By", + "value": "Spring" + }, + { + "key": "Implementation-Title", + "value": "Spring Boot Actuator AutoConfigure" + }, + { + "key": "Implementation-Version", + "value": "3.3.0-SNAPSHOT" + } + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "8b8f74be822e6f2ab120ea0687acf629ef114399" + } + ] + } + }, + { + "id": "b40bdc90eb8832a3", + "name": "spring-boot-autoconfigure", + "version": "3.3.0-SNAPSHOT", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-boot-autoconfigure-3.3.0-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "Apache-2.0", + "spdxExpression": "Apache-2.0", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-boot-autoconfigure-3.3.0-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:spring-boot-autoconfigure:spring-boot-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot-autoconfigure:spring_boot_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot_autoconfigure:spring-boot-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot_autoconfigure:spring_boot_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring-boot-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring_boot_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot:spring-boot-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot:spring_boot_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot:spring-boot-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot:spring_boot_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring-boot-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring_boot_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:spring-boot-autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:spring_boot_autoconfigure:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot-autoconfigure:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot_autoconfigure:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.3.0-SNAPSHOT", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-boot-autoconfigure-3.3.0-SNAPSHOT.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Automatic-Module-Name", + "value": "spring.boot.autoconfigure" + }, + { + "key": "Build-Jdk-Spec", + "value": "17" + }, + { + "key": "Built-By", + "value": "Spring" + }, + { + "key": "Implementation-Title", + "value": "Spring Boot AutoConfigure" + }, + { + "key": "Implementation-Version", + "value": "3.3.0-SNAPSHOT" + } + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "31a960bb63af836f35760077af8ef58d24b548e3" + } + ] + } + }, + { + "id": "8069f3f866b2e657", + "name": "spring-boot-jarmode-layertools", + "version": "3.3.0-SNAPSHOT", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-boot-jarmode-layertools-3.3.0-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "Apache-2.0", + "spdxExpression": "Apache-2.0", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-boot-jarmode-layertools-3.3.0-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:spring-boot-jarmode-layertools:spring-boot-jarmode-layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot-jarmode-layertools:spring_boot_jarmode_layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot_jarmode_layertools:spring-boot-jarmode-layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot_jarmode_layertools:spring_boot_jarmode_layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot-jarmode:spring-boot-jarmode-layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot-jarmode:spring_boot_jarmode_layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot_jarmode:spring-boot-jarmode-layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot_jarmode:spring_boot_jarmode_layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring-boot-jarmode-layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring_boot_jarmode_layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot:spring-boot-jarmode-layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot:spring_boot_jarmode_layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot:spring-boot-jarmode-layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot:spring_boot_jarmode_layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring-boot-jarmode-layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring_boot_jarmode_layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:spring-boot-jarmode-layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:spring_boot_jarmode_layertools:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot-jarmode-layertools:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot_jarmode_layertools:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot-jarmode:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot_jarmode:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:boot:boot:3.3.0-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.springframework.boot/spring-boot-jarmode-layertools@3.3.0-SNAPSHOT", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-boot-jarmode-layertools-3.3.0-SNAPSHOT.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Automatic-Module-Name", + "value": "spring.boot.jarmode.layertools" + }, + { + "key": "Build-Jdk-Spec", + "value": "17" + }, + { + "key": "Built-By", + "value": "Spring" + }, + { + "key": "Implementation-Title", + "value": "Spring Boot Layers Tools" + }, + { + "key": "Implementation-Version", + "value": "3.3.0-SNAPSHOT" + } + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "d86f1782ad3d9ee047863a5023aaa22f858cd9a4" + } + ] + } + }, + { + "id": "3d5d71e0e85398af", + "name": "spring-context", + "version": "6.1.4-SNAPSHOT", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-context-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "Apache-2.0", + "spdxExpression": "Apache-2.0", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-context-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + }, + { + "value": "BSD-3-Clause", + "spdxExpression": "BSD-3-Clause", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-context-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:springframework:spring-context:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring_context:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-context:spring-context:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-context:spring_context:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_context:spring-context:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_context:spring_context:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring-context:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring_context:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.springframework/spring-context@6.1.4-SNAPSHOT", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-context-6.1.4-SNAPSHOT.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Implementation-Title", + "value": "spring-context" + }, + { + "key": "Implementation-Version", + "value": "6.1.4-SNAPSHOT" + }, + { + "key": "Automatic-Module-Name", + "value": "spring.context" + }, + { + "key": "Created-By", + "value": "17.0.10 (Oracle Corporation)" + } + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "75440f70a649ca15948af5923ebdef345848a856" + } + ] + } + }, + { + "id": "519fe54307d2d43d", + "name": "spring-core", + "version": "6.1.4-SNAPSHOT", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-core-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "Apache-2.0", + "spdxExpression": "Apache-2.0", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-core-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + }, + { + "value": "BSD-3-Clause", + "spdxExpression": "BSD-3-Clause", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-core-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:springsource-spring-framework:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource_spring_framework:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource-spring:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource_spring:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:pivotal_software:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-framework:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_framework:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource-spring-framework:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource_spring_framework:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-core:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_core:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource-spring-framework:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource-spring-framework:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource_spring_framework:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource_spring_framework:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource-spring:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource_spring:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:vmware:springsource_spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:pivotal_software:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-framework:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_framework:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource-spring:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource-spring:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource_spring:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource_spring:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:pivotal_software:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:pivotal_software:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-core:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-framework:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-framework:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_core:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_framework:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_framework:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springsource:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-core:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-core:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_core:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_core:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:vmware:spring_framework:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:vmware:spring-core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:vmware:spring_core:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.springframework/spring-core@6.1.4-SNAPSHOT", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-core-6.1.4-SNAPSHOT.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Implementation-Title", + "value": "spring-core" + }, + { + "key": "Implementation-Version", + "value": "6.1.4-SNAPSHOT" + }, + { + "key": "Automatic-Module-Name", + "value": "spring.core" + }, + { + "key": "Created-By", + "value": "17.0.10 (Oracle Corporation)" + }, + { + "key": "Multi-Release", + "value": "true" + }, + { + "key": "Dependencies", + "value": "jdk.unsupported,org.jboss.vfs" + } + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "27d0900a14e240a7311c979e7b30cf65f9de9074" + } + ] + } + }, + { + "id": "546794e924e39088", + "name": "spring-expression", + "version": "6.1.4-SNAPSHOT", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-expression-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "Apache-2.0", + "spdxExpression": "Apache-2.0", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-expression-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + }, + { + "value": "BSD-3-Clause", + "spdxExpression": "BSD-3-Clause", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-expression-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:spring-expression:spring-expression:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-expression:spring_expression:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_expression:spring-expression:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_expression:spring_expression:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring-expression:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring_expression:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring-expression:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring_expression:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.springframework/spring-expression@6.1.4-SNAPSHOT", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-expression-6.1.4-SNAPSHOT.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Implementation-Title", + "value": "spring-expression" + }, + { + "key": "Implementation-Version", + "value": "6.1.4-SNAPSHOT" + }, + { + "key": "Automatic-Module-Name", + "value": "spring.expression" + }, + { + "key": "Created-By", + "value": "17.0.10 (Oracle Corporation)" + } + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "a5d7041ca11fd188e9d17ac8a795eabed8be55e4" + } + ] + } + }, + { + "id": "173ea637a5756944", + "name": "spring-jcl", + "version": "6.1.4-SNAPSHOT", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-jcl-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "Apache-2.0", + "spdxExpression": "Apache-2.0", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-jcl-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + }, + { + "value": "BSD-3-Clause", + "spdxExpression": "BSD-3-Clause", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-jcl-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:springframework:spring-jcl:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring_jcl:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-jcl:spring-jcl:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-jcl:spring_jcl:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_jcl:spring-jcl:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_jcl:spring_jcl:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring-jcl:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring_jcl:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.springframework/spring-jcl@6.1.4-SNAPSHOT", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-jcl-6.1.4-SNAPSHOT.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Implementation-Title", + "value": "spring-jcl" + }, + { + "key": "Implementation-Version", + "value": "6.1.4-SNAPSHOT" + }, + { + "key": "Automatic-Module-Name", + "value": "spring.jcl" + }, + { + "key": "Created-By", + "value": "17.0.10 (Oracle Corporation)" + } + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "604cea28d23d8027a31c35f372d2b8d0fdec211d" + } + ] + } + }, + { + "id": "adc63cefcede34fc", + "name": "spring-web", + "version": "6.1.4-SNAPSHOT", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-web-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "Apache-2.0", + "spdxExpression": "Apache-2.0", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-web-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + }, + { + "value": "BSD-3-Clause", + "spdxExpression": "BSD-3-Clause", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-web-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:springframework:spring-web:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring_web:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-web:spring-web:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-web:spring_web:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_web:spring-web:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_web:spring_web:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring-web:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring_web:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.springframework/spring-web@6.1.4-SNAPSHOT", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-web-6.1.4-SNAPSHOT.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Implementation-Title", + "value": "spring-web" + }, + { + "key": "Implementation-Version", + "value": "6.1.4-SNAPSHOT" + }, + { + "key": "Automatic-Module-Name", + "value": "spring.web" + }, + { + "key": "Created-By", + "value": "17.0.10 (Oracle Corporation)" + } + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "c0600dcd73db226c3d121af16d6a155ecee08d30" + } + ] + } + }, + { + "id": "940aed7082581b67", + "name": "spring-webmvc", + "version": "6.1.4-SNAPSHOT", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-webmvc-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "Apache-2.0", + "spdxExpression": "Apache-2.0", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-webmvc-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + }, + { + "value": "BSD-3-Clause", + "spdxExpression": "BSD-3-Clause", + "type": "concluded", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-webmvc-6.1.4-SNAPSHOT.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:springframework:spring-webmvc:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:springframework:spring_webmvc:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-webmvc:spring-webmvc:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring-webmvc:spring_webmvc:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_webmvc:spring-webmvc:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring_webmvc:spring_webmvc:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring-webmvc:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:spring:spring_webmvc:6.1.4-SNAPSHOT:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.springframework/spring-webmvc@6.1.4-SNAPSHOT", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/spring-webmvc-6.1.4-SNAPSHOT.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Implementation-Title", + "value": "spring-webmvc" + }, + { + "key": "Implementation-Version", + "value": "6.1.4-SNAPSHOT" + }, + { + "key": "Automatic-Module-Name", + "value": "spring.webmvc" + }, + { + "key": "Created-By", + "value": "17.0.10 (Oracle Corporation)" + } + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "34a510cf565bec1c2f74f049b1730b22f877bd37" + } + ] + } + }, + { + "id": "a753aca6ee68c738", + "name": "tomcat-embed-core", + "version": "10.1.18", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/tomcat-embed-core-10.1.18.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "https://www.apache.org/licenses/LICENSE-2.0.txt", + "spdxExpression": "", + "type": "declared", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/tomcat-embed-core-10.1.18.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:apache:tomcat-embed-core:10.1.18:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:apache:tomcat_embed_core:10.1.18:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:apache:tomcat:10.1.18:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:apache:embed:10.1.18:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@10.1.18", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/tomcat-embed-core-10.1.18.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Bundle-License", + "value": "https://www.apache.org/licenses/LICENSE-2.0.txt" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Bundle-Name", + "value": "tomcat-embed-core" + }, + { + "key": "Bundle-SymbolicName", + "value": "org.apache.tomcat-embed-core" + }, + { + "key": "Bundle-Version", + "value": "10.1.18" + }, + { + "key": "Export-Package", + "value": "jakarta.security.auth.message;version=\"3.0\";uses:=\"javax.security.auth,javax.security.auth.login\",jakarta.security.auth.message.callback;version=\"3.0\";uses:=\"javax.crypto,javax.security.auth,javax.security.auth.callback,javax.security.auth.x500\",jakarta.security.auth.message.config;version=\"3.0\";uses:=\"jakarta.security.auth.message,jakarta.security.auth.message.module,javax.security.auth,javax.security.auth.callback\",jakarta.security.auth.message.module;version=\"3.0\";uses:=\"jakarta.security.auth.message,javax.security.auth.callback\",jakarta.servlet;version=\"6.0\";uses:=\"jakarta.servlet.annotation,jakarta.servlet.descriptor\",jakarta.servlet.annotation;version=\"6.0\";uses:=\"jakarta.servlet\",jakarta.servlet.descriptor;version=\"6.0\",jakarta.servlet.http;version=\"6.0\";uses:=\"jakarta.servlet\",jakarta.servlet.resources;version=\"6.0\",org.apache.catalina;uses:=\"jakarta.servlet,jakarta.servlet.descriptor,jakarta.servlet.http,javax.management,javax.naming,org.apache.catalina.connector,org.apache.catalina.deploy,org.apache.catalina.mapper,org.apache.catalina.startup,org.apache.juli.logging,org.apache.tomcat,org.apache.tomcat.util.descriptor.web,org.apache.tomcat.util.file,org.apache.tomcat.util.http,org.ietf.jgss\";version=\"10.1.18\",org.apache.catalina.authenticator;uses:=\"jakarta.security.auth.message.config,jakarta.servlet,jakarta.servlet.http,org.apache.catalina,org.apache.catalina.connector,org.apache.catalina.util,org.apache.catalina.valves,org.apache.tomcat.util.buf,org.apache.tomcat.util.descriptor.web,org.apache.tomcat.util.res,org.ietf.jgss\";version=\"10.1.18\",org.apache.catalina.authenticator.jaspic;uses:=\"jakarta.security.auth.message,jakarta.security.auth.message.config,jakarta.security.auth.message.module,jakarta.servlet.http,javax.security.auth,javax.security.auth.callback,org.apache.catalina,org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.catalina.connector;uses:=\"jakarta.servlet,jakarta.servlet.http,javax.security.auth,org.apache.catalina,org.apache.catalina.core,org.apache.catalina.mapper,org.apache.catalina.util,org.apache.coyote,org.apache.tomcat.util.buf,org.apache.tomcat.util.http,org.apache.tomcat.util.net,org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.catalina.core;uses:=\"jakarta.servlet,jakarta.servlet.descriptor,jakarta.servlet.http,javax.management,javax.naming,org.apache.catalina,org.apache.catalina.connector,org.apache.catalina.deploy,org.apache.catalina.mapper,org.apache.catalina.startup,org.apache.catalina.util,org.apache.coyote,org.apache.juli.logging,org.apache.naming,org.apache.tomcat,org.apache.tomcat.util.descriptor.web,org.apache.tomcat.util.http,org.apache.tomcat.util.http.fileupload,org.apache.tomcat.util.res,org.apache.tomcat.util.threads\";version=\"10.1.18\",org.apache.catalina.deploy;uses:=\"org.apache.catalina,org.apache.catalina.util,org.apache.tomcat.util.descriptor.web\";version=\"10.1.18\",org.apache.catalina.filters;uses:=\"jakarta.servlet,jakarta.servlet.http,org.apache.juli.logging,org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.catalina.loader;uses:=\"org.apache.catalina,org.apache.catalina.util,org.apache.juli,org.apache.tomcat,org.apache.tomcat.util.res,org.apache.tomcat.util.security\";version=\"10.1.18\",org.apache.catalina.manager;uses:=\"jakarta.servlet,jakarta.servlet.http,javax.management,javax.naming,org.apache.catalina,org.apache.catalina.util,org.apache.tomcat.util.modeler,org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.catalina.manager.host;uses:=\"jakarta.servlet,jakarta.servlet.http,org.apache.catalina,org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.catalina.manager.util;uses:=\"jakarta.servlet.http,org.apache.catalina\";version=\"10.1.18\",org.apache.catalina.mapper;uses:=\"jakarta.servlet.http,org.apache.catalina,org.apache.catalina.util,org.apache.tomcat.util.buf\";version=\"10.1.18\",org.apache.catalina.mbeans;uses:=\"javax.management,javax.naming,org.apache.catalina,org.apache.catalina.connector,org.apache.catalina.core,org.apache.tomcat.util.descriptor.web,org.apache.tomcat.util.modeler,org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.catalina.realm;uses:=\"javax.management,javax.naming,javax.naming.directory,javax.net.ssl,javax.security.auth,javax.security.auth.callback,javax.security.auth.login,javax.security.auth.spi,org.apache.catalina,org.apache.catalina.connector,org.apache.catalina.util,org.apache.juli.logging,org.apache.tomcat.util.collections,org.apache.tomcat.util.descriptor.web,org.apache.tomcat.util.digester,org.apache.tomcat.util.res,org.ietf.jgss\";version=\"10.1.18\",org.apache.catalina.security;uses:=\"jakarta.servlet,org.apache.catalina\";version=\"10.1.18\",org.apache.catalina.servlets;uses:=\"jakarta.servlet,jakarta.servlet.http,javax.xml.parsers,javax.xml.transform,org.apache.catalina,org.apache.tomcat.util.http,org.apache.tomcat.util.http.parser,org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.catalina.session;uses:=\"jakarta.servlet,jakarta.servlet.http,javax.sql,org.apache.catalina,org.apache.catalina.util,org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.catalina.startup;uses:=\"jakarta.annotation,jakarta.servlet,jakarta.servlet.descriptor,javax.management,org.apache.catalina,org.apache.catalina.connector,org.apache.catalina.core,org.apache.catalina.deploy,org.apache.catalina.util,org.apache.juli.logging,org.apache.tomcat,org.apache.tomcat.util.bcel.classfile,org.apache.tomcat.util.descriptor.web,org.apache.tomcat.util.digester,org.apache.tomcat.util.file,org.apache.tomcat.util.http,org.apache.tomcat.util.res,org.xml.sax\";version=\"10.1.18\",org.apache.catalina.users;uses:=\"javax.naming,javax.naming.spi,javax.sql,org.apache.catalina\";version=\"10.1.18\",org.apache.catalina.util;uses:=\"jakarta.servlet.http,javax.management,org.apache.catalina,org.apache.juli.logging,org.apache.tomcat.util.descriptor.web,org.w3c.dom\";version=\"10.1.18\",org.apache.catalina.valves;uses:=\"jakarta.servlet,org.apache.catalina,org.apache.catalina.connector,org.apache.catalina.util,org.apache.juli.logging,org.apache.tomcat.util.descriptor.web,org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.catalina.valves.rewrite;uses:=\"jakarta.servlet,org.apache.catalina,org.apache.catalina.connector,org.apache.catalina.valves,org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.catalina.webresources;uses:=\"org.apache.catalina,org.apache.catalina.util,org.apache.juli.logging,org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.catalina.webresources.war;version=\"10.1.18\",org.apache.coyote;uses:=\"jakarta.servlet,jakarta.servlet.http,javax.management,org.apache.coyote.http11,org.apache.coyote.http11.upgrade,org.apache.juli.logging,org.apache.tomcat,org.apache.tomcat.util.buf,org.apache.tomcat.util.collections,org.apache.tomcat.util.http,org.apache.tomcat.util.log,org.apache.tomcat.util.modeler,org.apache.tomcat.util.net\";version=\"10.1.18\",org.apache.coyote.ajp;uses:=\"jakarta.servlet,org.apache.coyote,org.apache.juli.logging,org.apache.tomcat.util.buf,org.apache.tomcat.util.net,org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.coyote.http11;uses:=\"jakarta.servlet,javax.management,org.apache.coyote,org.apache.coyote.http11.upgrade,org.apache.juli.logging,org.apache.tomcat.util.buf,org.apache.tomcat.util.http.parser,org.apache.tomcat.util.net,org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.coyote.http11.filters;uses:=\"org.apache.coyote,org.apache.coyote.http11,org.apache.juli.logging,org.apache.tomcat.util.buf,org.apache.tomcat.util.net\";version=\"10.1.18\",org.apache.coyote.http11.upgrade;uses:=\"jakarta.servlet,jakarta.servlet.http,org.apache.coyote,org.apache.juli.logging,org.apache.tomcat.util.modeler,org.apache.tomcat.util.net\";version=\"10.1.18\",org.apache.coyote.http2;uses:=\"jakarta.servlet,jakarta.servlet.http,org.apache.coyote,org.apache.coyote.http11,org.apache.coyote.http11.upgrade,org.apache.tomcat.util.http.parser,org.apache.tomcat.util.net,org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.juli;version=\"10.1.18\",org.apache.juli.logging;version=\"10.1.18\",org.apache.naming;uses:=\"javax.naming\";version=\"10.1.18\",org.apache.naming.factory;uses:=\"javax.naming,javax.naming.spi,javax.sql,org.apache.naming\";version=\"10.1.18\",org.apache.naming.java;uses:=\"javax.naming,javax.naming.spi\";version=\"10.1.18\",org.apache.tomcat;uses:=\"jakarta.servlet,javax.naming\";version=\"10.1.18\",org.apache.tomcat.jni;version=\"10.1.18\",org.apache.tomcat.util;uses:=\"org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.tomcat.util.bcel.classfile;version=\"10.1.18\",org.apache.tomcat.util.buf;uses:=\"org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.tomcat.util.codec.binary;uses:=\"org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.tomcat.util.collections;version=\"10.1.18\",org.apache.tomcat.util.compat;version=\"10.1.18\",org.apache.tomcat.util.descriptor;uses:=\"org.apache.juli.logging,org.apache.tomcat.util.digester,org.xml.sax,org.xml.sax.ext\";version=\"10.1.18\",org.apache.tomcat.util.descriptor.tagplugin;uses:=\"jakarta.servlet,org.xml.sax\";version=\"10.1.18\",org.apache.tomcat.util.descriptor.web;uses:=\"jakarta.servlet,jakarta.servlet.descriptor,org.apache.juli.logging,org.apache.tomcat,org.apache.tomcat.util.digester,org.apache.tomcat.util.res,org.xml.sax\";version=\"10.1.18\",org.apache.tomcat.util.digester;uses:=\"javax.xml.parsers,org.apache.juli.logging,org.apache.tomcat.util,org.apache.tomcat.util.res,org.xml.sax,org.xml.sax.ext\";version=\"10.1.18\",org.apache.tomcat.util.file;version=\"10.1.18\",org.apache.tomcat.util.http;uses:=\"jakarta.servlet.http,org.apache.tomcat.util.buf\";version=\"10.1.18\",org.apache.tomcat.util.http.fileupload;uses:=\"org.apache.tomcat.util.http.fileupload.impl,org.apache.tomcat.util.http.fileupload.util\";version=\"10.1.18\",org.apache.tomcat.util.http.fileupload.disk;uses:=\"org.apache.tomcat.util.http.fileupload\";version=\"10.1.18\",org.apache.tomcat.util.http.fileupload.impl;uses:=\"org.apache.tomcat.util.http.fileupload\";version=\"10.1.18\",org.apache.tomcat.util.http.fileupload.servlet;uses:=\"jakarta.servlet.http,org.apache.tomcat.util.http.fileupload\";version=\"10.1.18\",org.apache.tomcat.util.http.fileupload.util;uses:=\"org.apache.tomcat.util.http.fileupload\";version=\"10.1.18\",org.apache.tomcat.util.http.parser;uses:=\"org.apache.tomcat.util.buf,org.apache.tomcat.util.http\";version=\"10.1.18\",org.apache.tomcat.util.log;uses:=\"org.apache.juli.logging\";version=\"10.1.18\",org.apache.tomcat.util.modeler;uses:=\"javax.management,javax.management.modelmbean\";version=\"10.1.18\",org.apache.tomcat.util.modeler.modules;uses:=\"javax.management,org.apache.tomcat.util.modeler,org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.tomcat.util.net;uses:=\"jakarta.servlet,javax.management,javax.net.ssl,org.apache.juli.logging,org.apache.tomcat.util.collections,org.apache.tomcat.util.net.openssl,org.apache.tomcat.util.net.openssl.ciphers,org.apache.tomcat.util.res,org.apache.tomcat.util.threads\";version=\"10.1.18\",org.apache.tomcat.util.net.openssl;uses:=\"javax.net.ssl,org.apache.juli.logging,org.apache.tomcat.util.net\";version=\"10.1.18\",org.apache.tomcat.util.net.openssl.ciphers;version=\"10.1.18\",org.apache.tomcat.util.res;version=\"10.1.18\",org.apache.tomcat.util.scan;uses:=\"jakarta.servlet,org.apache.tomcat\";version=\"10.1.18\",org.apache.tomcat.util.security;version=\"10.1.18\",org.apache.tomcat.util.threads;uses:=\"org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.catalina.ssi;version=\"10.1.18\"" + }, + { + "key": "Implementation-Title", + "value": "Apache Tomcat" + }, + { + "key": "Implementation-Vendor", + "value": "Apache Software Foundation" + }, + { + "key": "Implementation-Version", + "value": "10.1.18" + }, + { + "key": "Import-Package", + "value": "jakarta.annotation,jakarta.annotation.security,jakarta.ejb,jakarta.mail,jakarta.mail.internet,jakarta.persistence,jakarta.security.auth.message;version=\"[3.0,4)\",jakarta.security.auth.message.callback;version=\"[3.0,4)\",jakarta.security.auth.message.config;version=\"[3.0,4)\",jakarta.security.auth.message.module;version=\"[3.0,4)\",jakarta.servlet;version=\"[6.0,7)\",jakarta.servlet.annotation;version=\"[6.0,7)\",jakarta.servlet.descriptor;version=\"[6.0,7)\",jakarta.servlet.http;version=\"[6.0,7)\",jakarta.xml.ws,java.beans,java.io,java.lang,java.lang.annotation,java.lang.instrument,java.lang.invoke,java.lang.management,java.lang.module,java.lang.ref,java.lang.reflect,java.math,java.net,java.nio,java.nio.channels,java.nio.charset,java.nio.file,java.nio.file.attribute,java.rmi,java.security,java.security.cert,java.security.spec,java.sql,java.text,java.time,java.time.chrono,java.time.format,java.time.temporal,java.util,java.util.concurrent,java.util.concurrent.atomic,java.util.concurrent.locks,java.util.function,java.util.jar,java.util.logging,java.util.regex,java.util.stream,java.util.zip,javax.crypto,javax.crypto.spec,javax.imageio,javax.management,javax.management.loading,javax.management.modelmbean,javax.management.openmbean,javax.naming,javax.naming.directory,javax.naming.ldap,javax.naming.spi,javax.net.ssl,javax.security.auth,javax.security.auth.callback,javax.security.auth.login,javax.security.auth.spi,javax.security.auth.x500,javax.security.cert,javax.sql,javax.wsdl,javax.wsdl.extensions,javax.wsdl.extensions.soap,javax.wsdl.factory,javax.wsdl.xml,javax.xml.namespace,javax.xml.parsers,javax.xml.rpc,javax.xml.rpc.handler,javax.xml.transform,javax.xml.transform.dom,javax.xml.transform.stream,org.apache.catalina,org.apache.catalina.authenticator,org.apache.catalina.authenticator.jaspic,org.apache.catalina.connector,org.apache.catalina.core,org.apache.catalina.deploy,org.apache.catalina.filters,org.apache.catalina.loader,org.apache.catalina.manager.util,org.apache.catalina.mapper,org.apache.catalina.mbeans,org.apache.catalina.realm,org.apache.catalina.security,org.apache.catalina.session,org.apache.catalina.startup,org.apache.catalina.util,org.apache.catalina.webresources,org.apache.catalina.webresources.war,org.apache.coyote,org.apache.coyote.ajp,org.apache.coyote.http11,org.apache.coyote.http11.filters,org.apache.coyote.http11.upgrade,org.apache.juli,org.apache.juli.logging,org.apache.naming,org.apache.naming.factory,org.apache.tomcat,org.apache.tomcat.jakartaee,org.apache.tomcat.jni,org.apache.tomcat.util,org.apache.tomcat.util.buf,org.apache.tomcat.util.codec.binary,org.apache.tomcat.util.collections,org.apache.tomcat.util.compat,org.apache.tomcat.util.descriptor,org.apache.tomcat.util.descriptor.web,org.apache.tomcat.util.digester,org.apache.tomcat.util.file,org.apache.tomcat.util.http,org.apache.tomcat.util.http.fileupload.disk,org.apache.tomcat.util.http.fileupload.impl,org.apache.tomcat.util.http.fileupload.servlet,org.apache.tomcat.util.http.fileupload.util,org.apache.tomcat.util.http.parser,org.apache.tomcat.util.log,org.apache.tomcat.util.modeler,org.apache.tomcat.util.modeler.modules,org.apache.tomcat.util.net.openssl.ciphers,org.apache.tomcat.util.res,org.apache.tomcat.util.scan,org.apache.tomcat.util.security,org.apache.tomcat.util.threads,org.ietf.jgss,org.w3c.dom,org.xml.sax,org.xml.sax.ext,org.xml.sax.helpers" + }, + { + "key": "Private-Package", + "value": "org.apache.naming.factory.webservices,org.apache.tomcat.util.bcel,org.apache.tomcat.util.http.fileupload.util.mime,org.apache.tomcat.util.json,org.apache.tomcat.util.net.jsse" + }, + { + "key": "Provide-Capability", + "value": "osgi.contract;osgi.contract=JavaJASPIC;version:Version=\"3.0\";uses:=\"jakarta.security.auth.message,jakarta.security.auth.message.callback,jakarta.security.auth.message.config,jakarta.security.auth.message.module\",osgi.contract;osgi.contract=JavaServlet;version:Version=\"6.0\";uses:=\"jakarta.servlet,jakarta.servlet.annotation,jakarta.servlet.descriptor,jakarta.servlet.http,jakarta.servlet.resources\"" + }, + { + "key": "Require-Capability", + "value": "osgi.extender;filter:=\"(&(osgi.extender=osgi.serviceloader.processor)(version>=1.0.0)(!(version>=2.0.0)))\",osgi.serviceloader;filter:=\"(osgi.serviceloader=org.apache.juli.logging.Log)\";osgi.serviceloader=\"org.apache.juli.logging.Log\",osgi.contract;osgi.contract=JakartaAnnotations;filter:=\"(&(osgi.contract=JakartaAnnotations)(version=2.1.0))\",osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=11))\"" + }, + { + "key": "Specification-Title", + "value": "Apache Tomcat" + }, + { + "key": "Specification-Vendor", + "value": "Apache Software Foundation" + }, + { + "key": "Specification-Version", + "value": "10.1" + }, + { + "key": "X-Compile-Source-JDK", + "value": "11" + }, + { + "key": "X-Compile-Target-JDK", + "value": "11" + } + ], + "sections": [ + [ + { + "key": "Name", + "value": "jakarta/security/auth/message/" + }, + { + "key": "Implementation-Title", + "value": "jakarta.security.auth.message" + }, + { + "key": "Implementation-Vendor", + "value": "Apache Software Foundation" + }, + { + "key": "Implementation-Version", + "value": "3.0" + }, + { + "key": "Specification-Title", + "value": "Jakarta Authentication SPI for Containers" + }, + { + "key": "Specification-Vendor", + "value": "Eclipse Foundation" + }, + { + "key": "Specification-Version", + "value": "3.0" + } + ], + [ + { + "key": "Name", + "value": "jakarta/security/auth/message/callback/" + }, + { + "key": "Implementation-Title", + "value": "jakarta.security.auth.message" + }, + { + "key": "Implementation-Vendor", + "value": "Apache Software Foundation" + }, + { + "key": "Implementation-Version", + "value": "3.0" + }, + { + "key": "Specification-Title", + "value": "Jakarta Authentication SPI for Containers" + }, + { + "key": "Specification-Vendor", + "value": "Eclipse Foundation" + }, + { + "key": "Specification-Version", + "value": "3.0" + } + ], + [ + { + "key": "Name", + "value": "jakarta/security/auth/message/config/" + }, + { + "key": "Implementation-Title", + "value": "jakarta.security.auth.message" + }, + { + "key": "Implementation-Vendor", + "value": "Apache Software Foundation" + }, + { + "key": "Implementation-Version", + "value": "3.0" + }, + { + "key": "Specification-Title", + "value": "Jakarta Authentication SPI for Containers" + }, + { + "key": "Specification-Vendor", + "value": "Eclipse Foundation" + }, + { + "key": "Specification-Version", + "value": "3.0" + } + ], + [ + { + "key": "Name", + "value": "jakarta/security/auth/message/module/" + }, + { + "key": "Implementation-Title", + "value": "jakarta.security.auth.message" + }, + { + "key": "Implementation-Vendor", + "value": "Apache Software Foundation" + }, + { + "key": "Implementation-Version", + "value": "3.0" + }, + { + "key": "Specification-Title", + "value": "Jakarta Authentication SPI for Containers" + }, + { + "key": "Specification-Vendor", + "value": "Eclipse Foundation" + }, + { + "key": "Specification-Version", + "value": "3.0" + } + ], + [ + { + "key": "Name", + "value": "jakarta/servlet/" + }, + { + "key": "Implementation-Title", + "value": "jakarta.servlet" + }, + { + "key": "Implementation-Vendor", + "value": "Apache Software Foundation" + }, + { + "key": "Implementation-Version", + "value": "6.0" + }, + { + "key": "Specification-Title", + "value": "Jakarta Servlet" + }, + { + "key": "Specification-Vendor", + "value": "Eclipse Foundation" + }, + { + "key": "Specification-Version", + "value": "6.0" + } + ], + [ + { + "key": "Name", + "value": "jakarta/servlet/annotation/" + }, + { + "key": "Implementation-Title", + "value": "jakarta.servlet" + }, + { + "key": "Implementation-Vendor", + "value": "Apache Software Foundation" + }, + { + "key": "Implementation-Version", + "value": "6.0" + }, + { + "key": "Specification-Title", + "value": "Jakarta Servlet" + }, + { + "key": "Specification-Vendor", + "value": "Eclipse Foundation" + }, + { + "key": "Specification-Version", + "value": "6.0" + } + ], + [ + { + "key": "Name", + "value": "jakarta/servlet/descriptor/" + }, + { + "key": "Implementation-Title", + "value": "jakarta.servlet" + }, + { + "key": "Implementation-Vendor", + "value": "Apache Software Foundation" + }, + { + "key": "Implementation-Version", + "value": "6.0" + }, + { + "key": "Specification-Title", + "value": "Jakarta Servlet" + }, + { + "key": "Specification-Vendor", + "value": "Eclipse Foundation" + }, + { + "key": "Specification-Version", + "value": "6.0" + } + ], + [ + { + "key": "Name", + "value": "jakarta/servlet/http/" + }, + { + "key": "Implementation-Title", + "value": "jakarta.servlet" + }, + { + "key": "Implementation-Vendor", + "value": "Apache Software Foundation" + }, + { + "key": "Implementation-Version", + "value": "6.0" + }, + { + "key": "Specification-Title", + "value": "Jakarta Servlet" + }, + { + "key": "Specification-Vendor", + "value": "Eclipse Foundation" + }, + { + "key": "Specification-Version", + "value": "6.0" + } + ], + [ + { + "key": "Name", + "value": "jakarta/servlet/resources/" + }, + { + "key": "Implementation-Title", + "value": "jakarta.servlet" + }, + { + "key": "Implementation-Vendor", + "value": "Apache Software Foundation" + }, + { + "key": "Implementation-Version", + "value": "6.0" + }, + { + "key": "Specification-Title", + "value": "Jakarta Servlet" + }, + { + "key": "Specification-Vendor", + "value": "Eclipse Foundation" + }, + { + "key": "Specification-Version", + "value": "6.0" + } + ] + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "bff6c34649d1dd7b509e819794d73ba795947dcf" + } + ] + } + }, + { + "id": "7a59d22722f7701b", + "name": "tomcat-embed-el", + "version": "10.1.18", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/tomcat-embed-el-10.1.18.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "https://www.apache.org/licenses/LICENSE-2.0.txt", + "spdxExpression": "", + "type": "declared", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/tomcat-embed-el-10.1.18.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:apache:tomcat-embed-el:10.1.18:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:apache:tomcat_embed_el:10.1.18:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:apache:tomcat:10.1.18:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:apache:embed:10.1.18:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.apache.tomcat.embed/tomcat-embed-el@10.1.18", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/tomcat-embed-el-10.1.18.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Bundle-License", + "value": "https://www.apache.org/licenses/LICENSE-2.0.txt" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Bundle-Name", + "value": "tomcat-embed-jasper-el" + }, + { + "key": "Bundle-SymbolicName", + "value": "org.apache.tomcat-embed-jasper-el" + }, + { + "key": "Bundle-Version", + "value": "10.1.18" + }, + { + "key": "Export-Package", + "value": "jakarta.el;version=\"5.0\",org.apache.el;uses:=\"jakarta.el,org.apache.el.parser\";version=\"10.1.18\",org.apache.el.lang;uses:=\"jakarta.el,org.apache.el.parser\";version=\"10.1.18\",org.apache.el.parser;uses:=\"jakarta.el,org.apache.el.lang\";version=\"10.1.18\"" + }, + { + "key": "Implementation-Title", + "value": "Apache Tomcat" + }, + { + "key": "Implementation-Vendor", + "value": "Apache Software Foundation" + }, + { + "key": "Implementation-Version", + "value": "10.1.18" + }, + { + "key": "Import-Package", + "value": "jakarta.el;version=\"[5.0,6)\",java.beans,java.io,java.lang,java.lang.annotation,java.lang.invoke,java.lang.ref,java.lang.reflect,java.math,java.security,java.text,java.util,java.util.concurrent,java.util.concurrent.locks,java.util.function" + }, + { + "key": "Private-Package", + "value": "org.apache.el.stream,org.apache.el.util" + }, + { + "key": "Provide-Capability", + "value": "osgi.contract;osgi.contract=JakartaExpressionLanguage;version:Version=\"5.0\";uses:=\"jakarta.el\",osgi.service;objectClass:List=\"jakarta.el.ExpressionFactory\";effective:=active,osgi.serviceloader;osgi.serviceloader=\"jakarta.el.ExpressionFactory\";register:=\"org.apache.el.ExpressionFactoryImpl\"" + }, + { + "key": "Require-Capability", + "value": "osgi.extender;filter:=\"(&(osgi.extender=osgi.serviceloader.processor)(version>=1.0.0)(!(version>=2.0.0)))\",osgi.serviceloader;filter:=\"(osgi.serviceloader=jakarta.el.ExpressionFactory)\";osgi.serviceloader=\"jakarta.el.ExpressionFactory\",osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\",osgi.extender;filter:=\"(&(osgi.extender=osgi.serviceloader.registrar)(version>=1.0.0)(!(version>=2.0.0)))\"" + }, + { + "key": "Specification-Title", + "value": "Apache Tomcat" + }, + { + "key": "Specification-Vendor", + "value": "Apache Software Foundation" + }, + { + "key": "Specification-Version", + "value": "10.1" + }, + { + "key": "X-Compile-Source-JDK", + "value": "11" + }, + { + "key": "X-Compile-Target-JDK", + "value": "11" + } + ], + "sections": [ + [ + { + "key": "Name", + "value": "jakarta/el/" + }, + { + "key": "Implementation-Title", + "value": "jakarta.annotation" + }, + { + "key": "Implementation-Vendor", + "value": "Apache Software Foundation" + }, + { + "key": "Implementation-Version", + "value": "5.0" + }, + { + "key": "Specification-Title", + "value": "Jakarta Expression Language" + }, + { + "key": "Specification-Vendor", + "value": "Eclipse Foundation" + }, + { + "key": "Specification-Version", + "value": "5.0" + } + ] + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "b2c4dc05abd363c63b245523bb071727aa2f1046" + } + ] + } + }, + { + "id": "6c04f8ee22f9157e", + "name": "tomcat-embed-websocket", + "version": "10.1.18", + "type": "java-archive", + "foundBy": "java-archive-cataloger", + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/tomcat-embed-websocket-10.1.18.jar", + "annotations": { + "evidence": "primary" + } + } + ], + "licenses": [ + { + "value": "https://www.apache.org/licenses/LICENSE-2.0.txt", + "spdxExpression": "", + "type": "declared", + "urls": [], + "locations": [ + { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "accessPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/tomcat-embed-websocket-10.1.18.jar", + "annotations": { + "evidence": "primary" + } + } + ] + } + ], + "language": "java", + "cpes": [ + { + "cpe": "cpe:2.3:a:apache:tomcat-embed-websocket:10.1.18:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:apache:tomcat_embed_websocket:10.1.18:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:apache:tomcat:10.1.18:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:apache:embed:10.1.18:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "purl": "pkg:maven/org.apache.tomcat.embed/tomcat-embed-websocket@10.1.18", + "metadataType": "java-archive", + "metadata": { + "virtualPath": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar:BOOT-INF/lib/tomcat-embed-websocket-10.1.18.jar", + "manifest": { + "main": [ + { + "key": "Manifest-Version", + "value": "1.0" + }, + { + "key": "Bundle-License", + "value": "https://www.apache.org/licenses/LICENSE-2.0.txt" + }, + { + "key": "Bundle-ManifestVersion", + "value": "2" + }, + { + "key": "Bundle-Name", + "value": "tomcat-embed-websocket" + }, + { + "key": "Bundle-SymbolicName", + "value": "org.apache.tomcat-embed-websocket" + }, + { + "key": "Bundle-Version", + "value": "10.1.18" + }, + { + "key": "Export-Package", + "value": "jakarta.websocket;version=\"2.1\";uses:=\"javax.net.ssl\",jakarta.websocket.server;version=\"2.1\";uses:=\"jakarta.websocket\",org.apache.tomcat.websocket;uses:=\"jakarta.websocket,jakarta.websocket.server,javax.net.ssl,org.apache.juli.logging,org.apache.tomcat,org.apache.tomcat.util.res\";version=\"10.1.18\",org.apache.tomcat.websocket.server;uses:=\"jakarta.servlet,jakarta.servlet.annotation,jakarta.servlet.http,jakarta.websocket,jakarta.websocket.server,org.apache.coyote.http11.upgrade,org.apache.juli.logging,org.apache.tomcat,org.apache.tomcat.util.net,org.apache.tomcat.websocket\";version=\"10.1.18\"" + }, + { + "key": "Implementation-Title", + "value": "Apache Tomcat" + }, + { + "key": "Implementation-Vendor", + "value": "Apache Software Foundation" + }, + { + "key": "Implementation-Version", + "value": "10.1.18" + }, + { + "key": "Import-Package", + "value": "jakarta.servlet,jakarta.servlet.annotation,jakarta.servlet.http,jakarta.websocket;version=\"[2.1,3)\",jakarta.websocket.server;version=\"[2.1,3)\",java.io,java.lang,java.lang.annotation,java.lang.invoke,java.lang.reflect,java.net,java.nio,java.nio.channels,java.nio.charset,java.security,java.util,java.util.concurrent,java.util.concurrent.atomic,java.util.concurrent.locks,java.util.function,java.util.regex,java.util.zip,javax.naming,javax.net.ssl,org.apache.coyote.http11.upgrade;version=\"[10.1,11)\",org.apache.juli.logging;version=\"[10.1,11)\",org.apache.tomcat;version=\"[10.1,11)\",org.apache.tomcat.util;version=\"[10.1,11)\",org.apache.tomcat.util.buf;version=\"[10.1,11)\",org.apache.tomcat.util.codec.binary;version=\"[10.1,11)\",org.apache.tomcat.util.collections;version=\"[10.1,11)\",org.apache.tomcat.util.net;version=\"[10.1,11)\",org.apache.tomcat.util.res;version=\"[10.1,11)\",org.apache.tomcat.util.security;version=\"[10.1,11)\",org.apache.tomcat.util.threads;version=\"[10.1,11)\"" + }, + { + "key": "Private-Package", + "value": "org.apache.tomcat.websocket.pojo" + }, + { + "key": "Provide-Capability", + "value": "osgi.contract;osgi.contract=JavaWebSockets;version:Version=\"2.1\";uses:=\"jakarta.websocket,jakarta.websocket.server\",osgi.service;objectClass:List=\"jakarta.websocket.ContainerProvider\";effective:=active,osgi.service;objectClass:List=\"jakarta.websocket.server.ServerEndpointConfig$Configurator\";effective:=active,osgi.serviceloader;osgi.serviceloader=\"jakarta.websocket.ContainerProvider\";register:=\"org.apache.tomcat.websocket.WsContainerProvider\",osgi.serviceloader;osgi.serviceloader=\"jakarta.websocket.server.ServerEndpointConfig$Configurator\";register:=\"org.apache.tomcat.websocket.server.DefaultServerEndpointConfigurator\"" + }, + { + "key": "Require-Capability", + "value": "osgi.extender;filter:=\"(&(osgi.extender=osgi.serviceloader.processor)(version>=1.0.0)(!(version>=2.0.0)))\",osgi.serviceloader;filter:=\"(osgi.serviceloader=jakarta.websocket.ContainerProvider)\";osgi.serviceloader=\"jakarta.websocket.ContainerProvider\",osgi.serviceloader;filter:=\"(osgi.serviceloader=jakarta.websocket.server.ServerEndpointConfig$Configurator)\";osgi.serviceloader=\"jakarta.websocket.server.ServerEndpointConfig$Configurator\",osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\",osgi.extender;filter:=\"(&(osgi.extender=osgi.serviceloader.registrar)(version>=1.0.0)(!(version>=2.0.0)))\",osgi.contract;osgi.contract=JavaServlet;filter:=\"(&(osgi.contract=JavaServlet)(version=6.0.0))\"" + }, + { + "key": "Specification-Title", + "value": "Apache Tomcat" + }, + { + "key": "Specification-Vendor", + "value": "Apache Software Foundation" + }, + { + "key": "Specification-Version", + "value": "10.1" + }, + { + "key": "X-Compile-Source-JDK", + "value": "11" + }, + { + "key": "X-Compile-Target-JDK", + "value": "11" + } + ], + "sections": [ + [ + { + "key": "Name", + "value": "jakarta/websocket/" + }, + { + "key": "Implementation-Title", + "value": "jakarta.websocket" + }, + { + "key": "Implementation-Vendor", + "value": "Apache Software Foundation" + }, + { + "key": "Implementation-Version", + "value": "2.1" + }, + { + "key": "Specification-Title", + "value": "Jakarta WebSocket" + }, + { + "key": "Specification-Vendor", + "value": "Eclipse Foundation" + }, + { + "key": "Specification-Version", + "value": "2.1" + } + ], + [ + { + "key": "Name", + "value": "jakarta/websocket/server/" + }, + { + "key": "Implementation-Title", + "value": "jakarta.websocket" + }, + { + "key": "Implementation-Vendor", + "value": "Apache Software Foundation" + }, + { + "key": "Implementation-Version", + "value": "2.1" + }, + { + "key": "Specification-Title", + "value": "Jakarta WebSocket" + }, + { + "key": "Specification-Vendor", + "value": "Eclipse Foundation" + }, + { + "key": "Specification-Version", + "value": "2.1" + } + ] + ] + }, + "digest": [ + { + "algorithm": "sha1", + "value": "83a3bc6898f2ceed2357ba231a5e83dc2016d454" + } + ] + } + } + ], + "artifactRelationships": [ + { + "parent": "0408f25059f495c5", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "1347581c05f302c0", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "173ea637a5756944", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "1e7758a78bbc15ee", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "26b8a84479010ca8", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "2c7953c2c68ec3bc", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "3748310e1aac44ea", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "3c0d8567351e2ae4", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "3d5d71e0e85398af", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "44752cfa6770756d", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "519fe54307d2d43d", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "546794e924e39088", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "598311f4a5b2a501", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "6c04f8ee22f9157e", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "77a5bf527533d628", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "7a59d22722f7701b", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "8069f3f866b2e657", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "846731ed2e85561c", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "860f45be6a175d16", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "93ed082a147d9796", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "940aed7082581b67", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "9ad3756f611d1ed2", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "a11948291446c2f5", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "a753aca6ee68c738", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "adc63cefcede34fc", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "b40bdc90eb8832a3", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "b8eb893518786bb8", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "bb7e773a923726bb", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "c1e7975b6f55f7e8", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "c404b33d3a8ce0d8", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "c46f369578c77c43", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "0408f25059f495c5", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "1347581c05f302c0", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "173ea637a5756944", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "1e7758a78bbc15ee", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "26b8a84479010ca8", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "2c7953c2c68ec3bc", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "3748310e1aac44ea", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "3c0d8567351e2ae4", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "3d5d71e0e85398af", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "44752cfa6770756d", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "519fe54307d2d43d", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "546794e924e39088", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "598311f4a5b2a501", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "6c04f8ee22f9157e", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "77a5bf527533d628", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "7a59d22722f7701b", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "8069f3f866b2e657", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "846731ed2e85561c", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "860f45be6a175d16", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "93ed082a147d9796", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "940aed7082581b67", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "9ad3756f611d1ed2", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "a11948291446c2f5", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "a753aca6ee68c738", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "adc63cefcede34fc", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "b40bdc90eb8832a3", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "b8eb893518786bb8", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "bb7e773a923726bb", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "c1e7975b6f55f7e8", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "c404b33d3a8ce0d8", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "c46f369578c77c43", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "d91fe3ae6bb15cad", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "f4585c65c0a5b26a", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "f4ea2c844b65a026", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "f5bca9d628ab321f", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "f83d629168e25cce", + "type": "contains" + }, + { + "parent": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "child": "f9418986cc24a153", + "type": "contains" + }, + { + "parent": "d91fe3ae6bb15cad", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "f4585c65c0a5b26a", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "f4ea2c844b65a026", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "f5bca9d628ab321f", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "f83d629168e25cce", + "child": "af7261c65fbd5345", + "type": "evident-by" + }, + { + "parent": "f9418986cc24a153", + "child": "af7261c65fbd5345", + "type": "evident-by" + } + ], + "files": [ + { + "id": "af7261c65fbd5345", + "location": { + "path": "/sbom-test-gradle-0.0.1-SNAPSHOT.jar" + } + } + ], + "source": { + "id": "d2ff433e51158ef9a80dae682491d6500e9070bb143038bc0756d49fda3ad416", + "name": "sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "version": "sha256:f1802eb27e84114cfd7213ec83534a4b3219da6c4b2dcc827e0130b69ffa63b9", + "type": "file", + "metadata": { + "path": "sbom-test-gradle-0.0.1-SNAPSHOT.jar", + "digests": [ + { + "algorithm": "sha256", + "value": "f1802eb27e84114cfd7213ec83534a4b3219da6c4b2dcc827e0130b69ffa63b9" + } + ], + "mimeType": "application/jar" + } + }, + "distro": {}, + "descriptor": { + "name": "syft", + "version": "0.105.0", + "configuration": { + "catalogers": { + "requested": { + "default": [ + "directory" + ] + }, + "used": [ + "alpm-db-cataloger", + "apk-db-cataloger", + "binary-cataloger", + "cocoapods-cataloger", + "conan-cataloger", + "dart-pubspec-lock-cataloger", + "dotnet-deps-cataloger", + "dotnet-portable-executable-cataloger", + "dpkg-db-cataloger", + "elixir-mix-lock-cataloger", + "erlang-otp-application-cataloger", + "erlang-rebar-lock-cataloger", + "github-action-workflow-usage-cataloger", + "github-actions-usage-cataloger", + "go-module-binary-cataloger", + "go-module-file-cataloger", + "graalvm-native-image-cataloger", + "haskell-cataloger", + "java-archive-cataloger", + "java-gradle-lockfile-cataloger", + "java-pom-cataloger", + "javascript-lock-cataloger", + "linux-kernel-cataloger", + "nix-store-cataloger", + "php-composer-lock-cataloger", + "portage-cataloger", + "python-installed-package-cataloger", + "python-package-cataloger", + "rpm-archive-cataloger", + "rpm-db-cataloger", + "ruby-gemfile-cataloger", + "ruby-gemspec-cataloger", + "rust-cargo-lock-cataloger", + "swift-package-manager-cataloger", + "wordpress-plugins-cataloger" + ] + }, + "data-generation": { + "generate-cpes": true + }, + "files": { + "content": { + "globs": null, + "skip-files-above-size": 0 + }, + "hashers": [ + "sha-1", + "sha-256" + ], + "selection": "owned-by-package" + }, + "packages": { + "binary": [ + "python-binary", + "python-binary-lib", + "pypy-binary-lib", + "go-binary", + "julia-binary", + "helm", + "redis-binary", + "java-binary-openjdk", + "java-binary-ibm", + "java-binary-oracle", + "nodejs-binary", + "go-binary-hint", + "busybox-binary", + "haproxy-binary", + "perl-binary", + "php-cli-binary", + "php-fpm-binary", + "php-apache-binary", + "php-composer-binary", + "httpd-binary", + "memcached-binary", + "traefik-binary", + "postgresql-binary", + "mysql-binary", + "mysql-binary", + "mysql-binary", + "xtrabackup-binary", + "mariadb-binary", + "rust-standard-library-linux", + "rust-standard-library-macos", + "ruby-binary", + "erlang-binary", + "consul-binary", + "nginx-binary", + "bash-binary", + "openssl-binary", + "gcc-binary", + "wordpress-cli-binary" + ], + "golang": { + "local-mod-cache-dir": "/home/user/go/pkg/mod", + "main-module-version": { + "from-build-settings": true, + "from-contents": true, + "from-ld-flags": true + }, + "proxies": [ + "https://proxy.golang.org", + "direct" + ], + "search-local-mod-cache-licenses": false, + "search-remote-licenses": false + }, + "java-archive": { + "include-indexed-archives": true, + "include-unindexed-archives": false, + "maven-base-url": "https://repo1.maven.org/maven2", + "max-parent-recursive-depth": 5, + "use-network": false + }, + "javascript": { + "npm-base-url": "https://registry.npmjs.org", + "search-remote-licenses": false + }, + "linux-kernel": { + "catalog-modules": true + }, + "python": { + "guess-unpinned-requirements": false + } + }, + "relationships": { + "exclude-binary-packages-with-file-ownership-overlap": true, + "package-file-ownership": true, + "package-file-ownership-overlap": true + }, + "search": { + "scope": "squashed" + } + } + }, + "schema": { + "version": "16.0.4", + "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-16.0.4.json" + } +} diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index 3ed2730ddfe7..13ad7ba4cbd9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -1,18 +1,44 @@ plugins { id "java-library" - id "org.jetbrains.kotlin.jvm" id "org.springframework.boot.auto-configuration" id "org.springframework.boot.configuration-properties" id "org.springframework.boot.conventions" id "org.springframework.boot.deployed" + id "org.springframework.boot.docker-test" id "org.springframework.boot.optional-dependencies" } description = "Spring Boot AutoConfigure" +configurations.all { + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + if (details.requested.module.group == "org.apache.kafka" && details.requested.module.name == "kafka-server-common") { + details.artifactSelection { + selectArtifact(DependencyArtifact.DEFAULT_TYPE, null, null) + } + } + } +} + dependencies { api(project(":spring-boot-project:spring-boot")) + dockerTestImplementation(project(":spring-boot-project:spring-boot-test")) + dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker")) + dockerTestImplementation("org.assertj:assertj-core") + dockerTestImplementation("org.junit.jupiter:junit-jupiter") + dockerTestImplementation("org.mockito:mockito-core") + dockerTestImplementation("org.springframework:spring-test") + dockerTestImplementation("org.testcontainers:cassandra") + dockerTestImplementation("org.testcontainers:couchbase") + dockerTestImplementation("org.testcontainers:elasticsearch") + dockerTestImplementation("org.testcontainers:junit-jupiter") + dockerTestImplementation("org.testcontainers:mongodb") + dockerTestImplementation("org.testcontainers:neo4j") + dockerTestImplementation("org.testcontainers:pulsar") + dockerTestImplementation("org.testcontainers:testcontainers") + dockerTestImplementation("org.awaitility:awaitility") + optional("co.elastic.clients:elasticsearch-java") { exclude group: "commons-logging", module: "commons-logging" } @@ -29,11 +55,13 @@ dependencies { optional("com.nimbusds:oauth2-oidc-sdk") optional("com.oracle.database.jdbc:ojdbc11") optional("com.oracle.database.jdbc:ucp11") + optional("com.querydsl:querydsl-core") optional("com.samskivert:jmustache") optional("io.lettuce:lettuce-core") optional("io.projectreactor.netty:reactor-netty-http") optional("io.r2dbc:r2dbc-spi") optional("io.r2dbc:r2dbc-pool") + optional("io.r2dbc:r2dbc-proxy") optional("io.rsocket:rsocket-core") optional("io.rsocket:rsocket-transport-netty") optional("io.undertow:undertow-servlet") @@ -48,7 +76,7 @@ dependencies { optional("jakarta.ws.rs:jakarta.ws.rs-api") optional("javax.cache:cache-api") optional("javax.money:money-api") - optional("org.apache.activemq:activemq-client-jakarta") + optional("org.apache.activemq:activemq-client") optional("org.apache.activemq:artemis-jakarta-client") { exclude group: "commons-logging", module: "commons-logging" } @@ -78,20 +106,10 @@ dependencies { optional("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect") optional("org.aspectj:aspectjweaver") optional("org.cache2k:cache2k-spring") - optional("org.eclipse.jetty:jetty-webapp") { - exclude(group: "org.eclipse.jetty", module: "jetty-jndi") - exclude(group: "org.eclipse.jetty.toolchain", module: "jetty-jakarta-servlet-api") - } + optional("org.eclipse.jetty.ee10:jetty-ee10-webapp") optional("org.eclipse.jetty:jetty-reactive-httpclient") - optional("org.eclipse.jetty.websocket:websocket-jakarta-server") { - exclude(group: "org.eclipse.jetty", module: "jetty-jndi") - exclude(group: "org.eclipse.jetty.toolchain", module: "jetty-jakarta-servlet-api") - exclude(group: "org.eclipse.jetty.toolchain", module: "jetty-jakarta-websocket-api") - } - optional("org.eclipse.jetty.websocket:websocket-jetty-server") { - exclude(group: "org.eclipse.jetty", module: "jetty-jndi") - exclude(group: "org.eclipse.jetty.toolchain", module: "jetty-jakarta-servlet-api") - } + optional("org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-server") + optional("org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-server") optional("org.ehcache:ehcache") { artifact { classifier = 'jakarta' @@ -104,6 +122,8 @@ dependencies { exclude group: "commons-logging", module: "commons-logging" } optional("org.flywaydb:flyway-core") + optional("org.flywaydb:flyway-database-postgresql") + optional("org.flywaydb:flyway-database-oracle") optional("org.flywaydb:flyway-sqlserver") optional("org.freemarker:freemarker") optional("org.glassfish.jersey.containers:jersey-container-servlet-core") @@ -114,17 +134,11 @@ dependencies { optional("org.hibernate.orm:hibernate-core") optional("org.hibernate.orm:hibernate-jcache") optional("org.hibernate.validator:hibernate-validator") - optional("org.infinispan:infinispan-commons-jakarta") + optional("org.infinispan:infinispan-commons") optional("org.infinispan:infinispan-component-annotations") - optional("org.infinispan:infinispan-core-jakarta") - optional("org.infinispan:infinispan-jcache") { - exclude group: "org.infinispan", module: "infinispan-commons" - exclude group: "org.infinispan", module: "infinispan-core" - } - optional("org.infinispan:infinispan-spring5-embedded") { - exclude group: "org.infinispan", module: "infinispan-commons" - exclude group: "org.infinispan", module: "infinispan-core" - } + optional("org.infinispan:infinispan-core") + optional("org.infinispan:infinispan-jcache") + optional("org.infinispan:infinispan-spring6-embedded") optional("org.influxdb:influxdb-java") optional("org.jooq:jooq") { exclude group: "javax.xml.bind", module: "jaxb-api" @@ -178,6 +192,8 @@ dependencies { optional("org.springframework.data:spring-data-redis") optional("org.springframework.graphql:spring-graphql") optional("org.springframework.hateoas:spring-hateoas") + optional("org.springframework.pulsar:spring-pulsar") + optional("org.springframework.pulsar:spring-pulsar-reactive") optional("org.springframework.security:spring-security-acl") optional("org.springframework.security:spring-security-config") optional("org.springframework.security:spring-security-data") { @@ -223,9 +239,9 @@ dependencies { testImplementation("com.ibm.db2:jcc") testImplementation("com.jayway.jsonpath:json-path") testImplementation("com.mysql:mysql-connector-j") - testImplementation("com.querydsl:querydsl-core") testImplementation("com.squareup.okhttp3:mockwebserver") testImplementation("com.sun.xml.messaging.saaj:saaj-impl") + testImplementation("io.micrometer:context-propagation") testImplementation("io.projectreactor:reactor-test") testImplementation("io.r2dbc:r2dbc-h2") testImplementation("jakarta.json:jakarta.json-api") @@ -246,18 +262,15 @@ dependencies { testImplementation("org.springframework:spring-test") testImplementation("org.springframework:spring-core-test") testImplementation("org.springframework.graphql:spring-graphql-test") - testImplementation("org.springframework.kafka:spring-kafka-test") + testImplementation("org.springframework.kafka:spring-kafka-test") { + exclude group: "commons-logging", module: "commons-logging" + } + testImplementation("org.springframework.pulsar:spring-pulsar-cache-provider-caffeine") testImplementation("org.springframework.security:spring-security-test") - testImplementation("org.testcontainers:cassandra") - testImplementation("org.testcontainers:couchbase") - testImplementation("org.testcontainers:elasticsearch") - testImplementation("org.testcontainers:junit-jupiter") - testImplementation("org.testcontainers:mongodb") - testImplementation("org.testcontainers:neo4j") - testImplementation("org.testcontainers:testcontainers") testImplementation("org.yaml:snakeyaml") testRuntimeOnly("jakarta.management.j2ee:jakarta.management.j2ee-api") + testRuntimeOnly("org.flywaydb:flyway-database-hsqldb") testRuntimeOnly("org.jetbrains.kotlin:kotlin-reflect") } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfigurationIntegrationTests.java similarity index 91% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfigurationIntegrationTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfigurationIntegrationTests.java index e120b7978848..5d89b6c9aeca 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import org.junit.jupiter.api.Test; +import org.testcontainers.containers.CassandraContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -26,7 +27,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.testsupport.testcontainers.CassandraContainer; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -43,7 +44,7 @@ class CassandraAutoConfigurationIntegrationTests { @Container - static final CassandraContainer cassandra = new CassandraContainer(); + static final CassandraContainer cassandra = TestImage.container(CassandraContainer.class); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(CassandraAutoConfiguration.class)) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfigurationWithPasswordAuthenticationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfigurationWithPasswordAuthenticationIntegrationTests.java similarity index 88% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfigurationWithPasswordAuthenticationIntegrationTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfigurationWithPasswordAuthenticationIntegrationTests.java index 4880ea1dedbc..4f144c65d592 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfigurationWithPasswordAuthenticationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfigurationWithPasswordAuthenticationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; -import java.time.Duration; import java.util.concurrent.TimeUnit; import com.datastax.oss.driver.api.core.ConsistencyLevel; @@ -28,16 +27,18 @@ import org.junit.jupiter.api.Test; import org.rnorth.ducttape.TimeoutException; import org.rnorth.ducttape.unreliables.Unreliables; +import org.testcontainers.containers.CassandraContainer; import org.testcontainers.containers.ContainerLaunchException; import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy; import org.testcontainers.images.builder.Transferable; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; import org.springframework.beans.factory.BeanCreationException; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.testsupport.testcontainers.CassandraContainer; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.util.StreamUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -52,8 +53,9 @@ class CassandraAutoConfigurationWithPasswordAuthenticationIntegrationTests { @Container - static final CassandraContainer cassandra = new PasswordAuthenticatorCassandraContainer().withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)) + static final PasswordAuthenticatorCassandraContainer cassandra = TestImage + .container(PasswordAuthenticatorCassandraContainer.class) + .withStartupAttempts(5) .waitingFor(new CassandraWaitStrategy()); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() @@ -83,7 +85,12 @@ void authenticationWithInvalidCredentials() { .withMessageContaining("Authentication error")); } - static final class PasswordAuthenticatorCassandraContainer extends CassandraContainer { + static final class PasswordAuthenticatorCassandraContainer + extends CassandraContainer { + + PasswordAuthenticatorCassandraContainer(DockerImageName dockerImageName) { + super(dockerImageName); + } @Override protected void containerIsCreated(String containerId) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationIntegrationTests.java similarity index 92% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationIntegrationTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationIntegrationTests.java index 5fcd8bb95857..9fdf08e63c97 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import static org.assertj.core.api.Assertions.assertThat; @@ -51,11 +51,9 @@ class CouchbaseAutoConfigurationIntegrationTests { private static final String BUCKET_NAME = "cbbucket"; @Container - static final CouchbaseContainer couchbase = new CouchbaseContainer(DockerImageNames.couchbase()) + static final CouchbaseContainer couchbase = TestImage.container(CouchbaseContainer.class) .withEnabledServices(CouchbaseService.KV) .withCredentials("spring", "password") - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)) .withBucket(new BucketDefinition(BUCKET_NAME).withPrimaryIndex(false)); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationIntegrationTests.java similarity index 90% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationIntegrationTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationIntegrationTests.java index aacd79fe8d6d..fd5c2727d62b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.CqlSessionBuilder; import org.junit.jupiter.api.Test; +import org.testcontainers.containers.CassandraContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -28,7 +29,7 @@ import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; import org.springframework.boot.autoconfigure.data.cassandra.city.City; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.testsupport.testcontainers.CassandraContainer; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.cassandra.config.SchemaAction; @@ -46,7 +47,7 @@ class CassandraDataAutoConfigurationIntegrationTests { @Container - static final CassandraContainer cassandra = new CassandraContainer(); + static final CassandraContainer cassandra = TestImage.container(CassandraContainer.class); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration( @@ -79,7 +80,7 @@ static class KeyspaceTestConfiguration { CqlSession cqlSession(CqlSessionBuilder cqlSessionBuilder) { try (CqlSession session = cqlSessionBuilder.build()) { session.execute("CREATE KEYSPACE IF NOT EXISTS boot_test" - + " WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"); + + " WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"); } return cqlSessionBuilder.withKeyspace("boot_test").build(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfigurationTests.java similarity index 91% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfigurationTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfigurationTests.java index cd8afa626751..ff993d8c4b6c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.boot.autoconfigure.data.elasticsearch; -import java.time.Duration; - import org.junit.jupiter.api.Test; import org.testcontainers.elasticsearch.ElasticsearchContainer; import org.testcontainers.junit.jupiter.Container; @@ -32,7 +30,7 @@ import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration; import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Configuration; import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate; import org.springframework.data.elasticsearch.config.EnableElasticsearchAuditing; @@ -52,10 +50,7 @@ class ElasticsearchRepositoriesAutoConfigurationTests { @Container - static final ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) - .withEnv("ES_JAVA_OPTS", "-Xms32m -Xmx512m") - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + static final ElasticsearchContainer elasticsearch = TestImage.container(ElasticsearchContainer.class); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class, diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRepositoriesAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRepositoriesAutoConfigurationTests.java similarity index 87% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRepositoriesAutoConfigurationTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRepositoriesAutoConfigurationTests.java index 68d2b5d173f8..a5b7ec800b86 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRepositoriesAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRepositoriesAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.boot.autoconfigure.data.elasticsearch; -import java.time.Duration; - import org.junit.jupiter.api.Test; import org.testcontainers.elasticsearch.ElasticsearchContainer; import org.testcontainers.junit.jupiter.Container; @@ -32,10 +30,8 @@ import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration; import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; import org.springframework.boot.autoconfigure.elasticsearch.ReactiveElasticsearchClientAutoConfiguration; -import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener; -import org.springframework.boot.logging.LogLevel; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Configuration; import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchTemplate; import org.springframework.data.elasticsearch.config.EnableElasticsearchAuditing; @@ -55,10 +51,7 @@ class ReactiveElasticsearchRepositoriesAutoConfigurationTests { @Container - static ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) - .withEnv("ES_JAVA_OPTS", "-Xms32m -Xmx512m") - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + static final ElasticsearchContainer elasticsearch = TestImage.container(ElasticsearchContainer.class); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(ElasticsearchClientAutoConfiguration.class, @@ -67,8 +60,7 @@ class ReactiveElasticsearchRepositoriesAutoConfigurationTests { ReactiveElasticsearchClientAutoConfiguration.class)) .withPropertyValues( "spring.elasticsearch.uris=" + elasticsearch.getHost() + ":" + elasticsearch.getFirstMappedPort(), - "spring.elasticsearch.socket-timeout=30s") - .withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO)); + "spring.elasticsearch.socket-timeout=30s"); @Test void testDefaultRepositoryConfiguration() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationIntegrationTests.java similarity index 83% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationIntegrationTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationIntegrationTests.java index d9a007bbf4dc..f9875aae74e3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.boot.autoconfigure.data.neo4j; -import java.time.Duration; - import org.junit.jupiter.api.Test; import org.testcontainers.containers.Neo4jContainer; import org.testcontainers.junit.jupiter.Container; @@ -28,7 +26,7 @@ import org.springframework.boot.autoconfigure.data.neo4j.country.CountryRepository; import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Configuration; import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; import org.springframework.test.context.DynamicPropertyRegistry; @@ -46,15 +44,13 @@ class Neo4jRepositoriesAutoConfigurationIntegrationTests { @Container - private static final Neo4jContainer neo4jServer = new Neo4jContainer<>(DockerImageNames.neo4j()) - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + static final Neo4jContainer neo4j = TestImage.container(Neo4jContainer.class); @DynamicPropertySource static void neo4jProperties(DynamicPropertyRegistry registry) { - registry.add("spring.neo4j.uri", neo4jServer::getBoltUrl); + registry.add("spring.neo4j.uri", neo4j::getBoltUrl); registry.add("spring.neo4j.authentication.username", () -> "neo4j"); - registry.add("spring.neo4j.authentication.password", neo4jServer::getAdminPassword); + registry.add("spring.neo4j.authentication.password", neo4j::getAdminPassword); } @Autowired diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisRepositoriesAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/data/redis/RedisRepositoriesAutoConfigurationTests.java similarity index 92% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisRepositoriesAutoConfigurationTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/data/redis/RedisRepositoriesAutoConfigurationTests.java index f7e719c865b0..7e22b198f9fa 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisRepositoriesAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/data/redis/RedisRepositoriesAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.boot.autoconfigure.data.redis; -import java.time.Duration; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -31,7 +29,8 @@ import org.springframework.boot.autoconfigure.data.redis.city.City; import org.springframework.boot.autoconfigure.data.redis.city.CityRepository; import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.boot.testsupport.testcontainers.RedisContainer; +import org.springframework.boot.testsupport.container.RedisContainer; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; @@ -47,8 +46,7 @@ class RedisRepositoriesAutoConfigurationTests { @Container - public static RedisContainer redis = new RedisContainer().withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + public static RedisContainer redis = TestImage.container(RedisContainer.class); private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientAutoConfigurationIntegrationTests.java similarity index 86% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientAutoConfigurationIntegrationTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientAutoConfigurationIntegrationTests.java index 9935d29bee12..b3b73148b2b1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientAutoConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.springframework.boot.autoconfigure.elasticsearch; -import java.time.Duration; import java.util.Map; import co.elastic.clients.elasticsearch.ElasticsearchClient; @@ -29,7 +28,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import static org.assertj.core.api.Assertions.assertThat; @@ -42,10 +41,7 @@ class ElasticsearchClientAutoConfigurationIntegrationTests { @Container - static ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) - .withEnv("ES_JAVA_OPTS", "-Xms32m -Xmx512m") - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + static final ElasticsearchContainer elasticsearch = TestImage.container(ElasticsearchContainer.class); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationIntegrationTests.java similarity index 86% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationIntegrationTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationIntegrationTests.java index 606de234947b..34aa5f9e0920 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package org.springframework.boot.autoconfigure.elasticsearch; import java.io.InputStream; -import java.time.Duration; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -31,7 +30,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import static org.assertj.core.api.Assertions.assertThat; @@ -47,10 +46,7 @@ class ElasticsearchRestClientAutoConfigurationIntegrationTests { @Container - static final ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) - .withEnv("ES_JAVA_OPTS", "-Xms32m -Xmx512m") - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + static final ElasticsearchContainer elasticsearch = TestImage.container(ElasticsearchContainer.class); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class)); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchClientAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchClientAutoConfigurationIntegrationTests.java similarity index 87% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchClientAutoConfigurationIntegrationTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchClientAutoConfigurationIntegrationTests.java index 83088a3a3054..cdb88dea998c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchClientAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/elasticsearch/ReactiveElasticsearchClientAutoConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.springframework.boot.autoconfigure.elasticsearch; -import java.time.Duration; import java.util.Map; import co.elastic.clients.elasticsearch.core.GetResponse; @@ -30,7 +29,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient; import static org.assertj.core.api.Assertions.assertThat; @@ -45,10 +44,7 @@ class ReactiveElasticsearchClientAutoConfigurationIntegrationTests { @Container - static ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) - .withEnv("ES_JAVA_OPTS", "-Xms32m -Xmx512m") - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + static final ElasticsearchContainer elasticsearch = TestImage.container(ElasticsearchContainer.class); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, diff --git a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/mail/MailSenderAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/mail/MailSenderAutoConfigurationIntegrationTests.java new file mode 100644 index 000000000000..61a6518c38e1 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/mail/MailSenderAutoConfigurationIntegrationTests.java @@ -0,0 +1,219 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.mail; + +import java.net.SocketTimeoutException; +import java.security.cert.CertPathBuilderException; +import java.time.Duration; +import java.util.Arrays; + +import javax.net.ssl.SSLException; + +import jakarta.mail.Folder; +import jakarta.mail.Message; +import jakarta.mail.MessagingException; +import jakarta.mail.Session; +import jakarta.mail.Store; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.MountableFile; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.testsupport.container.MailpitContainer; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatException; + +/** + * Integration tests for {@link MailSenderAutoConfiguration}. + * + * @author Rui Figueira + */ +@Testcontainers(disabledWithoutDocker = true) +class MailSenderAutoConfigurationIntegrationTests { + + private SimpleMailMessage createMessage(String subject) { + SimpleMailMessage msg = new SimpleMailMessage(); + msg.setFrom("from@example.com"); + msg.setTo("to@example.com"); + msg.setSubject(subject); + msg.setText("Subject: " + subject); + return msg; + } + + private String getSubject(Message message) { + try { + return message.getSubject(); + } + catch (MessagingException ex) { + throw new RuntimeException("Failed to get message subject", ex); + } + } + + private void assertMessagesContainSubject(Session session, String subject) throws MessagingException { + try (Store store = session.getStore("pop3")) { + String host = session.getProperty("mail.pop3.host"); + int port = Integer.parseInt(session.getProperty("mail.pop3.port")); + store.connect(host, port, "user", "pass"); + try (Folder folder = store.getFolder("inbox")) { + folder.open(Folder.READ_ONLY); + Awaitility.await() + .atMost(Duration.ofSeconds(5)) + .ignoreExceptions() + .untilAsserted(() -> assertThat(Arrays.stream(folder.getMessages()).map(this::getSubject)) + .contains(subject)); + } + } + } + + @Nested + class ImplicitTlsTests { + + @Container + private static final MailpitContainer mailpit = TestImage.container(MailpitContainer.class) + .withSmtpRequireTls(true) + .withSmtpTlsCert(MountableFile + .forClasspathResource("/org/springframework/boot/autoconfigure/mail/ssl/test-server.crt")) + .withSmtpTlsKey(MountableFile + .forClasspathResource("/org/springframework/boot/autoconfigure/mail/ssl/test-server.key")) + .withPop3Auth("user:pass"); + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(MailSenderAutoConfiguration.class, SslAutoConfiguration.class)); + + @Test + void sendEmailWithSslEnabledAndCert() { + this.contextRunner.withPropertyValues("spring.mail.host:" + mailpit.getHost(), + "spring.mail.port:" + mailpit.getSmtpPort(), "spring.mail.ssl.enabled:true", + "spring.mail.ssl.bundle:test-bundle", + "spring.ssl.bundle.pem.test-bundle.truststore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-ca.crt", + "spring.ssl.bundle.pem.test-bundle.keystore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.crt", + "spring.ssl.bundle.pem.test-bundle.keystore.private-key=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.key", + "spring.mail.properties.mail.pop3.host:" + mailpit.getHost(), + "spring.mail.properties.mail.pop3.port:" + mailpit.getPop3Port()) + .run((context) -> { + JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class); + mailSender.send(createMessage("Hello World!")); + assertMessagesContainSubject(mailSender.getSession(), "Hello World!"); + }); + } + + @Test + void sendEmailWithSslEnabledWithoutCert() { + this.contextRunner + .withPropertyValues("spring.mail.host:" + mailpit.getHost(), + "spring.mail.port:" + mailpit.getSmtpPort(), "spring.mail.ssl.enabled:true") + .run((context) -> { + JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class); + assertThatException().isThrownBy(() -> mailSender.send(createMessage("Should fail"))) + .withRootCauseInstanceOf(CertPathBuilderException.class); + }); + } + + @Test + void sendEmailWithoutSslWithCert() { + this.contextRunner.withPropertyValues("spring.mail.host:" + mailpit.getHost(), + "spring.mail.port:" + mailpit.getSmtpPort(), "spring.mail.properties.mail.smtp.timeout:1000", + "spring.mail.ssl.bundle:test-bundle", + "spring.ssl.bundle.pem.test-bundle.truststore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-ca.crt", + "spring.ssl.bundle.pem.test-bundle.keystore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.crt", + "spring.ssl.bundle.pem.test-bundle.keystore.private-key=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.key") + .run((context) -> { + JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class); + assertThatException().isThrownBy(() -> mailSender.send(createMessage("Should fail"))) + .withRootCauseInstanceOf(SocketTimeoutException.class); + }); + } + + } + + @Nested + class StarttlsTests { + + @Container + private static final MailpitContainer mailpit = TestImage.container(MailpitContainer.class) + .withSmtpRequireStarttls(true) + .withSmtpTlsCert(MountableFile + .forClasspathResource("/org/springframework/boot/autoconfigure/mail/ssl/test-server.crt")) + .withSmtpTlsKey(MountableFile + .forClasspathResource("/org/springframework/boot/autoconfigure/mail/ssl/test-server.key")) + .withPop3Auth("user:pass"); + + final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(MailSenderAutoConfiguration.class, SslAutoConfiguration.class)); + + @Test + void sendEmailWithStarttlsAndCertAndSslDisabled() { + this.contextRunner.withPropertyValues("spring.mail.host:" + mailpit.getHost(), + "spring.mail.port:" + mailpit.getSmtpPort(), + "spring.mail.properties.mail.smtp.starttls.enable:true", + "spring.mail.properties.mail.smtp.starttls.required:true", "spring.mail.ssl.bundle:test-bundle", + "spring.ssl.bundle.pem.test-bundle.truststore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-ca.crt", + "spring.ssl.bundle.pem.test-bundle.keystore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.crt", + "spring.ssl.bundle.pem.test-bundle.keystore.private-key=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.key", + "spring.mail.properties.mail.pop3.host:" + mailpit.getHost(), + "spring.mail.properties.mail.pop3.port:" + mailpit.getPop3Port()) + .run((context) -> { + JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class); + mailSender.send(createMessage("Sent with STARTTLS")); + assertMessagesContainSubject(mailSender.getSession(), "Sent with STARTTLS"); + }); + } + + @Test + void sendEmailWithStarttlsAndCertAndSslEnabled() { + this.contextRunner.withPropertyValues("spring.mail.host:" + mailpit.getHost(), + "spring.mail.port:" + mailpit.getSmtpPort(), "spring.mail.ssl.enabled:true", + "spring.mail.properties.mail.smtp.starttls.enable:true", + "spring.mail.properties.mail.smtp.starttls.required:true", "spring.mail.ssl.bundle:test-bundle", + "spring.ssl.bundle.pem.test-bundle.truststore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-ca.crt", + "spring.ssl.bundle.pem.test-bundle.keystore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.crt", + "spring.ssl.bundle.pem.test-bundle.keystore.private-key=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.key", + "spring.mail.properties.mail.pop3.host:" + mailpit.getHost(), + "spring.mail.properties.mail.pop3.port:" + mailpit.getPop3Port()) + .run((context) -> { + JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class); + assertThatException().isThrownBy(() -> mailSender.send(createMessage("Should fail"))) + .withRootCauseInstanceOf(SSLException.class); + }); + } + + @Test + void sendEmailWithStarttlsWithoutCert() { + this.contextRunner + .withPropertyValues("spring.mail.host:" + mailpit.getHost(), + "spring.mail.port:" + mailpit.getSmtpPort(), + "spring.mail.properties.mail.smtp.starttls.enable:true", + "spring.mail.properties.mail.smtp.starttls.required:true") + .run((context) -> { + JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class); + assertThatException().isThrownBy(() -> mailSender.send(createMessage("Should fail"))) + .withRootCauseInstanceOf(CertPathBuilderException.class); + }); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationIntegrationTests.java new file mode 100644 index 000000000000..1b8ec950208b --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationIntegrationTests.java @@ -0,0 +1,180 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.neo4j; + +import java.net.URI; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.neo4j.driver.AuthToken; +import org.neo4j.driver.AuthTokenManager; +import org.neo4j.driver.AuthTokenManagers; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Driver; +import org.neo4j.driver.Result; +import org.neo4j.driver.Session; +import org.neo4j.driver.Transaction; +import org.testcontainers.containers.Neo4jContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link Neo4jAutoConfiguration}. + * + * @author Michael J. Simons + * @author Stephane Nicoll + */ +@Testcontainers(disabledWithoutDocker = true) +class Neo4jAutoConfigurationIntegrationTests { + + @Container + private static final Neo4jContainer neo4j = TestImage.container(Neo4jContainer.class); + + @SpringBootTest + @Nested + class DriverWithDefaultAuthToken { + + @DynamicPropertySource + static void neo4jProperties(DynamicPropertyRegistry registry) { + registry.add("spring.neo4j.uri", neo4j::getBoltUrl); + registry.add("spring.neo4j.authentication.username", () -> "neo4j"); + registry.add("spring.neo4j.authentication.password", neo4j::getAdminPassword); + } + + @Autowired + private Driver driver; + + @Test + void driverCanHandleRequest() { + try (Session session = this.driver.session(); Transaction tx = session.beginTransaction()) { + Result statementResult = tx.run("MATCH (n:Thing) RETURN n LIMIT 1"); + assertThat(statementResult.hasNext()).isFalse(); + tx.commit(); + } + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(Neo4jAutoConfiguration.class) + static class TestConfiguration { + + } + + } + + @SpringBootTest + @Nested + class DriverWithDynamicAuthToken { + + @DynamicPropertySource + static void neo4jProperties(DynamicPropertyRegistry registry) { + registry.add("spring.neo4j.uri", neo4j::getBoltUrl); + registry.add("spring.neo4j.authentication.username", () -> "wrong"); + registry.add("spring.neo4j.authentication.password", () -> "alsowrong"); + } + + @Autowired + private Driver driver; + + @Test + void driverCanHandleRequest() { + try (Session session = this.driver.session(); Transaction tx = session.beginTransaction()) { + Result statementResult = tx.run("MATCH (n:Thing) RETURN n LIMIT 1"); + assertThat(statementResult.hasNext()).isFalse(); + tx.commit(); + } + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(Neo4jAutoConfiguration.class) + static class TestConfiguration { + + @Bean + AuthTokenManager authTokenManager() { + return AuthTokenManagers.bearer(() -> AuthTokens.basic("neo4j", neo4j.getAdminPassword()) + .expiringAt(System.currentTimeMillis() + 5_000)); + } + + } + + } + + @SpringBootTest + @Nested + class DriverWithCustomConnectionDetailsIgnoresAuthTokenManager { + + @DynamicPropertySource + static void neo4jProperties(DynamicPropertyRegistry registry) { + registry.add("spring.neo4j.uri", neo4j::getBoltUrl); + registry.add("spring.neo4j.authentication.username", () -> "wrong"); + registry.add("spring.neo4j.authentication.password", () -> "alsowrong"); + } + + @Autowired + private Driver driver; + + @Test + void driverCanHandleRequest() { + try (Session session = this.driver.session(); Transaction tx = session.beginTransaction()) { + Result statementResult = tx.run("MATCH (n:Thing) RETURN n LIMIT 1"); + assertThat(statementResult.hasNext()).isFalse(); + tx.commit(); + } + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(Neo4jAutoConfiguration.class) + static class TestConfiguration { + + @Bean + AuthTokenManager authTokenManager() { + return AuthTokenManagers.bearer(() -> AuthTokens.basic("wrongagain", "stillwrong") + .expiringAt(System.currentTimeMillis() + 5_000)); + } + + @Bean + Neo4jConnectionDetails connectionDetails() { + return new Neo4jConnectionDetails() { + + @Override + public URI getUri() { + return URI.create(neo4j.getBoltUrl()); + } + + @Override + public AuthToken getAuthToken() { + return AuthTokens.basic("neo4j", neo4j.getAdminPassword()); + } + + }; + } + + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/pulsar/PulsarAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/pulsar/PulsarAutoConfigurationIntegrationTests.java new file mode 100644 index 000000000000..fc9d6ccd9082 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/pulsar/PulsarAutoConfigurationIntegrationTests.java @@ -0,0 +1,115 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.pulsar.client.api.PulsarClientException; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.PulsarContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.pulsar.annotation.PulsarListener; +import org.springframework.pulsar.core.PulsarTemplate; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link PulsarAutoConfiguration}. + * + * @author Chris Bono + * @author Phillip Webb + */ +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@Testcontainers(disabledWithoutDocker = true) +class PulsarAutoConfigurationIntegrationTests { + + @Container + static final PulsarContainer pulsar = TestImage.container(PulsarContainer.class); + + private static final CountDownLatch listenLatch = new CountDownLatch(1); + + private static final String TOPIC = "pacit-hello-topic"; + + @DynamicPropertySource + static void pulsarProperties(DynamicPropertyRegistry registry) { + registry.add("spring.pulsar.client.service-url", pulsar::getPulsarBrokerUrl); + registry.add("spring.pulsar.admin.service-url", pulsar::getHttpServiceUrl); + } + + @Test + void appStartsWithAutoConfiguredSpringPulsarComponents( + @Autowired(required = false) PulsarTemplate pulsarTemplate) { + assertThat(pulsarTemplate).isNotNull(); + } + + @Test + void templateCanBeAccessedDuringWebRequest(@Autowired TestRestTemplate restTemplate) throws InterruptedException { + assertThat(restTemplate.getForObject("/hello", String.class)).startsWith("Hello World -> "); + assertThat(listenLatch.await(5, TimeUnit.SECONDS)).isTrue(); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration({ DispatcherServletAutoConfiguration.class, ServletWebServerFactoryAutoConfiguration.class, + WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, JacksonAutoConfiguration.class, + PulsarAutoConfiguration.class, PulsarReactiveAutoConfiguration.class }) + @Import(TestWebController.class) + static class TestConfiguration { + + @PulsarListener(subscriptionName = TOPIC + "-sub", topics = TOPIC) + void listen(String ignored) { + listenLatch.countDown(); + } + + } + + @RestController + static class TestWebController { + + private final PulsarTemplate pulsarTemplate; + + TestWebController(PulsarTemplate pulsarTemplate) { + this.pulsarTemplate = pulsarTemplate; + } + + @GetMapping("/hello") + String sayHello() throws PulsarClientException { + return "Hello World -> " + this.pulsarTemplate.send(TOPIC, "hello"); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationMongoTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationMongoTests.java similarity index 95% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationMongoTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationMongoTests.java index 4ad31f136f73..5bd7cc4da5ba 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationMongoTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationMongoTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.http.ResponseCookie; import org.springframework.session.MapSession; import org.springframework.session.data.mongo.ReactiveMongoSessionRepository; @@ -52,8 +52,7 @@ class ReactiveSessionAutoConfigurationMongoTests extends AbstractSessionAutoConfigurationTests { @Container - static final MongoDBContainer mongoDb = new MongoDBContainer(DockerImageNames.mongo()).withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(5)); + static final MongoDBContainer mongoDb = TestImage.container(MongoDBContainer.class); private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() .withClassLoader(new FilteredClassLoader(ReactiveRedisSessionRepository.class)) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationRedisTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationRedisTests.java similarity index 95% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationRedisTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationRedisTests.java index 0cb97f900eee..55d55bb4e9d1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationRedisTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationRedisTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,8 @@ import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; -import org.springframework.boot.testsupport.testcontainers.RedisContainer; +import org.springframework.boot.testsupport.container.RedisContainer; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.http.ResponseCookie; import org.springframework.session.MapSession; import org.springframework.session.SaveMode; @@ -52,8 +53,7 @@ class ReactiveSessionAutoConfigurationRedisTests extends AbstractSessionAutoConfigurationTests { @Container - public static RedisContainer redis = new RedisContainer().withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + public static RedisContainer redis = TestImage.container(RedisContainer.class); protected final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() .withClassLoader(new FilteredClassLoader(ReactiveMongoSessionRepository.class)) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationMongoTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationMongoTests.java similarity index 92% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationMongoTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationMongoTests.java index ec221f78d6d4..beafacd75ab9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationMongoTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationMongoTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.session.config.SessionRepositoryCustomizer; @@ -51,15 +51,14 @@ class SessionAutoConfigurationMongoTests extends AbstractSessionAutoConfigurationTests { @Container - static final MongoDBContainer mongoDB = new MongoDBContainer(DockerImageNames.mongo()).withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(5)); + static final MongoDBContainer mongoDb = TestImage.container(MongoDBContainer.class); private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() .withClassLoader(new FilteredClassLoader(HazelcastIndexedSessionRepository.class, JdbcIndexedSessionRepository.class, RedisIndexedSessionRepository.class)) .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, SessionAutoConfiguration.class)) - .withPropertyValues("spring.data.mongodb.uri=" + mongoDB.getReplicaSetUrl()); + .withPropertyValues("spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl()); @Test void defaultConfig() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java similarity index 97% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java index e98b88fc0a52..eecebb7ad9ca 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,8 @@ import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; -import org.springframework.boot.testsupport.testcontainers.RedisContainer; +import org.springframework.boot.testsupport.container.RedisContainer; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnection; @@ -60,8 +61,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfigurationTests { @Container - public static RedisContainer redis = new RedisContainer().withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + public static RedisContainer redis = TestImage.container(RedisContainer.class); protected final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() .withClassLoader(new FilteredClassLoader(HazelcastIndexedSessionRepository.class, diff --git a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/resources/org/springframework/boot/autoconfigure/mail/ssl/test-ca.crt b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/resources/org/springframework/boot/autoconfigure/mail/ssl/test-ca.crt new file mode 100644 index 000000000000..beed250b132b --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/resources/org/springframework/boot/autoconfigure/mail/ssl/test-ca.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFhjCCA26gAwIBAgIUfIkk29IT9OpbgfjL8oRIPSLjUcAwDQYJKoZIhvcNAQEL +BQAwOzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlm +aWNhdGUgQXV0aG9yaXR5MB4XDTI0MDUwMTE2NTMyNVoXDTM0MDQyOTE2NTMyNVow +OzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlmaWNh +dGUgQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAusN2 +KzQQUUxZSiI3ZZuZohFwq2KXSUNPdJ6rgD3/YKNTDSZXKZPO53kYPP0DXf0sm3CH +cyWSWVabyimZYuPWena1MElSL4ZpJ9WwkZoOQ3bPFK1utz6kMOwrgAUcky8H/rIK +j2JEBhkSHUIGr57NjUEwG1ygaSerM8RzWw1PtMq+C8LOu3v94qzE3NDg1QRpyvV9 +OmsLsjISd0ZmAJNi9vmiEH923KnPyiqnQmWKpYicdgQmX1GXylS22jZqAwaOkYGj +X8UdeyvrohkZkM0hn9uaSufQGEW4yKACn3PkjJtzi8drBIyjIi9YcAzBxZB9oVKq +XZMlltgO2fDMmIJi0Ngt0Ci7fCoEMqSocKyDKML6YLr9UWtx4bfsrk+rVO9Q/D/v +8RKgstv7dCf2KWRX3ZJEC0IBHS5gLNq0qqqVcGx3LcSyhdiKJOtSwAnNkHMh+jSQ +xLSlBjcSqTPiGTRK/Rddl+xnU/mBgk7ZBGNrUFaD5McMFjddS7Ih82aHnpQ1gekW +nUGv+Tm/G68h2BvZ5U2q+RfeOCgRW9i/AYW2jgT7IFnfjyUXgBQveauMAchomqFE +VLe95ZgViF6vmH34EKo3w9L5TQiwk/r53YlM7TSOTyDqx66t4zGYDsVMicpKmzi4 +2Rp8EpErARRyREUIKSvWs9O9+uT3+7arNLgHe5ECAwEAAaOBgTB/MB0GA1UdDgQW +BBRVMLDVqPECWaH6GruL9E52VcTrPjAfBgNVHSMEGDAWgBRVMLDVqPECWaH6GruL +9E52VcTrPjAPBgNVHRMBAf8EBTADAQH/MCwGA1UdEQQlMCOCC2V4YW1wbGUuY29t +gglsb2NhbGhvc3SCCTEyNy4wLjAuMTANBgkqhkiG9w0BAQsFAAOCAgEAeSpjCL3j +2GIFBNKr/5amLOYa0kZ6r1dJs+K6xvMsUvsBJ/QQsV5nYDMIoV/NYUd8SyYV4lEj +7LHX5ZbmJrvPk30LGEBG/5Vy2MIATrQrQ14S4nXtEdSnBvTQwPOOaHc+2dTp3YpM +f4ffELKWyispTifx1eqdiUJhURKeQBh+3W7zpyaiN4vJaqEDKGgFQtHA/OyZL2hZ +BpxHB0zpb2iDHV8MeyfOT7HQWUk6p13vdYm6EnyJT8fzWvE+TqYNbqFmB+CLRSXy +R3p1yaeTd4LnVknJ0UBKqEyul3ziHZDhKhBpwdglYOQz4eWjSFhikX9XZ8NaI38Q +QqLZVn0DsH2ztkjrQrUVgK2xn4aUuqoLDk4Hu6h5baUn+f2GLuzx+EXc/i3ikYvw +Y3JyufOgw6nGGFG+/QXEj85XtLPhN7Wm42z2e/BGzi0MLl65sfpEDXvFTA72Yzws +OYaeg/HxeYwUHQgs2fKl/LgV4chntSCvTqfNl6OnQafD/ISJNpx3xWR3HwF+ypFG +UaLE+e1soqEJbzL31U/6pypHLsj8Y8r9hJbZXo2ibnhjFV6fypUAP0rbIzaoWcrJ +T0Sbliz+KQTMzCcubiAi4bI/kZ5FJ4kkaHqUpIWzlx1h2WVJ65ASFDjBWb8eVmB6 +Dyno/RVFR/rUL5091gjGRXhLsi1oUHKdEzU= +-----END CERTIFICATE----- diff --git a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/resources/org/springframework/boot/autoconfigure/mail/ssl/test-ca.key b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/resources/org/springframework/boot/autoconfigure/mail/ssl/test-ca.key new file mode 100644 index 000000000000..1142d91aceed --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/resources/org/springframework/boot/autoconfigure/mail/ssl/test-ca.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC6w3YrNBBRTFlK +Ijdlm5miEXCrYpdJQ090nquAPf9go1MNJlcpk87neRg8/QNd/SybcIdzJZJZVpvK +KZli49Z6drUwSVIvhmkn1bCRmg5Dds8UrW63PqQw7CuABRyTLwf+sgqPYkQGGRId +Qgavns2NQTAbXKBpJ6szxHNbDU+0yr4Lws67e/3irMTc0ODVBGnK9X06awuyMhJ3 +RmYAk2L2+aIQf3bcqc/KKqdCZYqliJx2BCZfUZfKVLbaNmoDBo6RgaNfxR17K+ui +GRmQzSGf25pK59AYRbjIoAKfc+SMm3OLx2sEjKMiL1hwDMHFkH2hUqpdkyWW2A7Z +8MyYgmLQ2C3QKLt8KgQypKhwrIMowvpguv1Ra3Hht+yuT6tU71D8P+/xEqCy2/t0 +J/YpZFfdkkQLQgEdLmAs2rSqqpVwbHctxLKF2Iok61LACc2QcyH6NJDEtKUGNxKp +M+IZNEr9F12X7GdT+YGCTtkEY2tQVoPkxwwWN11LsiHzZoeelDWB6RadQa/5Ob8b +ryHYG9nlTar5F944KBFb2L8BhbaOBPsgWd+PJReAFC95q4wByGiaoURUt73lmBWI +Xq+YffgQqjfD0vlNCLCT+vndiUztNI5PIOrHrq3jMZgOxUyJykqbOLjZGnwSkSsB +FHJERQgpK9az07365Pf7tqs0uAd7kQIDAQABAoICAAthB10ggfICHdqXdRqavWST +fXLjweXz1O59EGPy4xFnQhMmB99/ovaVeTWWENN0LniWBZqtalpJHZrWqALPcOzr +OKTlgr1kihmkOmrUoRPZNErFOl6t0WEtsoTNSu1oyyrofB46VXytoF3p/PBMU6fM +lfrEzP07LoIr8P9WM0oHpEahKulfZ5uc/S2bCGfSKgP0qxmZFhBYXqmnv2U/laMI +mKg6q+pL6l4d9SzldOobBbVnEVNzbDUmrjFjaVgf2SXiaSrXnrE3ftbUgqtA5FCS +F7eCojooXVbT8PT4Ia+zdPnKP6n6S6I0kkXZcSDxacYffEPRSFQFe/opYr3UC+Mk +1/UmOnoI8X8+N9SPcVD9cbVQUzBuuXfTy+LMx9mg3QxFebRSRre22xSOSlM7MF9B +6MPeNgwCk3Z0NTr+IedGfyA+d6+iHTMGnv0hF4b4UkcXbC3HdeR3K4hf+msGD2oG +7JF423T/d7t+g883y4CZm7p096apR8cCLIe2HKSwcYbKhft7LkAdm8kpnqkr5ER1 +anI7RDmucrx3HgrXeuCz9Uai6EMU6jNU1MAEBVeu4jz1rlO4e9zS2Ak68AwIz0zI +tl5el3paHjlRYY6YTslM5qjGerJt19IyHvZxXXIzF7JdF7w1nSK9bjvninALJl49 +YZAPRIbyQ8P6DLqiDNBFAoIBAQDvQoow86vNg6zHdb8eBC10l2Y6M5DAKTWPE8RJ +n0td1TLwEHzKvkR25v6yGKABbBO1+7ABACCqA8rkcB7M5jugak/kR9vuDrFPAsqf +lgckf1Up7ekDheTH8X1VSDiRZPv07UElO0M3aFeMVR/xi9Wae8C3WZo9dT2wKnM0 +d0Acr4Kt4SYm1Dw7kuh+Y1L/vvWuryPm1btxhfKO6JN5v2W8DTrqVkxuxYEM1VnR +69LfauLVico2q8EGXmQTth/Iok5wj1qI6kmrlgQR+eSY1qgNk1qzwjJVsbSmAOL8 +6Y9Ksct53bEN6DIdYRE/SrEVCz/FY1Pry2DNTjdiwImaSOZ3AoIBAQDH1KRkqsET +YUnPJxp9pHWlynicEVE/Y7FFhhtpUKzhY1nZ+NsNy91FrZiyx5Os7pSxhLNID8g5 +xKCOfYd7qdvZCg/5bMXhtagQ3gwa/wyuyamc29dKkCpHDz/GkoEkgVe6eYu1GNdR +iNpY5ye5T9fBE1s3odbDcnRVeHAP7vqz5z17JKrlqZVhbLYlR4qGHmAogq7vWlyd +IR5qLoXMgyqq5OHl1GaaiqfViBpJeoEWYze0cARUWOcrJRblJYS03WHMuLDG5RZd +5nmf2xwEcMgW5AX7+GB8CdXRVZy6OZcGn7TU9+xnBJA2LbzxJlHBXjWEd8Uma2Al ++ohlDbGrd8g3AoIBAHsWzGlqstREDbt/xBb5Jzl4OktvA+UYTkmRbcZCgU+Aw3fl +w426XRaeuCF/sbGJnIpfNakOG7/bu6HSXMYlHD/m8bsLjQXn4Sg4021OjdYk+/da +Qiph09VZU5VwVknWnhjfhkhVOLtknsW/dXOa8QVM7VRmcId1rYrYC/TN9NnNIXm6 +/xmyzloHtjxvdN/Fqjd4OwwioRBCTQtgc56K7RfV5p1wUFocmcu0Z0UsAYyXPKOH +A9Ukf2V7YhkR9UAO4DPgTD9r6QKxZt6opQZMSKDTUjJwkdysU7ejdSOQNPvEhF3p +w5DYCBA9Q9Y/4uJkqyYtd5szQlXdC3lufFw3bPkCggEBAKPA3GpmB0xjWEG6UJoP +UB1pWwbBpivk/Rr097eI1fLpIHNf29plalE0HcK7i4eWByGllekCjdjRCaVattCe +9DraZRbHjS0WWMBhxdfFk9YUCbsx6C4BD7QlieSmn8+TcpmsCtF/psr4870Qx9uy +0yI0Q3bGV6DYRP7ZcDOOacFNSHOGK8mB+5jXpjfMdXbMo43u8X3RNb3JqwvmTdy2 +zBs47ukQ8nfIEhsIqkn2apw2+CoT9WhNZjpT7XwgD6zLEd7apnqGtpqCSL63pjD5 +Xu5rM4A1HJPo11/w4Ts2AE38SAqRlBcjhS3wszmGZk6obgC8yUFfkm3s7SKqYyMZ +SGcCggEBAO0IDB/h1meZ2y+6bSsCVaDSxdRl0JF0CDUYVTANQsJ+q7u7CpF9xOo8 +YNrSy8eM0K6RMY/3WbTm+4z9tOldxEV2dn+29oVeMKkgpJYo0k2Au3wTMI2xMyyl +HZ+ZttsqSZsj2CPx83LMaPwKdzVjwA7alVx4P+AkQKn7jGJgidj5xyw0G3gnzdfT +nGzuitQFlcrcPyrVHAAmRhIw+B5CsvMFlM8PAvojN7burGswjWGeZjkgqoLvKlgq +jRMGzLTzF9Pay7P/D/pWQwPVGiseJq+QVIA+iILpy9Zb9T6DnBFaPFGOKAduzVU9 +lTLiho2DATppaxNUQKh/5k70hzbipDg= +-----END PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/resources/org/springframework/boot/autoconfigure/mail/ssl/test-client.crt b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/resources/org/springframework/boot/autoconfigure/mail/ssl/test-client.crt new file mode 100644 index 000000000000..811d880fcbd3 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/resources/org/springframework/boot/autoconfigure/mail/ssl/test-client.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEWjCCAkKgAwIBAgIURBZvq442tp+/K9TZII5Vy/LzVx0wDQYJKoZIhvcNAQEL +BQAwOzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlm +aWNhdGUgQXV0aG9yaXR5MB4XDTI0MDUwMTE2NTMyNVoXDTM0MDQyOTE2NTMyNVow +LzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDESMBAGA1UEAwwJbG9jYWxob3N0 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvGb7tu0odSuOjeY1lHlh +sRR4PayAvlryjfrrp49hjoVTiL3d/Jo6Po5HlqwJcYuclm0EWQR5Vur/zYJpfUE7 +b8+E9Qwe50+YzfQ2tVFEdq/VfqemrYRGee+pMelOCI90enOKCxfpo6EHbz+WnUP0 +mnD8OAF9QpolSdWAMOGJoPdWX65KQvyMXvQbj9VIHmsx7NCaIOYxjHXB/dI2FmXV ++m4VT6mb8he9dXmgK/ozMq6XIPOAXe0n3dlfMTSEddeNeVwnBpr/n5e0cpwGFhdf +NNu5CI4ecipBhXljJi/4/47M/6hd69HwE05C4zyH4ZDZ2JTfaSKOLV+jYdBUqJP5 +dwIDAQABo2IwYDALBgNVHQ8EBAMCBaAwEQYJYIZIAYb4QgEBBAQDAgeAMB0GA1Ud +DgQWBBRWiWOo9cm2IF/ZlhWLVjifLzYa/DAfBgNVHSMEGDAWgBRVMLDVqPECWaH6 +GruL9E52VcTrPjANBgkqhkiG9w0BAQsFAAOCAgEAA5Wphtu2nBhY+QNOBOwXq4zF +N5qt2IYTLfR7xqpKhhXx9VkIjdPWpcsGuCuMmfPVNvQWE6iK0/jMMqToTj4H6K7e +MN74j0GwwcknT1P42tUzEpg8LKR8VMdhWhyqdniCDNWWuaz1iVSoF0S2i4jFSzH5 +1q3KMKMZ4niK5aJI0fAGa4fCjyuun1Mfg/qGBGwLnqDkIXjeAopZf4Jb64TtzjAs +j9NT6mYbe3E0tw3fHT9ihYdbZDZgSjeCsuq9OiRMVb0DWWmRoLmmOrlN8IJlHV/3 +WyI/ta4Cw5EZ0oaOg0lIyOxXyvElth1xIvh+kdqZSBsU0gNBri6ZIzYbbTh2KTTO +BJHQt9L5naWG27pDrIxBicWXS/MIYonktm3YgCLfuW3kWcVk8bIlNhfcoAYBBgfM +IEYSYEq+bH2IQ+YoWQz3AxjJ8gEuuSUP6R6mYY65FfpjkKgcpGBvw4EIAmqKDtPS +hlLY/F0XVj9KZzrMyH4/vonu+DAb/P7Zmt2fyk/dQO6bAc3ltRmJbJm4VJ2v/T8I +LVu2FtcUYgtLNtkWUPfdb3GSUUgkKlUpWSty31TKSUszJjW1oRykQhEko6o5U3S8 +ptQzXdApsb1lGOqewkubE25tIu2RLiNkKcjFOjJ/lu0vP9k76wWwRVnFLFvfo4lW +pgywiOifs5JbcCt0ZQ0= +-----END CERTIFICATE----- diff --git a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/resources/org/springframework/boot/autoconfigure/mail/ssl/test-client.key b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/resources/org/springframework/boot/autoconfigure/mail/ssl/test-client.key new file mode 100644 index 000000000000..2ae0f49bf4a4 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/resources/org/springframework/boot/autoconfigure/mail/ssl/test-client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8Zvu27Sh1K46N +5jWUeWGxFHg9rIC+WvKN+uunj2GOhVOIvd38mjo+jkeWrAlxi5yWbQRZBHlW6v/N +gml9QTtvz4T1DB7nT5jN9Da1UUR2r9V+p6athEZ576kx6U4Ij3R6c4oLF+mjoQdv +P5adQ/SacPw4AX1CmiVJ1YAw4Ymg91ZfrkpC/Ixe9BuP1UgeazHs0Jog5jGMdcH9 +0jYWZdX6bhVPqZvyF711eaAr+jMyrpcg84Bd7Sfd2V8xNIR11415XCcGmv+fl7Ry +nAYWF18027kIjh5yKkGFeWMmL/j/jsz/qF3r0fATTkLjPIfhkNnYlN9pIo4tX6Nh +0FSok/l3AgMBAAECggEABXnBe3MwXAMQENzNypOiXK4VE3XMYkePfdsSK163byOD +w3ZeTgQNfU4g8LJK8/homzO0SQIJAdz2+ZFbpsp4A2W2zJ+1jvN5RuX/8/UcVhmk +tb1IL/LWCvx5/aoYBWkgIA70UfQJa2jDbdM0v5j/Gu9yE7GI14jh6DFC3xGMGV3b +fOwManxf7sDibCI1nGjnFYNGxninRr+tpb+a1KNbVzhett68LrgPmtph6B3HCPAJ +zBigk1Phgb8WHozTXxnLyw9/RdKJ0Ro4PFmtQv0EvCSlytptnF+0nXkqr3f851XS +bUWwYFchIFWPMhPfD5B3niNWCV42/sU/bQlk+BMQAQKBgQD6NvMq8EdYy2Y7fXT5 +FgB4s+7EkLgI2d5LUaCXCFgc6iZtCTQKUXj1rIWeRfGrFVCCe8qV+XIMKt/G5eEi +tn5ifHhktA2A8GK1scj026qHP3bVn0hMaUnkCF1UpDRKPiEO5G/apPtav8PbCNaX +GAimLGw+WZNZuv7+T33bEBeUdwKBgQDAwiidayLXkRkz2deefdDKcXQsB7RHFGGy +vfZPBCGqizxml+6ojJkkDsVUKL1IXFfyK9KpQAI6tezn4oktgu4jAQqkYY7QZobs +RpQx1dR+KxEm7ISDBTq/B1Q9cFKUKVvQQy8N2pnIbCdzb6MTOKLmJqFGTjr+5T8q +F32B5vkDAQKBgDCKfH42AwFc5EZiPlEcTZcdARMtKCa/bXqbKVZjjgR+AFpi0K+3 +womWoI1l8E5KYkYOEe0qaU+m+aaybgy37qjYkNqoe34qJFwvU1b9ToXScBFdRz9b +pbQRU1naSTKl/u/OrUxzeTfPwAU8H7VMOlFSiOVHp2he+J0JetcGtixdAoGBAIJQ +QMj7rxhxHcqyEVUy1b6nKNTDeJs9Kjd+uU/+CQyVCQaK3GvScY2w9rLIv/51f3dX +LRoDDf7HExxJSFgeVgQQJjOvSK+XQMvngzSVzQxm7TeVWpiBJpAS0l6e2xUTSODp +KpyBFsoqZBlkdaj+9xIFN66iILxGG4fHTbBOiDYBAoGBAOZMKjM5N/hGcCmik/6t +p/zBA2pN9O6zwPndITTsdyVWSlVqCZhXlRX47CerAN+/WVCidlh7Vp5Tuy75Wa77 +v16IDLO01txgWNobcLaM4VgFsyLi5JuxK73S18Vb1cKWdHFRF0LH3cUIq20fjpv6 +Odl4vjNOncXMZCLPHQ+bKWaf +-----END PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/resources/org/springframework/boot/autoconfigure/mail/ssl/test-server.crt b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/resources/org/springframework/boot/autoconfigure/mail/ssl/test-server.crt new file mode 100644 index 000000000000..57c66cc78a3b --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/resources/org/springframework/boot/autoconfigure/mail/ssl/test-server.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEWjCCAkKgAwIBAgIURBZvq442tp+/K9TZII5Vy/LzVxwwDQYJKoZIhvcNAQEL +BQAwOzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlm +aWNhdGUgQXV0aG9yaXR5MB4XDTI0MDUwMTE2NTMyNVoXDTM0MDQyOTE2NTMyNVow +LzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDESMBAGA1UEAwwJbG9jYWxob3N0 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsllxsSQzTTJlNHMfXC2b +CIXCPsfCgCBl7FbPz828jwJk+EYcXh0+WTFGks0WxSwb8NQza5UtyCUDEueZj9fV +j5mWBY97WCu01Sl/3xClHmYisXfyyv27GKec7PaSOurCm2JDkyHRNumiJROa4jte +N0GOHzw7FYsM3779TuNw14/gtW+eBrGnvgrpU7fbUvx42Di6ftGYQUwIi+3uIaqT +//i7ktDMaAQJtkL6haTzZ5JN2qKO5a34/WRz/ApvPw3lpDV8c4qoTk3C0Bg9MP+a +DnZtjtLBSN9CJWwr+n11QaMgHTotEKsOahGdi3J2zYxCvJP0LT+hjN2O9aRzSMIs +MwIDAQABo2IwYDALBgNVHQ8EBAMCBaAwEQYJYIZIAYb4QgEBBAQDAgZAMB0GA1Ud +DgQWBBS9XQHGwJZhG0olAGM1UMNuwZ65DzAfBgNVHSMEGDAWgBRVMLDVqPECWaH6 +GruL9E52VcTrPjANBgkqhkiG9w0BAQsFAAOCAgEAhBcqm5UQahn8iFMETXvfLMR6 +OOPijsHQ5lVfhig08s46a9O5eaJ9EYSYyiDnxYvZ4gYVH03f/kPwNLamvGR5KIBQ +R0DltkPPX4a11/vjwlSq1cXAt9r59nY+sNcVXWgIWH7zNodL8lyTpYhqvB2wEQkx +t2/JKZ8A0sGjed4S6I5HofYd7bnBxQZgfZShQ2SdDbzbcyg4SCEb8ghwnsH0KNZo +jJF+20RpK2VMViE6lylLTEMd/PyAdST/NPoqVxyva3QjTrKt+tkkFTsmNVMXcmYC +f1xo1/YFp73FFE63VYFI+Yw+Ajau8sYSo4+YvgFCy+Efhf3h3GFDtaiNod56uX9G +9M/cu8XsFzFP2e/0YWY3XL+v7ESOdc3g7yS4FQZ7Z6YvfAed9hCB25cDECvZXqJG +HSYDR38NHyAPROuCwlEwDyVmWRl9bpwZt+hr9kaTQScIDx+rV/EF3o0GKIwtR7AK +jaPAta0f4/Uu+EuWAcccSRUMtfx5/Jse/6iliBvy7JXmA+Y0PrT7K4uHO7iktdI+ +x8WbfZKfnLVuqw5fneTjC1n48Ltjis/f8DgO7BuWTmLdZXddjqqxzBSukFTBn4Hg +/oSg3XiMywOAVrRCNJehcdTG0u/BqZsrRjcYAJaf5qG/0tMLNsuF9Y53XQQAeezE +etL+7y0mkeQhVF+Kmy4= +-----END CERTIFICATE----- diff --git a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/resources/org/springframework/boot/autoconfigure/mail/ssl/test-server.key b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/resources/org/springframework/boot/autoconfigure/mail/ssl/test-server.key new file mode 100644 index 000000000000..95e2ef3e8b31 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/resources/org/springframework/boot/autoconfigure/mail/ssl/test-server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQCyWXGxJDNNMmU0 +cx9cLZsIhcI+x8KAIGXsVs/PzbyPAmT4RhxeHT5ZMUaSzRbFLBvw1DNrlS3IJQMS +55mP19WPmZYFj3tYK7TVKX/fEKUeZiKxd/LK/bsYp5zs9pI66sKbYkOTIdE26aIl +E5riO143QY4fPDsViwzfvv1O43DXj+C1b54Gsae+CulTt9tS/HjYOLp+0ZhBTAiL +7e4hqpP/+LuS0MxoBAm2QvqFpPNnkk3aoo7lrfj9ZHP8Cm8/DeWkNXxziqhOTcLQ +GD0w/5oOdm2O0sFI30IlbCv6fXVBoyAdOi0Qqw5qEZ2LcnbNjEK8k/QtP6GM3Y71 +pHNIwiwzAgMBAAECgf9REZuCvy2Bi8SoTnjqQuHG5FuA6cPuisuFZr1k88IO+zJQ +uY3WKNs29BV+LcxnoK29W8jQnjqPHXcMfrF5dVWmkrrJdu8JLaGWVHF+uBq8nRb0 +2LvREh5XhZTGzIESNdc/7GIxdouag/8FlzCUYQGuT3v9+wUCiim+4CuIuPvv7ncD +8vANe3Ua5G0mHjVshOiMNpegg45zYlzYpMtUFPs+asLilW6A7UlgC+pLZ1cHUUlU +ZB7KOGT9JdrZpilTidl6LLvDDQK30TSWz8A26SuEAE71DR2VEjLVpjTNS76vlx+c +CrYr/WwpMb0xul+e/uHiNgo+51FiTiJ/IfuGeskCgYEA804CXQM6i5m4/Upps2yG +aTae5xBaYUquZREp5Zb054U6lUAHI41iTMTIwTTvWn5ogNojgi+YjljkzRj2RQ5k +NccBkjBBwwUNVWpBoGeZ73KAdejNB4C4ucGc2kkqEDo4MU5x3IE4JK1Yi1jl9mKb +IR6m3pqb2PCQHjO8sqKNHYkCgYEAu6fH/qUd/XGmCZJWY5K6jg3dISXH16MTO5M+ +jetprkGMMybWKZQa1GedXurPexE48oRlRhkjdQkW6Wcj1Qh6OKp6N2Zx8sY4dLeQ +yVChnMPFE2LK+UlRCKJUZi+rzX415ML6pZg+yW7O2cHpMKv7PlXISw2YDqtboCAi +Y+doqNsCgYBE1yqmBJbZDuqfiCF2KduyA0lcmWzpIEdNw1h2ZIrwwup7dj1O2t8Y +V4lx2TdsBF4vLwli+XKRvCcovMpZaaQC70bLhSnmMxS9uS3OY+HTNTORqQfx+oLJ +1DU8Mf1b0A08LjTbLhijkASAkOuoFehMq66NR3OXIyGz2fGnHYUN+QKBgCC47SL2 +X/hl7PIWVoIef/FtcXXqRKLRiPUGhA3zUwZT38K7rvSpItSPDN4UTAHFywxfEdnb +YFd0Mk6Y8aKgS8+9ynoGnzAaaJXRvKmeKdBQQvlSbNpzcnHy/IylG2xF6dfuOA7Q +MYKmk+Nc8PDPzIveIYMU58MHFn8hm12YaKOpAoGAV1CE8hFkEK9sbRGoKNJkx9nm +CZTv7PybaG/RN4ZrBSwVmnER0FEagA/Tzrlp1pi3sC8ZsC9onSOf6Btq8ZE0zbO1 +vsAm3gTBXcrCJxzw0Wjt8pzEbk3yELm4WE6VDEx4da2jWocdspslpIwdjHnPwsbH +r5O3ZAgigZs/ZtKW/U4= +-----END PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/BackgroundPreinitializer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/BackgroundPreinitializer.java index 8ddac8fa3631..36ec4abfef6c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/BackgroundPreinitializer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/BackgroundPreinitializer.java @@ -17,11 +17,14 @@ package org.springframework.boot.autoconfigure; import java.nio.charset.StandardCharsets; +import java.time.ZoneId; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import jakarta.validation.Configuration; import jakarta.validation.Validation; +import org.apache.catalina.authenticator.NonLoginAuthenticator; +import org.apache.tomcat.util.http.Rfc6265CookieProcessor; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationFailedEvent; @@ -107,6 +110,8 @@ public void run() { runSafely(new JacksonInitializer()); } runSafely(new CharsetInitializer()); + runSafely(new TomcatInitializer()); + runSafely(new JdkInitializer()); preinitializationComplete.countDown(); } @@ -189,4 +194,23 @@ public void run() { } + private static final class TomcatInitializer implements Runnable { + + @Override + public void run() { + new Rfc6265CookieProcessor(); + new NonLoginAuthenticator(); + } + + } + + private static final class JdkInitializer implements Runnable { + + @Override + public void run() { + ZoneId.systemDefault(); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/AbstractRabbitListenerContainerFactoryConfigurer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/AbstractRabbitListenerContainerFactoryConfigurer.java index feab224f2ca7..dd91f59e90bf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/AbstractRabbitListenerContainerFactoryConfigurer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/AbstractRabbitListenerContainerFactoryConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.amqp; import java.util.List; +import java.util.concurrent.Executor; import org.springframework.amqp.rabbit.config.AbstractRabbitListenerContainerFactory; import org.springframework.amqp.rabbit.config.RetryInterceptorBuilder; @@ -47,6 +48,8 @@ public abstract class AbstractRabbitListenerContainerFactoryConfigurer r this.retryTemplateCustomizers = retryTemplateCustomizers; } + /** + * Set the task executor to use. + * @param taskExecutor the task executor + * @since 3.2.0 + */ + public void setTaskExecutor(Executor taskExecutor) { + this.taskExecutor = taskExecutor; + } + protected final RabbitProperties getRabbitProperties() { return this.rabbitProperties; } @@ -118,6 +130,11 @@ protected void configure(T factory, ConnectionFactory connectionFactory, } factory.setMissingQueuesFatal(configuration.isMissingQueuesFatal()); factory.setDeBatchingEnabled(configuration.isDeBatchingEnabled()); + factory.setForceStop(configuration.isForceStop()); + if (this.taskExecutor != null) { + factory.setTaskExecutor(this.taskExecutor); + } + factory.setObservationEnabled(configuration.isObservationEnabled()); ListenerRetry retryConfig = configuration.getRetry(); if (retryConfig.isEnabled()) { RetryInterceptorBuilder builder = (retryConfig.isStateless()) ? RetryInterceptorBuilder.stateless() diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAnnotationDrivenConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAnnotationDrivenConfiguration.java index 51decebff512..65948817aae6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAnnotationDrivenConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAnnotationDrivenConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,14 +30,18 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading; +import org.springframework.boot.autoconfigure.thread.Threading; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.VirtualThreadTaskExecutor; /** * Configuration for Spring AMQP annotation driven endpoints. * * @author Stephane Nicoll * @author Josh Thornhill + * @author Moritz Halbritter */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(EnableRabbit.class) @@ -62,12 +66,17 @@ class RabbitAnnotationDrivenConfiguration { @Bean @ConditionalOnMissingBean + @ConditionalOnThreading(Threading.PLATFORM) SimpleRabbitListenerContainerFactoryConfigurer simpleRabbitListenerContainerFactoryConfigurer() { - SimpleRabbitListenerContainerFactoryConfigurer configurer = new SimpleRabbitListenerContainerFactoryConfigurer( - this.properties); - configurer.setMessageConverter(this.messageConverter.getIfUnique()); - configurer.setMessageRecoverer(this.messageRecoverer.getIfUnique()); - configurer.setRetryTemplateCustomizers(this.retryTemplateCustomizers.orderedStream().toList()); + return simpleListenerConfigurer(); + } + + @Bean(name = "simpleRabbitListenerContainerFactoryConfigurer") + @ConditionalOnMissingBean + @ConditionalOnThreading(Threading.VIRTUAL) + SimpleRabbitListenerContainerFactoryConfigurer simpleRabbitListenerContainerFactoryConfigurerVirtualThreads() { + SimpleRabbitListenerContainerFactoryConfigurer configurer = simpleListenerConfigurer(); + configurer.setTaskExecutor(new VirtualThreadTaskExecutor("rabbit-simple-")); return configurer; } @@ -86,12 +95,17 @@ SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory( @Bean @ConditionalOnMissingBean + @ConditionalOnThreading(Threading.PLATFORM) DirectRabbitListenerContainerFactoryConfigurer directRabbitListenerContainerFactoryConfigurer() { - DirectRabbitListenerContainerFactoryConfigurer configurer = new DirectRabbitListenerContainerFactoryConfigurer( - this.properties); - configurer.setMessageConverter(this.messageConverter.getIfUnique()); - configurer.setMessageRecoverer(this.messageRecoverer.getIfUnique()); - configurer.setRetryTemplateCustomizers(this.retryTemplateCustomizers.orderedStream().toList()); + return directListenerConfigurer(); + } + + @Bean(name = "directRabbitListenerContainerFactoryConfigurer") + @ConditionalOnMissingBean + @ConditionalOnThreading(Threading.VIRTUAL) + DirectRabbitListenerContainerFactoryConfigurer directRabbitListenerContainerFactoryConfigurerVirtualThreads() { + DirectRabbitListenerContainerFactoryConfigurer configurer = directListenerConfigurer(); + configurer.setTaskExecutor(new VirtualThreadTaskExecutor("rabbit-direct-")); return configurer; } @@ -107,6 +121,24 @@ DirectRabbitListenerContainerFactory directRabbitListenerContainerFactory( return factory; } + private SimpleRabbitListenerContainerFactoryConfigurer simpleListenerConfigurer() { + SimpleRabbitListenerContainerFactoryConfigurer configurer = new SimpleRabbitListenerContainerFactoryConfigurer( + this.properties); + configurer.setMessageConverter(this.messageConverter.getIfUnique()); + configurer.setMessageRecoverer(this.messageRecoverer.getIfUnique()); + configurer.setRetryTemplateCustomizers(this.retryTemplateCustomizers.orderedStream().toList()); + return configurer; + } + + private DirectRabbitListenerContainerFactoryConfigurer directListenerConfigurer() { + DirectRabbitListenerContainerFactoryConfigurer configurer = new DirectRabbitListenerContainerFactoryConfigurer( + this.properties); + configurer.setMessageConverter(this.messageConverter.getIfUnique()); + configurer.setMessageRecoverer(this.messageRecoverer.getIfUnique()); + configurer.setRetryTemplateCustomizers(this.retryTemplateCustomizers.orderedStream().toList()); + return configurer; + } + @Configuration(proxyBeanMethods = false) @EnableRabbit @ConditionalOnMissingBean(name = RabbitListenerConfigUtils.RABBIT_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java index 1d924f8fcb7d..5e3039598ffc 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,6 +38,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.ssl.SslBundles; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -69,6 +70,7 @@ * @author Chris Bono * @author Moritz Halbritter * @author Andy Wilkinson + * @author Scott Frederick * @since 1.0.0 */ @AutoConfiguration @@ -96,9 +98,10 @@ RabbitConnectionDetails rabbitConnectionDetails() { @ConditionalOnMissingBean RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader, RabbitConnectionDetails connectionDetails, ObjectProvider credentialsProvider, - ObjectProvider credentialsRefreshService) { + ObjectProvider credentialsRefreshService, + ObjectProvider sslBundles) { RabbitConnectionFactoryBeanConfigurer configurer = new RabbitConnectionFactoryBeanConfigurer(resourceLoader, - this.properties, connectionDetails); + this.properties, connectionDetails, sslBundles.getIfAvailable()); configurer.setCredentialsProvider(credentialsProvider.getIfUnique()); configurer.setCredentialsRefreshService(credentialsRefreshService.getIfUnique()); return configurer; @@ -120,17 +123,14 @@ CachingConnectionFactory rabbitConnectionFactory( RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer, CachingConnectionFactoryConfigurer rabbitCachingConnectionFactoryConfigurer, ObjectProvider connectionFactoryCustomizers) throws Exception { - - RabbitConnectionFactoryBean connectionFactoryBean = new RabbitConnectionFactoryBean(); + RabbitConnectionFactoryBean connectionFactoryBean = new SslBundleRabbitConnectionFactoryBean(); rabbitConnectionFactoryBeanConfigurer.configure(connectionFactoryBean); connectionFactoryBean.afterPropertiesSet(); com.rabbitmq.client.ConnectionFactory connectionFactory = connectionFactoryBean.getObject(); connectionFactoryCustomizers.orderedStream() .forEach((customizer) -> customizer.customize(connectionFactory)); - CachingConnectionFactory factory = new CachingConnectionFactory(connectionFactory); rabbitCachingConnectionFactoryConfigurer.configure(factory); - return factory; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitConnectionFactoryBeanConfigurer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitConnectionFactoryBeanConfigurer.java index f54e91ace5c2..2f59e2d8faed 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitConnectionFactoryBeanConfigurer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitConnectionFactoryBeanConfigurer.java @@ -24,8 +24,11 @@ import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean; import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails.Address; import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; +import org.springframework.util.unit.DataSize; /** * Configures {@link RabbitConnectionFactoryBean} with sensible defaults. @@ -34,6 +37,7 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick * @since 2.6.0 */ public class RabbitConnectionFactoryBeanConfigurer { @@ -44,6 +48,8 @@ public class RabbitConnectionFactoryBeanConfigurer { private final RabbitConnectionDetails connectionDetails; + private final SslBundles sslBundles; + private CredentialsProvider credentialsProvider; private CredentialsRefreshService credentialsRefreshService; @@ -64,17 +70,33 @@ public RabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader, Rabb * priority over the properties. * @param resourceLoader the resource loader * @param properties the properties - * @param connectionDetails the connection details. + * @param connectionDetails the connection details * @since 3.1.0 */ public RabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader, RabbitProperties properties, RabbitConnectionDetails connectionDetails) { + this(resourceLoader, properties, connectionDetails, null); + } + + /** + * Creates a new configurer that will use the given {@code resourceLoader}, + * {@code properties}, {@code connectionDetails}, and {@code sslBundles}. The + * connection details have priority over the properties. + * @param resourceLoader the resource loader + * @param properties the properties + * @param connectionDetails the connection details + * @param sslBundles the SSL bundles + * @since 3.2.0 + */ + public RabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader, RabbitProperties properties, + RabbitConnectionDetails connectionDetails, SslBundles sslBundles) { Assert.notNull(resourceLoader, "ResourceLoader must not be null"); Assert.notNull(properties, "Properties must not be null"); Assert.notNull(connectionDetails, "ConnectionDetails must not be null"); this.resourceLoader = resourceLoader; this.rabbitProperties = properties; this.connectionDetails = connectionDetails; + this.sslBundles = sslBundles; } public void setCredentialsProvider(CredentialsProvider credentialsProvider) { @@ -110,15 +132,23 @@ public void configure(RabbitConnectionFactoryBean factory) { RabbitProperties.Ssl ssl = this.rabbitProperties.getSsl(); if (ssl.determineEnabled()) { factory.setUseSSL(true); - map.from(ssl::getAlgorithm).whenNonNull().to(factory::setSslAlgorithm); - map.from(ssl::getKeyStoreType).to(factory::setKeyStoreType); - map.from(ssl::getKeyStore).to(factory::setKeyStore); - map.from(ssl::getKeyStorePassword).to(factory::setKeyStorePassphrase); - map.from(ssl::getKeyStoreAlgorithm).whenNonNull().to(factory::setKeyStoreAlgorithm); - map.from(ssl::getTrustStoreType).to(factory::setTrustStoreType); - map.from(ssl::getTrustStore).to(factory::setTrustStore); - map.from(ssl::getTrustStorePassword).to(factory::setTrustStorePassphrase); - map.from(ssl::getTrustStoreAlgorithm).whenNonNull().to(factory::setTrustStoreAlgorithm); + if (ssl.getBundle() != null) { + SslBundle bundle = this.sslBundles.getBundle(ssl.getBundle()); + if (factory instanceof SslBundleRabbitConnectionFactoryBean sslFactory) { + sslFactory.setSslBundle(bundle); + } + } + else { + map.from(ssl::getAlgorithm).whenNonNull().to(factory::setSslAlgorithm); + map.from(ssl::getKeyStoreType).to(factory::setKeyStoreType); + map.from(ssl::getKeyStore).to(factory::setKeyStore); + map.from(ssl::getKeyStorePassword).to(factory::setKeyStorePassphrase); + map.from(ssl::getKeyStoreAlgorithm).whenNonNull().to(factory::setKeyStoreAlgorithm); + map.from(ssl::getTrustStoreType).to(factory::setTrustStoreType); + map.from(ssl::getTrustStore).to(factory::setTrustStore); + map.from(ssl::getTrustStorePassword).to(factory::setTrustStorePassphrase); + map.from(ssl::getTrustStoreAlgorithm).whenNonNull().to(factory::setTrustStoreAlgorithm); + } map.from(ssl::isValidateServerCertificate) .to((validate) -> factory.setSkipServerCertificateValidation(!validate)); map.from(ssl::getVerifyHostname).to(factory::setEnableHostnameVerification); @@ -133,6 +163,10 @@ public void configure(RabbitConnectionFactoryBean factory) { .to(factory::setChannelRpcTimeout); map.from(this.credentialsProvider).whenNonNull().to(factory::setCredentialsProvider); map.from(this.credentialsRefreshService).whenNonNull().to(factory::setCredentialsRefreshService); + map.from(this.rabbitProperties.getMaxInboundMessageBodySize()) + .whenNonNull() + .asInt(DataSize::toBytes) + .to(factory::setMaxInboundMessageBodySize); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitProperties.java index 53e88d0ac741..abc949cad3b6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitProperties.java @@ -31,6 +31,7 @@ import org.springframework.boot.convert.DurationUnit; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import org.springframework.util.unit.DataSize; /** * Configuration properties for Rabbit. @@ -45,7 +46,9 @@ * @author Franjo Zilic * @author Eddú Meléndez * @author Rafael Carvalho + * @author Scott Frederick * @author Lasse Wulff + * @author Yanming Zhou * @since 1.0.0 */ @ConfigurationProperties(prefix = "spring.rabbitmq") @@ -131,6 +134,11 @@ public class RabbitProperties { */ private Duration channelRpcTimeout = Duration.ofMinutes(10); + /** + * Maximum size of the body of inbound (received) messages. + */ + private DataSize maxInboundMessageBodySize = DataSize.ofMegabytes(64); + /** * Cache configuration. */ @@ -361,6 +369,14 @@ public void setChannelRpcTimeout(Duration channelRpcTimeout) { this.channelRpcTimeout = channelRpcTimeout; } + public DataSize getMaxInboundMessageBodySize() { + return this.maxInboundMessageBodySize; + } + + public void setMaxInboundMessageBodySize(DataSize maxInboundMessageBodySize) { + this.maxInboundMessageBodySize = maxInboundMessageBodySize; + } + public Cache getCache() { return this.cache; } @@ -387,6 +403,11 @@ public class Ssl { */ private Boolean enabled; + /** + * SSL bundle name. + */ + private String bundle; + /** * Path to the key store that holds the SSL certificate. */ @@ -454,7 +475,7 @@ public Boolean getEnabled() { * @see #getEnabled() () */ public boolean determineEnabled() { - boolean defaultEnabled = Optional.ofNullable(getEnabled()).orElse(false); + boolean defaultEnabled = Optional.ofNullable(getEnabled()).orElse(false) || this.bundle != null; if (CollectionUtils.isEmpty(RabbitProperties.this.parsedAddresses)) { return defaultEnabled; } @@ -466,6 +487,14 @@ public void setEnabled(Boolean enabled) { this.enabled = enabled; } + public String getBundle() { + return this.bundle; + } + + public void setBundle(String bundle) { + this.bundle = bundle; + } + public String getKeyStore() { return this.keyStore; } @@ -691,6 +720,19 @@ public StreamContainer getStream() { public abstract static class BaseContainer { + /** + * Whether to enable observation. + */ + private boolean observationEnabled; + + public boolean isObservationEnabled() { + return this.observationEnabled; + } + + public void setObservationEnabled(boolean observationEnabled) { + this.observationEnabled = observationEnabled; + } + } public abstract static class AmqpContainer extends BaseContainer { @@ -727,6 +769,12 @@ public abstract static class AmqpContainer extends BaseContainer { */ private boolean deBatchingEnabled = true; + /** + * Whether the container (when stopped) should stop immediately after processing + * the current message or stop after processing all pre-fetched messages. + */ + private boolean forceStop; + /** * Optional properties for a retry interceptor. */ @@ -782,6 +830,14 @@ public void setDeBatchingEnabled(boolean deBatchingEnabled) { this.deBatchingEnabled = deBatchingEnabled; } + public boolean isForceStop() { + return this.forceStop; + } + + public void setForceStop(boolean forceStop) { + this.forceStop = forceStop; + } + public ListenerRetry getRetry() { return this.retry; } @@ -955,6 +1011,16 @@ public static class Template { */ private String defaultReceiveQueue; + /** + * Whether to enable observation. + */ + private boolean observationEnabled; + + /** + * Simple patterns for allowable packages/classes for deserialization. + */ + private List allowedListPatterns; + public Retry getRetry() { return this.retry; } @@ -1007,6 +1073,22 @@ public void setDefaultReceiveQueue(String defaultReceiveQueue) { this.defaultReceiveQueue = defaultReceiveQueue; } + public boolean isObservationEnabled() { + return this.observationEnabled; + } + + public void setObservationEnabled(boolean observationEnabled) { + this.observationEnabled = observationEnabled; + } + + public List getAllowedListPatterns() { + return this.allowedListPatterns; + } + + public void setAllowedListPatterns(List allowedListPatterns) { + this.allowedListPatterns = allowedListPatterns; + } + } public static class Retry { @@ -1193,6 +1275,12 @@ public static final class Stream { */ private int port = DEFAULT_STREAM_PORT; + /** + * Virtual host of a RabbitMQ instance with the Stream plugin enabled. When not + * set, spring.rabbitmq.virtual-host is used. + */ + private String virtualHost; + /** * Login user to authenticate to the broker. When not set, * spring.rabbitmq.username is used. @@ -1226,6 +1314,14 @@ public void setPort(int port) { this.port = port; } + public String getVirtualHost() { + return this.virtualHost; + } + + public void setVirtualHost(String virtualHost) { + this.virtualHost = virtualHost; + } + public String getUsername() { return this.username; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitStreamConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitStreamConfiguration.java index 6547cfdc4e9c..fa2ec57e7ff2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitStreamConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitStreamConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.springframework.amqp.rabbit.config.ContainerCustomizer; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.amqp.RabbitProperties.StreamContainer; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -57,7 +58,9 @@ StreamRabbitListenerContainerFactory streamRabbitListenerContainerFactory(Enviro ObjectProvider> containerCustomizer) { StreamRabbitListenerContainerFactory factory = new StreamRabbitListenerContainerFactory( rabbitStreamEnvironment); - factory.setNativeListener(properties.getListener().getStream().isNativeListener()); + StreamContainer stream = properties.getListener().getStream(); + factory.setObservationEnabled(stream.isObservationEnabled()); + factory.setNativeListener(stream.isNativeListener()); consumerCustomizer.ifUnique(factory::setConsumerCustomizer); containerCustomizer.ifUnique(factory::setContainerCustomizer); return factory; @@ -102,6 +105,10 @@ static EnvironmentBuilder configure(EnvironmentBuilder builder, RabbitProperties PropertyMapper map = PropertyMapper.get(); map.from(stream.getHost()).to(builder::host); map.from(stream.getPort()).to(builder::port); + map.from(stream.getVirtualHost()) + .as(withFallback(properties::getVirtualHost)) + .whenNonNull() + .to(builder::virtualHost); map.from(stream.getUsername()).as(withFallback(properties::getUsername)).whenNonNull().to(builder::username); map.from(stream.getPassword()).as(withFallback(properties::getPassword)).whenNonNull().to(builder::password); return builder; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitTemplateConfigurer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitTemplateConfigurer.java index 6d20e66c7b4c..d002323c7416 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitTemplateConfigurer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitTemplateConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,14 +21,18 @@ import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.support.converter.AllowedListDeserializingMessageConverter; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; /** * Configure {@link RabbitTemplate} with sensible defaults. * * @author Stephane Nicoll + * @author Yanming Zhou * @since 2.3.0 */ public class RabbitTemplateConfigurer { @@ -101,6 +105,20 @@ public void configure(RabbitTemplate template, ConnectionFactory connectionFacto map.from(templateProperties::getExchange).to(template::setExchange); map.from(templateProperties::getRoutingKey).to(template::setRoutingKey); map.from(templateProperties::getDefaultReceiveQueue).whenNonNull().to(template::setDefaultReceiveQueue); + map.from(templateProperties::isObservationEnabled).to(template::setObservationEnabled); + map.from(templateProperties::getAllowedListPatterns) + .whenNot(CollectionUtils::isEmpty) + .to((allowedListPatterns) -> setAllowedListPatterns(template.getMessageConverter(), allowedListPatterns)); + } + + private void setAllowedListPatterns(MessageConverter messageConverter, List allowedListPatterns) { + if (messageConverter instanceof AllowedListDeserializingMessageConverter allowedListDeserializingMessageConverter) { + allowedListDeserializingMessageConverter.setAllowedListPatterns(allowedListPatterns); + return; + } + throw new InvalidConfigurationPropertyValueException("spring.rabbitmq.template.allowed-list-patterns", + allowedListPatterns, + "Allowed list patterns can only be applied to an AllowedListDeserializingMessageConverter"); } private boolean determineMandatoryFlag() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/SslBundleRabbitConnectionFactoryBean.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/SslBundleRabbitConnectionFactoryBean.java new file mode 100644 index 000000000000..526a187dd428 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/SslBundleRabbitConnectionFactoryBean.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.amqp; + +import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean; +import org.springframework.boot.ssl.SslBundle; + +/** + * A {@link RabbitConnectionFactoryBean} that can be configured with custom SSL trust + * material from an {@link SslBundle}. + * + * @author Scott Frederick + */ +class SslBundleRabbitConnectionFactoryBean extends RabbitConnectionFactoryBean { + + private SslBundle sslBundle; + + private boolean enableHostnameVerification; + + @Override + protected void setUpSSL() { + if (this.sslBundle != null) { + this.connectionFactory.useSslProtocol(this.sslBundle.createSslContext()); + if (this.enableHostnameVerification) { + this.connectionFactory.enableHostnameVerification(); + } + } + else { + super.setUpSSL(); + } + } + + void setSslBundle(SslBundle sslBundle) { + this.sslBundle = sslBundle; + } + + @Override + public void setEnableHostnameVerification(boolean enable) { + this.enableHostnameVerification = enable; + super.setEnableHostnameVerification(enable); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java index 7f31db1fb7a5..8635c1f9c5ea 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration; import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.ExecutionContextSerializer; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.ExitCodeGenerator; @@ -62,6 +63,8 @@ * @author Eddú Meléndez * @author Kazuki Shimizu * @author Mahmoud Ben Hassine + * @author Lars Uffmann + * @author Lasse Wulff * @since 1.0.0 */ @AutoConfiguration(after = { HibernateJpaAutoConfiguration.class, TransactionAutoConfiguration.class }) @@ -102,13 +105,19 @@ static class SpringBootBatchConfiguration extends DefaultBatchConfiguration { private final List batchConversionServiceCustomizers; + private final ExecutionContextSerializer executionContextSerializer; + SpringBootBatchConfiguration(DataSource dataSource, @BatchDataSource ObjectProvider batchDataSource, - PlatformTransactionManager transactionManager, BatchProperties properties, - ObjectProvider batchConversionServiceCustomizers) { + PlatformTransactionManager transactionManager, + @BatchTransactionManager ObjectProvider batchTransactionManager, + BatchProperties properties, + ObjectProvider batchConversionServiceCustomizers, + ObjectProvider executionContextSerializer) { this.dataSource = batchDataSource.getIfAvailable(() -> dataSource); - this.transactionManager = transactionManager; + this.transactionManager = batchTransactionManager.getIfAvailable(() -> transactionManager); this.properties = properties; this.batchConversionServiceCustomizers = batchConversionServiceCustomizers.orderedStream().toList(); + this.executionContextSerializer = executionContextSerializer.getIfAvailable(); } @Override @@ -142,6 +151,12 @@ protected ConfigurableConversionService getConversionService() { return conversionService; } + @Override + protected ExecutionContextSerializer getExecutionContextSerializer() { + return (this.executionContextSerializer != null) ? this.executionContextSerializer + : super.getExecutionContextSerializer(); + } + } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchTransactionManager.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchTransactionManager.java new file mode 100644 index 000000000000..208257106d52 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchTransactionManager.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.batch; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Primary; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * Qualifier annotation for a {@link PlatformTransactionManager} to be injected into Batch + * auto-configuration. Can be used on a secondary {@link PlatformTransactionManager}, if + * there is another one marked as {@link Primary @Primary}. + * + * @author Lasse Wulff + * @since 3.3.0 + */ +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Qualifier +public @interface BatchTransactionManager { + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/JobLauncherApplicationRunner.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/JobLauncherApplicationRunner.java index b4415ac365ef..a343346eb3e6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/JobLauncherApplicationRunner.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/JobLauncherApplicationRunner.java @@ -19,7 +19,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; @@ -230,7 +229,8 @@ private JobParameters getNextJobParameters(Job job, JobParameters jobParameters) private JobParameters getNextJobParametersForExisting(Job job, JobParameters jobParameters) { JobExecution lastExecution = this.jobRepository.getLastJobExecution(job.getName(), jobParameters); if (isStoppedOrFailed(lastExecution) && job.isRestartable()) { - JobParameters previousIdentifyingParameters = getGetIdentifying(lastExecution.getJobParameters()); + JobParameters previousIdentifyingParameters = new JobParameters( + lastExecution.getJobParameters().getIdentifyingParameters()); return merge(previousIdentifyingParameters, jobParameters); } return jobParameters; @@ -241,16 +241,6 @@ private boolean isStoppedOrFailed(JobExecution execution) { return (status == BatchStatus.STOPPED || status == BatchStatus.FAILED); } - private JobParameters getGetIdentifying(JobParameters parameters) { - HashMap> nonIdentifying = new LinkedHashMap<>(parameters.getParameters().size()); - parameters.getParameters().forEach((key, value) -> { - if (value.isIdentifying()) { - nonIdentifying.put(key, value); - } - }); - return new JobParameters(nonIdentifying); - } - private JobParameters merge(JobParameters parameters, JobParameters additionals) { Map> merged = new LinkedHashMap<>(); merged.putAll(parameters.getParameters()); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastJCacheCustomizationConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastJCacheCustomizationConfiguration.java index ff0206b2b755..7832328506e3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastJCacheCustomizationConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastJCacheCustomizationConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,21 +38,26 @@ class HazelcastJCacheCustomizationConfiguration { @Bean - HazelcastPropertiesCustomizer hazelcastPropertiesCustomizer(ObjectProvider hazelcastInstance) { - return new HazelcastPropertiesCustomizer(hazelcastInstance.getIfUnique()); + HazelcastPropertiesCustomizer hazelcastPropertiesCustomizer(ObjectProvider hazelcastInstance, + CacheProperties cacheProperties) { + return new HazelcastPropertiesCustomizer(hazelcastInstance.getIfUnique(), cacheProperties); } static class HazelcastPropertiesCustomizer implements JCachePropertiesCustomizer { private final HazelcastInstance hazelcastInstance; - HazelcastPropertiesCustomizer(HazelcastInstance hazelcastInstance) { + private final CacheProperties cacheProperties; + + HazelcastPropertiesCustomizer(HazelcastInstance hazelcastInstance, CacheProperties cacheProperties) { this.hazelcastInstance = hazelcastInstance; + this.cacheProperties = cacheProperties; } @Override - public void customize(CacheProperties cacheProperties, Properties properties) { - Resource configLocation = cacheProperties.resolveConfigLocation(cacheProperties.getJcache().getConfig()); + public void customize(Properties properties) { + Resource configLocation = this.cacheProperties + .resolveConfigLocation(this.cacheProperties.getJcache().getConfig()); if (configLocation != null) { // Hazelcast does not use the URI as a mean to specify a custom config. properties.setProperty("hazelcast.config.location", toUri(configLocation).toString()); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCacheCacheConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCacheCacheConfiguration.java index 07d9d3c4029b..e040229da3ec 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCacheCacheConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCacheCacheConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -95,7 +95,7 @@ CacheManager jCacheCacheManager(CacheProperties cacheProperties, private CacheManager createCacheManager(CacheProperties cacheProperties, ObjectProvider cachePropertiesCustomizers) throws IOException { CachingProvider cachingProvider = getCachingProvider(cacheProperties.getJcache().getProvider()); - Properties properties = createCacheManagerProperties(cachePropertiesCustomizers, cacheProperties); + Properties properties = createCacheManagerProperties(cachePropertiesCustomizers); Resource configLocation = cacheProperties.resolveConfigLocation(cacheProperties.getJcache().getConfig()); if (configLocation != null) { return cachingProvider.getCacheManager(configLocation.getURI(), this.beanClassLoader, properties); @@ -111,10 +111,9 @@ private CachingProvider getCachingProvider(String cachingProviderFqn) { } private Properties createCacheManagerProperties( - ObjectProvider cachePropertiesCustomizers, CacheProperties cacheProperties) { + ObjectProvider cachePropertiesCustomizers) { Properties properties = new Properties(); - cachePropertiesCustomizers.orderedStream() - .forEach((customizer) -> customizer.customize(cacheProperties, properties)); + cachePropertiesCustomizers.orderedStream().forEach((customizer) -> customizer.customize(properties)); return properties; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCachePropertiesCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCachePropertiesCustomizer.java index c18886c17e90..8a5905a62257 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCachePropertiesCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCachePropertiesCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,15 +26,16 @@ * used by the {@link CachingProvider} to create the {@link CacheManager}. * * @author Stephane Nicoll + * @since 3.4.0 + * @see CachingProvider#getCacheManager(java.net.URI, ClassLoader, Properties) */ -interface JCachePropertiesCustomizer { +public interface JCachePropertiesCustomizer { /** * Customize the properties. - * @param cacheProperties the cache properties * @param properties the current properties - * @see CachingProvider#getCacheManager(java.net.URI, ClassLoader, Properties) + * */ - void customize(CacheProperties cacheProperties, Properties properties); + void customize(Properties properties); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraProperties.java index bd25ed980f89..f2f8cae7e39c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -482,7 +482,7 @@ public enum Compression { /** * No compression. */ - NONE; + NONE } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java index 6d9e727e8b3c..3126167f106e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java @@ -80,10 +80,7 @@ public void recordConditionEvaluation(String source, Condition condition, Condit Assert.notNull(condition, "Condition must not be null"); Assert.notNull(outcome, "Outcome must not be null"); this.unconditionalClasses.remove(source); - if (!this.outcomes.containsKey(source)) { - this.outcomes.put(source, new ConditionAndOutcomes()); - } - this.outcomes.get(source).add(condition, outcome); + this.outcomes.computeIfAbsent(source, (key) -> new ConditionAndOutcomes()).add(condition, outcome); this.addedAncestorOutcomes = false; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionMessage.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionMessage.java index 37308a1971a8..9b0a0a40a170 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionMessage.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnCheckpointRestore.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnCheckpointRestore.java new file mode 100644 index 000000000000..2505d4930ffc --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnCheckpointRestore.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.condition; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Conditional; + +/** + * {@link Conditional @Conditional} that only matches when coordinated restore at + * checkpoint is to be used. + * + * @author Andy Wilkinson + * @since 3.2.0 + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ConditionalOnClass(name = "org.crac.Resource") +public @interface ConditionalOnCheckpointRestore { + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnThreading.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnThreading.java new file mode 100644 index 000000000000..18da6ceddbda --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnThreading.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.condition; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.autoconfigure.thread.Threading; +import org.springframework.context.annotation.Conditional; + +/** + * {@link Conditional @Conditional} that matches when the specified threading is active. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Conditional(OnThreadingCondition.class) +public @interface ConditionalOnThreading { + + /** + * The {@link Threading threading} that must be active. + * @return the expected threading + */ + Threading value(); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java index 9841bf802e63..d6c596e6841c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import org.springframework.core.annotation.Order; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.util.MultiValueMap; +import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** @@ -142,8 +143,17 @@ private static final class ThreadedOutcomesResolver implements OutcomesResolver private volatile ConditionOutcome[] outcomes; + private volatile Throwable failure; + private ThreadedOutcomesResolver(OutcomesResolver outcomesResolver) { - this.thread = new Thread(() -> this.outcomes = outcomesResolver.resolveOutcomes()); + this.thread = new Thread(() -> { + try { + this.outcomes = outcomesResolver.resolveOutcomes(); + } + catch (Throwable ex) { + this.failure = ex; + } + }); this.thread.start(); } @@ -155,6 +165,9 @@ public ConditionOutcome[] resolveOutcomes() { catch (InterruptedException ex) { Thread.currentThread().interrupt(); } + if (this.failure != null) { + ReflectionUtils.rethrowRuntimeException(this.failure); + } return this.outcomes; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnThreadingCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnThreadingCondition.java new file mode 100644 index 000000000000..7856a63431a6 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnThreadingCondition.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.condition; + +import java.util.Map; + +import org.springframework.boot.autoconfigure.thread.Threading; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.env.Environment; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * {@link Condition} that checks for a required {@link Threading}. + * + * @author Moritz Halbritter + * @see ConditionalOnThreading + */ +class OnThreadingCondition extends SpringBootCondition { + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + Map attributes = metadata.getAnnotationAttributes(ConditionalOnThreading.class.getName()); + Threading threading = (Threading) attributes.get("value"); + return getMatchOutcome(context.getEnvironment(), threading); + } + + private ConditionOutcome getMatchOutcome(Environment environment, Threading threading) { + String name = threading.name(); + ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnThreading.class); + if (threading.isActive(environment)) { + return ConditionOutcome.match(message.foundExactly(name)); + } + return ConditionOutcome.noMatch(message.didNotFind(name).atAll()); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/MessageSourceAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/MessageSourceAutoConfiguration.java index ed306db9882e..0ec92b6568b9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/MessageSourceAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/MessageSourceAutoConfiguration.java @@ -18,6 +18,8 @@ import java.time.Duration; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -26,6 +28,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration.MessageSourceRuntimeHints; import org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration.ResourceBundleCondition; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -33,6 +36,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.core.Ordered; @@ -48,6 +52,7 @@ * @author Dave Syer * @author Phillip Webb * @author Eddú Meléndez + * @author Marc Becker * @since 1.5.0 */ @AutoConfiguration @@ -55,6 +60,7 @@ @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @Conditional(ResourceBundleCondition.class) @EnableConfigurationProperties +@ImportRuntimeHints(MessageSourceRuntimeHints.class) public class MessageSourceAutoConfiguration { private static final Resource[] NO_RESOURCES = {}; @@ -125,4 +131,13 @@ private Resource[] getResources(ClassLoader classLoader, String name) { } + static class MessageSourceRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.resources().registerPattern("messages.properties").registerPattern("messages_*.properties"); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfiguration.java index 77a6dc070f37..5b72723e1cb1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,6 @@ package org.springframework.boot.autoconfigure.couchbase; -import java.io.InputStream; -import java.net.URL; -import java.security.KeyStore; - -import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; import com.couchbase.client.java.Cluster; @@ -52,7 +47,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.util.Assert; -import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; /** @@ -134,45 +128,17 @@ private void configureSsl(Builder builder, SslBundles sslBundles) { "SSL Options cannot be specified with Couchbase"); builder.securityConfig((config) -> { config.enableTls(true); - TrustManagerFactory trustManagerFactory = getTrustManagerFactory(sslProperties, sslBundle); + TrustManagerFactory trustManagerFactory = getTrustManagerFactory(sslBundle); if (trustManagerFactory != null) { config.trustManagerFactory(trustManagerFactory); } }); } - @SuppressWarnings("removal") - private TrustManagerFactory getTrustManagerFactory(CouchbaseProperties.Ssl sslProperties, SslBundle sslBundle) { - if (sslProperties.getKeyStore() != null) { - return loadTrustManagerFactory(sslProperties); - } + private TrustManagerFactory getTrustManagerFactory(SslBundle sslBundle) { return (sslBundle != null) ? sslBundle.getManagers().getTrustManagerFactory() : null; } - @SuppressWarnings("removal") - private TrustManagerFactory loadTrustManagerFactory(CouchbaseProperties.Ssl ssl) { - String resource = ssl.getKeyStore(); - try { - TrustManagerFactory trustManagerFactory = TrustManagerFactory - .getInstance(KeyManagerFactory.getDefaultAlgorithm()); - KeyStore keyStore = loadKeyStore(resource, ssl.getKeyStorePassword()); - trustManagerFactory.init(keyStore); - return trustManagerFactory; - } - catch (Exception ex) { - throw new IllegalStateException("Could not load Couchbase key store '" + resource + "'", ex); - } - } - - private KeyStore loadKeyStore(String resource, String keyStorePassword) throws Exception { - KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType()); - URL url = ResourceUtils.getURL(resource); - try (InputStream stream = url.openStream()) { - store.load(stream, (keyStorePassword != null) ? keyStorePassword.toCharArray() : null); - } - return store; - } - @Configuration(proxyBeanMethods = false) @ConditionalOnClass(ObjectMapper.class) static class JacksonConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseProperties.java index 9b5d36215b1b..fbe2d5878eaa 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import java.time.Duration; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.util.StringUtils; /** @@ -149,59 +148,24 @@ public void setIdleHttpConnectionTimeout(Duration idleHttpConnectionTimeout) { public static class Ssl { /** - * Whether to enable SSL support. Enabled automatically if a "keyStore" or - * "bundle" is provided unless specified otherwise. + * Whether to enable SSL support. Enabled automatically if a "bundle" is provided + * unless specified otherwise. */ private Boolean enabled; - /** - * Path to the JVM key store that holds the certificates. - */ - private String keyStore; - - /** - * Password used to access the key store. - */ - private String keyStorePassword; - /** * SSL bundle name. */ private String bundle; public Boolean getEnabled() { - return (this.enabled != null) ? this.enabled - : StringUtils.hasText(this.keyStore) || StringUtils.hasText(this.bundle); + return (this.enabled != null) ? this.enabled : StringUtils.hasText(this.bundle); } public void setEnabled(Boolean enabled) { this.enabled = enabled; } - @Deprecated(since = "3.1.0", forRemoval = true) - @DeprecatedConfigurationProperty( - reason = "SSL bundle support with spring.ssl.bundle and spring.couchbase.env.ssl.bundle should be used instead") - public String getKeyStore() { - return this.keyStore; - } - - @Deprecated(since = "3.1.0", forRemoval = true) - public void setKeyStore(String keyStore) { - this.keyStore = keyStore; - } - - @Deprecated(since = "3.1.0", forRemoval = true) - @DeprecatedConfigurationProperty( - reason = "SSL bundle support with spring.ssl.bundle and spring.couchbase.env.ssl.bundle should be used instead") - public String getKeyStorePassword() { - return this.keyStorePassword; - } - - @Deprecated(since = "3.1.0", forRemoval = true) - public void setKeyStorePassword(String keyStorePassword) { - this.keyStorePassword = keyStorePassword; - } - public String getBundle() { return this.bundle; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfiguration.java index bd70e7d00f1b..890b4895414e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.domain.EntityScanPackages; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Lazy; import org.springframework.core.env.Environment; import org.springframework.data.cassandra.CassandraManagedTypes; import org.springframework.data.cassandra.SessionFactory; @@ -54,6 +55,7 @@ * @author Eddú Meléndez * @author Mark Paluch * @author Madhura Bhave + * @author Christoph Strobl * @since 1.3.0 */ @AutoConfiguration(after = CassandraAutoConfiguration.class) @@ -63,7 +65,7 @@ public class CassandraDataAutoConfiguration { private final CqlSession session; - public CassandraDataAutoConfiguration(CqlSession session) { + public CassandraDataAutoConfiguration(@Lazy CqlSession session) { this.session = session; } @@ -95,7 +97,7 @@ public CassandraMappingContext cassandraMappingContext(CassandraManagedTypes cas public CassandraConverter cassandraConverter(CassandraMappingContext mapping, CassandraCustomConversions conversions) { MappingCassandraConverter converter = new MappingCassandraConverter(mapping); - converter.setCodecRegistry(this.session.getContext().getCodecRegistry()); + converter.setCodecRegistry(() -> this.session.getContext().getCodecRegistry()); converter.setCustomConversions(conversions); converter.setUserTypeResolver(new SimpleUserTypeResolver(this.session)); return converter; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataConfiguration.java index 20826c607ba9..1b408f876620 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataConfiguration.java @@ -61,7 +61,7 @@ TranslationService couchbaseTranslationService() { @ConditionalOnMissingBean(name = BeanNames.COUCHBASE_MAPPING_CONTEXT) CouchbaseMappingContext couchbaseMappingContext(CouchbaseDataProperties properties, ApplicationContext applicationContext, CouchbaseCustomConversions couchbaseCustomConversions) - throws Exception { + throws ClassNotFoundException { CouchbaseMappingContext mappingContext = new CouchbaseMappingContext(); mappingContext.setInitialEntitySet(new EntityScanner(applicationContext).scan(Document.class)); mappingContext.setSimpleTypeHolder(couchbaseCustomConversions.getSimpleTypeHolder()); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jdbc/JdbcDataProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jdbc/JdbcDataProperties.java new file mode 100644 index 000000000000..bc2f8a95b71c --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jdbc/JdbcDataProperties.java @@ -0,0 +1,44 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.data.jdbc; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for Spring Data JDBC. + * + * @author Jens Schauder + * @since 3.3.0 + */ +@ConfigurationProperties(prefix = "spring.data.jdbc") +public class JdbcDataProperties { + + /** + * Dialect to use. By default, the dialect is determined by inspecting the database + * connection. + */ + private JdbcDatabaseDialect dialect; + + public JdbcDatabaseDialect getDialect() { + return this.dialect; + } + + public void setDialect(JdbcDatabaseDialect dialect) { + this.dialect = dialect; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jdbc/JdbcDatabaseDialect.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jdbc/JdbcDatabaseDialect.java new file mode 100644 index 000000000000..b14c1fb5340e --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jdbc/JdbcDatabaseDialect.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.data.jdbc; + +import org.springframework.data.jdbc.core.dialect.JdbcDb2Dialect; +import org.springframework.data.jdbc.core.dialect.JdbcMySqlDialect; +import org.springframework.data.jdbc.core.dialect.JdbcPostgresDialect; +import org.springframework.data.jdbc.core.dialect.JdbcSqlServerDialect; +import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.data.relational.core.dialect.H2Dialect; +import org.springframework.data.relational.core.dialect.HsqlDbDialect; +import org.springframework.data.relational.core.dialect.MariaDbDialect; +import org.springframework.data.relational.core.dialect.MySqlDialect; +import org.springframework.data.relational.core.dialect.OracleDialect; + +/** + * List of database dialects that can be configured in Boot for use with Spring Data JDBC. + * + * @author Jens Schauder + * @since 3.3.0 + */ +public enum JdbcDatabaseDialect { + + /** + * Provides an instance of {@link JdbcDb2Dialect}. + */ + DB2(JdbcDb2Dialect.INSTANCE), + + /** + * Provides an instance of {@link H2Dialect}. + */ + H2(H2Dialect.INSTANCE), + + /** + * Provides an instance of {@link HsqlDbDialect}. + */ + HSQL(HsqlDbDialect.INSTANCE), + + /** + * Provides an instance of {@link MariaDbDialect}. + */ + MARIA(MySqlDialect.INSTANCE), + + /** + * Provides an instance of {@link JdbcMySqlDialect}. + */ + MYSQL(MySqlDialect.INSTANCE), + + /** + * Provides an instance of {@link OracleDialect}. + */ + ORACLE(OracleDialect.INSTANCE), + + /** + * Provides an instance of {@link JdbcPostgresDialect}. + */ + POSTGRESQL(JdbcPostgresDialect.INSTANCE), + + /** + * Provides an instance of {@link JdbcSqlServerDialect}. + */ + SQL_SERVER(JdbcSqlServerDialect.INSTANCE); + + private final Dialect dialect; + + JdbcDatabaseDialect(Dialect dialect) { + this.dialect = dialect; + } + + final Dialect getDialect() { + return this.dialect; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jdbc/JdbcRepositoriesAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jdbc/JdbcRepositoriesAutoConfiguration.java index b17bd3bf40f1..4ba437be7b19 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jdbc/JdbcRepositoriesAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jdbc/JdbcRepositoriesAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.domain.EntityScanner; import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -59,6 +60,7 @@ * @author Andy Wilkinson * @author Stephane Nicoll * @author Mark Paluch + * @author Jens Schauder * @since 2.1.0 * @see EnableJdbcRepositories */ @@ -67,6 +69,7 @@ @ConditionalOnClass({ NamedParameterJdbcOperations.class, AbstractJdbcConfiguration.class }) @ConditionalOnProperty(prefix = "spring.data.jdbc.repositories", name = "enabled", havingValue = "true", matchIfMissing = true) +@EnableConfigurationProperties(JdbcDataProperties.class) public class JdbcRepositoriesAutoConfiguration { @Configuration(proxyBeanMethods = false) @@ -82,8 +85,11 @@ static class SpringBootJdbcConfiguration extends AbstractJdbcConfiguration { private final ApplicationContext applicationContext; - SpringBootJdbcConfiguration(ApplicationContext applicationContext) { + private final JdbcDataProperties properties; + + SpringBootJdbcConfiguration(ApplicationContext applicationContext, JdbcDataProperties properties) { this.applicationContext = applicationContext; + this.properties = properties; } @Override @@ -141,7 +147,8 @@ public DataAccessStrategy dataAccessStrategyBean(NamedParameterJdbcOperations op @Bean @ConditionalOnMissingBean public Dialect jdbcDialect(NamedParameterJdbcOperations operations) { - return super.jdbcDialect(operations); + JdbcDatabaseDialect dialect = this.properties.getDialect(); + return (dialect != null) ? dialect.getDialect() : super.jdbcDialect(operations); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfiguration.java index 4ad5d7010bd4..625598c109a5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataConfiguration.java index 898b4bc7e9ab..f11e0c6f20f8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.util.Collections; import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.domain.EntityScanner; import org.springframework.boot.autoconfigure.mongo.MongoProperties; @@ -27,8 +28,14 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.mapping.model.FieldNamingStrategy; +import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.MongoManagedTypes; +import org.springframework.data.mongodb.core.convert.DbRefResolver; +import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.convert.MongoCustomConversions; +import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; @@ -70,4 +77,16 @@ MongoCustomConversions mongoCustomConversions() { return new MongoCustomConversions(Collections.emptyList()); } + @Bean + @ConditionalOnMissingBean(MongoConverter.class) + MappingMongoConverter mappingMongoConverter(ObjectProvider factory, + MongoMappingContext context, MongoCustomConversions conversions) { + MongoDatabaseFactory mongoDatabaseFactory = factory.getIfAvailable(); + DbRefResolver dbRefResolver = (mongoDatabaseFactory != null) ? new DefaultDbRefResolver(mongoDatabaseFactory) + : NoOpDbRefResolver.INSTANCE; + MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, context); + mappingConverter.setCustomConversions(conversions); + return mappingConverter; + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoDatabaseFactoryDependentConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoDatabaseFactoryDependentConfiguration.java index 77ed0c6dc48d..ba0f6a1a95f0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoDatabaseFactoryDependentConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoDatabaseFactoryDependentConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,12 +33,7 @@ import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.convert.DbRefResolver; -import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; -import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoConverter; -import org.springframework.data.mongodb.core.convert.MongoCustomConversions; -import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.gridfs.GridFsOperations; import org.springframework.data.mongodb.gridfs.GridFsTemplate; import org.springframework.util.Assert; @@ -61,16 +56,6 @@ MongoTemplate mongoTemplate(MongoDatabaseFactory factory, MongoConverter convert return new MongoTemplate(factory, converter); } - @Bean - @ConditionalOnMissingBean(MongoConverter.class) - MappingMongoConverter mappingMongoConverter(MongoDatabaseFactory factory, MongoMappingContext context, - MongoCustomConversions conversions) { - DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory); - MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, context); - mappingConverter.setCustomConversions(conversions); - return mappingConverter; - } - @Bean @ConditionalOnMissingBean(GridFsOperations.class) GridFsTemplate gridFsTemplate(MongoProperties properties, MongoDatabaseFactory factory, MongoTemplate mongoTemplate, diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoReactiveDataAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoReactiveDataAutoConfiguration.java index ecdf179b611c..4b3c71f82e0a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoReactiveDataAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoReactiveDataAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,9 +48,6 @@ import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoConverter; -import org.springframework.data.mongodb.core.convert.MongoCustomConversions; -import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; -import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.gridfs.ReactiveGridFsOperations; import org.springframework.data.mongodb.gridfs.ReactiveGridFsTemplate; import org.springframework.util.StringUtils; @@ -101,15 +98,6 @@ public ReactiveMongoTemplate reactiveMongoTemplate(ReactiveMongoDatabaseFactory return new ReactiveMongoTemplate(reactiveMongoDatabaseFactory, converter); } - @Bean - @ConditionalOnMissingBean(MongoConverter.class) - public MappingMongoConverter mappingMongoConverter(MongoMappingContext context, - MongoCustomConversions conversions) { - MappingMongoConverter mappingConverter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context); - mappingConverter.setCustomConversions(conversions); - return mappingConverter; - } - @Bean @ConditionalOnMissingBean(DataBufferFactory.class) public DefaultDataBufferFactory dataBufferFactory() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java index 742577d68ab1..dcaef46cd6f8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import org.springframework.boot.autoconfigure.domain.EntityScanner; import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; +import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizationAutoConfiguration; import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; @@ -58,7 +59,8 @@ * @author Michael J. Simons * @since 1.4.0 */ -@AutoConfiguration(before = TransactionAutoConfiguration.class, after = Neo4jAutoConfiguration.class) +@AutoConfiguration(before = TransactionAutoConfiguration.class, + after = { Neo4jAutoConfiguration.class, TransactionManagerCustomizationAutoConfiguration.class }) @ConditionalOnClass({ Driver.class, Neo4jTransactionManager.class, PlatformTransactionManager.class }) @EnableConfigurationProperties(Neo4jDataProperties.class) @ConditionalOnBean(Driver.class) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jReactiveDataAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jReactiveDataAutoConfiguration.java index 94fa7836d314..e8b7d4f9ed68 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jReactiveDataAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jReactiveDataAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfiguration.java index 05f2c7729e14..76d2ae2a3cf2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java index 80bcde71166a..c1c6fbb6e811 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java @@ -26,12 +26,15 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading; +import org.springframework.boot.autoconfigure.thread.Threading; import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.ssl.SslOptions; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisSentinelConfiguration; @@ -69,11 +72,23 @@ class JedisConnectionConfiguration extends RedisConnectionConfiguration { } @Bean + @ConditionalOnThreading(Threading.PLATFORM) JedisConnectionFactory redisConnectionFactory( ObjectProvider builderCustomizers) { return createJedisConnectionFactory(builderCustomizers); } + @Bean + @ConditionalOnThreading(Threading.VIRTUAL) + JedisConnectionFactory redisConnectionFactoryVirtualThreads( + ObjectProvider builderCustomizers) { + JedisConnectionFactory factory = createJedisConnectionFactory(builderCustomizers); + SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("redis-"); + executor.setVirtualThreads(true); + factory.setExecutor(executor); + return factory; + } + private JedisConnectionFactory createJedisConnectionFactory( ObjectProvider builderCustomizers) { JedisClientConfiguration clientConfiguration = getJedisClientConfiguration(builderCustomizers); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceClientConfigurationBuilderCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceClientConfigurationBuilderCustomizer.java index 750daecd9e28..1599a68310cd 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceClientConfigurationBuilderCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceClientConfigurationBuilderCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,9 @@ * Callback interface that can be implemented by beans wishing to customize the * {@link LettuceClientConfiguration} through a {@link LettuceClientConfigurationBuilder * LettuceClientConfiguration.LettuceClientConfigurationBuilder} whilst retaining default - * auto-configuration. + * auto-configuration. To customize only the + * {@link LettuceClientConfiguration#getClientOptions() client options} of the + * configuration, use {@link LettuceClientOptionsBuilderCustomizer} instead. * * @author Mark Paluch * @since 2.0.0 diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceClientOptionsBuilderCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceClientOptionsBuilderCustomizer.java new file mode 100644 index 000000000000..625d8b6864de --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceClientOptionsBuilderCustomizer.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.data.redis; + +import io.lettuce.core.ClientOptions; +import io.lettuce.core.ClientOptions.Builder; + +import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; + +/** + * Callback interface that can be implemented by beans wishing to customize the + * {@link ClientOptions} of the {@link LettuceClientConfiguration} through a + * {@link Builder} whilst retaining default auto-configuration. To customize the entire + * configuration, use {@link LettuceClientConfigurationBuilderCustomizer} instead. + * + * @author Soohyun Lim + * @since 3.4.0 + */ +@FunctionalInterface +public interface LettuceClientOptionsBuilderCustomizer { + + /** + * Customize the {@link Builder}. + * @param clientOptionsBuilder the builder to customize + */ + void customize(Builder clientOptionsBuilder); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java index 9fbdcfabee0c..040eebd919cd 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java @@ -33,13 +33,16 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading; import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Lettuce.Cluster.Refresh; import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool; +import org.springframework.boot.autoconfigure.thread.Threading; import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.ssl.SslOptions; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisSentinelConfiguration; @@ -83,11 +86,36 @@ DefaultClientResources lettuceClientResources(ObjectProvider builderCustomizers, + ObjectProvider clientConfigurationBuilderCustomizers, + ObjectProvider clientOptionsBuilderCustomizers, ClientResources clientResources) { - LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources, - getProperties().getLettuce().getPool()); + return createConnectionFactory(clientConfigurationBuilderCustomizers, clientOptionsBuilderCustomizers, + clientResources); + } + + @Bean + @ConditionalOnMissingBean(RedisConnectionFactory.class) + @ConditionalOnThreading(Threading.VIRTUAL) + LettuceConnectionFactory redisConnectionFactoryVirtualThreads( + ObjectProvider clientConfigurationBuilderCustomizers, + ObjectProvider clientOptionsBuilderCustomizers, + ClientResources clientResources) { + LettuceConnectionFactory factory = createConnectionFactory(clientConfigurationBuilderCustomizers, + clientOptionsBuilderCustomizers, clientResources); + SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("redis-"); + executor.setVirtualThreads(true); + factory.setExecutor(executor); + return factory; + } + + private LettuceConnectionFactory createConnectionFactory( + ObjectProvider clientConfigurationBuilderCustomizers, + ObjectProvider clientOptionsBuilderCustomizers, + ClientResources clientResources) { + LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(clientConfigurationBuilderCustomizers, + clientOptionsBuilderCustomizers, clientResources, getProperties().getLettuce().getPool()); return createLettuceConnectionFactory(clientConfig); } @@ -102,16 +130,17 @@ private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientCon } private LettuceClientConfiguration getLettuceClientConfiguration( - ObjectProvider builderCustomizers, + ObjectProvider clientConfigurationBuilderCustomizers, + ObjectProvider clientOptionsBuilderCustomizers, ClientResources clientResources, Pool pool) { LettuceClientConfigurationBuilder builder = createBuilder(pool); applyProperties(builder); if (StringUtils.hasText(getProperties().getUrl())) { customizeConfigurationFromUrl(builder); } - builder.clientOptions(createClientOptions()); + builder.clientOptions(createClientOptions(clientOptionsBuilderCustomizers)); builder.clientResources(clientResources); - builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); + clientConfigurationBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); return builder.build(); } @@ -140,7 +169,8 @@ private void applyProperties(LettuceClientConfiguration.LettuceClientConfigurati } } - private ClientOptions createClientOptions() { + private ClientOptions createClientOptions( + ObjectProvider clientConfigurationBuilderCustomizers) { ClientOptions.Builder builder = initializeClientOptionsBuilder(); Duration connectTimeout = getProperties().getConnectTimeout(); if (connectTimeout != null) { @@ -160,7 +190,9 @@ private ClientOptions createClientOptions() { } builder.sslOptions(sslOptionsBuilder.build()); } - return builder.timeoutOptions(TimeoutOptions.enabled()).build(); + builder.timeoutOptions(TimeoutOptions.enabled()); + clientConfigurationBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); + return builder.build(); } private ClientOptions.Builder initializeClientOptionsBuilder() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionConfiguration.java index b64347ab22df..8d25cc49d130 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientConfigurations.java index 28197797567b..de1fd52b0833 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientConfigurations.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientConfigurations.java @@ -22,7 +22,7 @@ import co.elastic.clients.json.jackson.JacksonJsonpMapper; import co.elastic.clients.json.jsonb.JsonbJsonpMapper; import co.elastic.clients.transport.ElasticsearchTransport; -import co.elastic.clients.transport.TransportOptions; +import co.elastic.clients.transport.rest_client.RestClientOptions; import co.elastic.clients.transport.rest_client.RestClientTransport; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.json.bind.Jsonb; @@ -90,8 +90,8 @@ static class ElasticsearchTransportConfiguration { @Bean RestClientTransport restClientTransport(RestClient restClient, JsonpMapper jsonMapper, - ObjectProvider transportOptions) { - return new RestClientTransport(restClient, jsonMapper, transportOptions.getIfAvailable()); + ObjectProvider restClientOptions) { + return new RestClientTransport(restClient, jsonMapper, restClientOptions.getIfAvailable()); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java index a9c4c49aa288..36a732624102 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; import javax.sql.DataSource; @@ -32,6 +34,9 @@ import org.flywaydb.core.api.callback.Callback; import org.flywaydb.core.api.configuration.FluentConfiguration; import org.flywaydb.core.api.migration.JavaMigration; +import org.flywaydb.core.extensibility.ConfigurationExtension; +import org.flywaydb.database.oracle.OracleConfigurationExtension; +import org.flywaydb.database.postgresql.PostgreSQLConfigurationExtension; import org.flywaydb.database.sqlserver.SQLServerConfigurationExtension; import org.springframework.aot.hint.RuntimeHints; @@ -46,6 +51,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayAutoConfigurationRuntimeHints; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayDataSourceCondition; +import org.springframework.boot.autoconfigure.flyway.FlywayProperties.Oracle; +import org.springframework.boot.autoconfigure.flyway.FlywayProperties.Postgresql; +import org.springframework.boot.autoconfigure.flyway.FlywayProperties.Sqlserver; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; @@ -61,6 +69,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.io.ResourceLoader; @@ -71,6 +81,7 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; +import org.springframework.util.function.SingletonSupplier; /** * {@link EnableAutoConfiguration Auto-configuration} for Flyway database migrations. @@ -116,6 +127,12 @@ public FlywaySchemaManagementProvider flywayDefaultDdlModeProvider(ObjectProvide @EnableConfigurationProperties(FlywayProperties.class) public static class FlywayConfiguration { + private final FlywayProperties properties; + + FlywayConfiguration(FlywayProperties properties) { + this.properties = properties; + } + @Bean ResourceProviderCustomizer resourceProviderCustomizer() { return new ResourceProviderCustomizer(); @@ -123,31 +140,38 @@ ResourceProviderCustomizer resourceProviderCustomizer() { @Bean @ConditionalOnMissingBean(FlywayConnectionDetails.class) - PropertiesFlywayConnectionDetails flywayConnectionDetails(FlywayProperties properties) { - return new PropertiesFlywayConnectionDetails(properties); + PropertiesFlywayConnectionDetails flywayConnectionDetails() { + return new PropertiesFlywayConnectionDetails(this.properties); } - @Deprecated(since = "3.0.0", forRemoval = true) - public Flyway flyway(FlywayProperties properties, ResourceLoader resourceLoader, - ObjectProvider dataSource, ObjectProvider flywayDataSource, - ObjectProvider fluentConfigurationCustomizers, - ObjectProvider javaMigrations, ObjectProvider callbacks) { - return flyway(properties, new PropertiesFlywayConnectionDetails(properties), resourceLoader, dataSource, - flywayDataSource, fluentConfigurationCustomizers, javaMigrations, callbacks, - new ResourceProviderCustomizer()); + @Bean + @ConditionalOnClass(name = "org.flywaydb.database.sqlserver.SQLServerConfigurationExtension") + SqlServerFlywayConfigurationCustomizer sqlServerFlywayConfigurationCustomizer() { + return new SqlServerFlywayConfigurationCustomizer(this.properties); + } + + @Bean + @ConditionalOnClass(name = "org.flywaydb.database.oracle.OracleConfigurationExtension") + OracleFlywayConfigurationCustomizer oracleFlywayConfigurationCustomizer() { + return new OracleFlywayConfigurationCustomizer(this.properties); + } + + @Bean + @ConditionalOnClass(name = "org.flywaydb.database.postgresql.PostgreSQLConfigurationExtension") + PostgresqlFlywayConfigurationCustomizer postgresqlFlywayConfigurationCustomizer() { + return new PostgresqlFlywayConfigurationCustomizer(this.properties); } @Bean - Flyway flyway(FlywayProperties properties, FlywayConnectionDetails connectionDetails, - ResourceLoader resourceLoader, ObjectProvider dataSource, - @FlywayDataSource ObjectProvider flywayDataSource, + Flyway flyway(FlywayConnectionDetails connectionDetails, ResourceLoader resourceLoader, + ObjectProvider dataSource, @FlywayDataSource ObjectProvider flywayDataSource, ObjectProvider fluentConfigurationCustomizers, ObjectProvider javaMigrations, ObjectProvider callbacks, ResourceProviderCustomizer resourceProviderCustomizer) { FluentConfiguration configuration = new FluentConfiguration(resourceLoader.getClassLoader()); configureDataSource(configuration, flywayDataSource.getIfAvailable(), dataSource.getIfUnique(), connectionDetails); - configureProperties(configuration, properties); + configureProperties(configuration, this.properties); configureCallbacks(configuration, callbacks.orderedStream().toList()); configureJavaMigrations(configuration, javaMigrations.orderedStream().toList()); fluentConfigurationCustomizers.orderedStream().forEach((customizer) -> customizer.customize(configuration)); @@ -202,6 +226,8 @@ private void applyConnectionDetails(FlywayConnectionDetails connectionDetails, D * @param properties the properties */ private void configureProperties(FluentConfiguration configuration, FlywayProperties properties) { + // NOTE: Using method references in the mapper methods can break + // back-compatibilty (see gh-38164) PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); String[] locations = new LocationResolver(configuration.getDataSource()) .resolveLocations(properties.getLocations()) @@ -276,21 +302,14 @@ private void configureProperties(FluentConfiguration configuration, FlywayProper .to((suffix) -> configuration.scriptPlaceholderSuffix(suffix)); configureExecuteInTransaction(configuration, properties, map); map.from(properties::getLoggers).to((loggers) -> configuration.loggers(loggers)); + map.from(properties::getCommunityDbSupportEnabled) + .to((communityDbSupportEnabled) -> configuration.communityDBSupportEnabled(communityDbSupportEnabled)); // Flyway Teams properties map.from(properties.getBatch()).to((batch) -> configuration.batch(batch)); map.from(properties.getDryRunOutput()).to((dryRunOutput) -> configuration.dryRunOutput(dryRunOutput)); map.from(properties.getErrorOverrides()) .to((errorOverrides) -> configuration.errorOverrides(errorOverrides)); - map.from(properties.getLicenseKey()).to((licenseKey) -> configuration.licenseKey(licenseKey)); - map.from(properties.getOracleSqlplus()).to((oracleSqlplus) -> configuration.oracleSqlplus(oracleSqlplus)); - map.from(properties.getOracleSqlplusWarn()) - .to((oracleSqlplusWarn) -> configuration.oracleSqlplusWarn(oracleSqlplusWarn)); - map.from(properties.getOracleKerberosCacheFile()) - .to((oracleKerberosCacheFile) -> configuration.oracleKerberosCacheFile(oracleKerberosCacheFile)); map.from(properties.getStream()).to((stream) -> configuration.stream(stream)); - map.from(properties.getUndoSqlMigrationPrefix()) - .to((undoSqlMigrationPrefix) -> configuration.undoSqlMigrationPrefix(undoSqlMigrationPrefix)); - map.from(properties.getCherryPick()).to((cherryPick) -> configuration.cherryPick(cherryPick)); map.from(properties.getJdbcProperties()) .whenNot(Map::isEmpty) .to((jdbcProperties) -> configuration.jdbcProperties(jdbcProperties)); @@ -298,10 +317,6 @@ private void configureProperties(FluentConfiguration configuration, FlywayProper .to((configFile) -> configuration.kerberosConfigFile(configFile)); map.from(properties.getOutputQueryResults()) .to((outputQueryResults) -> configuration.outputQueryResults(outputQueryResults)); - map.from(properties.getSqlServerKerberosLoginFile()) - .whenNonNull() - .to((sqlServerKerberosLoginFile) -> configureSqlServerKerberosLoginFile(configuration, - sqlServerKerberosLoginFile)); map.from(properties.getSkipExecutingMigrations()) .to((skipExecutingMigrations) -> configuration.skipExecutingMigrations(skipExecutingMigrations)); map.from(properties.getIgnoreMigrationPatterns()) @@ -322,14 +337,6 @@ private void configureExecuteInTransaction(FluentConfiguration configuration, Fl } } - private void configureSqlServerKerberosLoginFile(FluentConfiguration configuration, - String sqlServerKerberosLoginFile) { - SQLServerConfigurationExtension sqlServerConfigurationExtension = configuration.getPluginRegister() - .getPlugin(SQLServerConfigurationExtension.class); - Assert.state(sqlServerConfigurationExtension != null, "Flyway SQL Server extension missing"); - sqlServerConfigurationExtension.setKerberosLoginFile(sqlServerKerberosLoginFile); - } - private void configureCallbacks(FluentConfiguration configuration, List callbacks) { if (!callbacks.isEmpty()) { configuration.callbacks(callbacks.toArray(new Callback[0])); @@ -491,4 +498,98 @@ public String getDriverClassName() { } + @Order(Ordered.HIGHEST_PRECEDENCE) + static final class OracleFlywayConfigurationCustomizer implements FlywayConfigurationCustomizer { + + private final FlywayProperties properties; + + OracleFlywayConfigurationCustomizer(FlywayProperties properties) { + this.properties = properties; + } + + @Override + public void customize(FluentConfiguration configuration) { + Extension extension = new Extension<>(configuration, + OracleConfigurationExtension.class, "Oracle"); + Oracle properties = this.properties.getOracle(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getSqlplus).to(extension.via((ext, sqlplus) -> ext.setSqlplus(sqlplus))); + map.from(properties::getSqlplusWarn) + .to(extension.via((ext, sqlplusWarn) -> ext.setSqlplusWarn(sqlplusWarn))); + map.from(properties::getWalletLocation) + .to(extension.via((ext, walletLocation) -> ext.setWalletLocation(walletLocation))); + map.from(properties::getKerberosCacheFile) + .to(extension.via((ext, kerberosCacheFile) -> ext.setKerberosCacheFile(kerberosCacheFile))); + } + + } + + @Order(Ordered.HIGHEST_PRECEDENCE) + static final class PostgresqlFlywayConfigurationCustomizer implements FlywayConfigurationCustomizer { + + private final FlywayProperties properties; + + PostgresqlFlywayConfigurationCustomizer(FlywayProperties properties) { + this.properties = properties; + } + + @Override + public void customize(FluentConfiguration configuration) { + Extension extension = new Extension<>(configuration, + PostgreSQLConfigurationExtension.class, "PostgreSQL"); + Postgresql properties = this.properties.getPostgresql(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getTransactionalLock) + .to(extension.via((ext, transactionalLock) -> ext.setTransactionalLock(transactionalLock))); + } + + } + + @Order(Ordered.HIGHEST_PRECEDENCE) + static final class SqlServerFlywayConfigurationCustomizer implements FlywayConfigurationCustomizer { + + private final FlywayProperties properties; + + SqlServerFlywayConfigurationCustomizer(FlywayProperties properties) { + this.properties = properties; + } + + @Override + public void customize(FluentConfiguration configuration) { + Extension extension = new Extension<>(configuration, + SQLServerConfigurationExtension.class, "SQL Server"); + Sqlserver properties = this.properties.getSqlserver(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getKerberosLoginFile).to(extension.via(this::setKerberosLoginFile)); + } + + private void setKerberosLoginFile(SQLServerConfigurationExtension configuration, String file) { + configuration.getKerberos().getLogin().setFile(file); + } + + } + + /** + * Helper class used to map properties to a {@link ConfigurationExtension}. + * + * @param the extension type + */ + static class Extension { + + private SingletonSupplier extension; + + Extension(FluentConfiguration configuration, Class type, String name) { + this.extension = SingletonSupplier.of(() -> { + E extension = configuration.getPluginRegister().getPlugin(type); + Assert.notNull(extension, () -> "Flyway %s extension missing".formatted(name)); + return extension; + }); + } + + Consumer via(BiConsumer action) { + return (value) -> action.accept(this.extension.get(), value); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java index b629e7c425f8..ad9c25864a6e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import java.util.Map; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.boot.convert.DurationUnit; /** @@ -290,38 +291,11 @@ public class FlywayProperties { */ private String[] errorOverrides; - /** - * Licence key for Flyway Teams. - */ - private String licenseKey; - - /** - * Whether to enable support for Oracle SQL*Plus commands. Requires Flyway Teams. - */ - private Boolean oracleSqlplus; - - /** - * Whether to issue a warning rather than an error when a not-yet-supported Oracle - * SQL*Plus statement is encountered. Requires Flyway Teams. - */ - private Boolean oracleSqlplusWarn; - /** * Whether to stream SQL migrations when executing them. Requires Flyway Teams. */ private Boolean stream; - /** - * File name prefix for undo SQL migrations. Requires Flyway Teams. - */ - private String undoSqlMigrationPrefix; - - /** - * Migrations that Flyway should consider when migrating or undoing. When empty all - * available migrations are considered. Requires Flyway Teams. - */ - private String[] cherryPick; - /** * Properties to pass to the JDBC driver. Requires Flyway Teams. */ @@ -332,28 +306,12 @@ public class FlywayProperties { */ private String kerberosConfigFile; - /** - * Path of the Oracle Kerberos cache file. Requires Flyway Teams. - */ - private String oracleKerberosCacheFile; - - /** - * Location of the Oracle Wallet, used to sign in to the database automatically. - * Requires Flyway Teams. - */ - private String oracleWalletLocation; - /** * Whether Flyway should output a table with the results of queries when executing * migrations. Requires Flyway Teams. */ private Boolean outputQueryResults; - /** - * Path to the SQL Server Kerberos login file. Requires Flyway Teams. - */ - private String sqlServerKerberosLoginFile; - /** * Whether Flyway should skip executing the contents of the migrations and only update * the schema history table. Requires Flyway teams. @@ -372,6 +330,17 @@ public class FlywayProperties { */ private Boolean detectEncoding; + /** + * Whether to enable community database support. + */ + private Boolean communityDbSupportEnabled; + + private final Oracle oracle = new Oracle(); + + private final Postgresql postgresql = new Postgresql(); + + private final Sqlserver sqlserver = new Sqlserver(); + public boolean isEnabled() { return this.enabled; } @@ -748,36 +717,37 @@ public void setErrorOverrides(String[] errorOverrides) { this.errorOverrides = errorOverrides; } - public String getLicenseKey() { - return this.licenseKey; - } - - public void setLicenseKey(String licenseKey) { - this.licenseKey = licenseKey; - } - + @DeprecatedConfigurationProperty(replacement = "spring.flyway.oracle.sqlplus", since = "3.2.0") + @Deprecated(since = "3.2.0", forRemoval = true) public Boolean getOracleSqlplus() { - return this.oracleSqlplus; + return getOracle().getSqlplus(); } + @Deprecated(since = "3.2.0", forRemoval = true) public void setOracleSqlplus(Boolean oracleSqlplus) { - this.oracleSqlplus = oracleSqlplus; + getOracle().setSqlplus(oracleSqlplus); } + @DeprecatedConfigurationProperty(replacement = "spring.flyway.oracle.sqlplus-warn", since = "3.2.0") + @Deprecated(since = "3.2.0", forRemoval = true) public Boolean getOracleSqlplusWarn() { - return this.oracleSqlplusWarn; + return getOracle().getSqlplusWarn(); } + @Deprecated(since = "3.2.0", forRemoval = true) public void setOracleSqlplusWarn(Boolean oracleSqlplusWarn) { - this.oracleSqlplusWarn = oracleSqlplusWarn; + getOracle().setSqlplusWarn(oracleSqlplusWarn); } + @DeprecatedConfigurationProperty(replacement = "spring.flyway.oracle.wallet-location", since = "3.2.0") + @Deprecated(since = "3.2.0", forRemoval = true) public String getOracleWalletLocation() { - return this.oracleWalletLocation; + return getOracle().getWalletLocation(); } + @Deprecated(since = "3.2.0", forRemoval = true) public void setOracleWalletLocation(String oracleWalletLocation) { - this.oracleWalletLocation = oracleWalletLocation; + getOracle().setWalletLocation(oracleWalletLocation); } public Boolean getStream() { @@ -788,22 +758,6 @@ public void setStream(Boolean stream) { this.stream = stream; } - public String getUndoSqlMigrationPrefix() { - return this.undoSqlMigrationPrefix; - } - - public void setUndoSqlMigrationPrefix(String undoSqlMigrationPrefix) { - this.undoSqlMigrationPrefix = undoSqlMigrationPrefix; - } - - public String[] getCherryPick() { - return this.cherryPick; - } - - public void setCherryPick(String[] cherryPick) { - this.cherryPick = cherryPick; - } - public Map getJdbcProperties() { return this.jdbcProperties; } @@ -820,12 +774,15 @@ public void setKerberosConfigFile(String kerberosConfigFile) { this.kerberosConfigFile = kerberosConfigFile; } + @DeprecatedConfigurationProperty(replacement = "spring.flyway.oracle.kerberos-cache-file", since = "3.2.0") + @Deprecated(since = "3.2.0", forRemoval = true) public String getOracleKerberosCacheFile() { - return this.oracleKerberosCacheFile; + return getOracle().getKerberosCacheFile(); } + @Deprecated(since = "3.2.0", forRemoval = true) public void setOracleKerberosCacheFile(String oracleKerberosCacheFile) { - this.oracleKerberosCacheFile = oracleKerberosCacheFile; + getOracle().setKerberosCacheFile(oracleKerberosCacheFile); } public Boolean getOutputQueryResults() { @@ -836,12 +793,15 @@ public void setOutputQueryResults(Boolean outputQueryResults) { this.outputQueryResults = outputQueryResults; } + @DeprecatedConfigurationProperty(replacement = "spring.flyway.sqlserver.kerberos-login-file") + @Deprecated(since = "3.2.0", forRemoval = true) public String getSqlServerKerberosLoginFile() { - return this.sqlServerKerberosLoginFile; + return getSqlserver().getKerberosLoginFile(); } + @Deprecated(since = "3.2.0", forRemoval = true) public void setSqlServerKerberosLoginFile(String sqlServerKerberosLoginFile) { - this.sqlServerKerberosLoginFile = sqlServerKerberosLoginFile; + getSqlserver().setKerberosLoginFile(sqlServerKerberosLoginFile); } public Boolean getSkipExecutingMigrations() { @@ -868,4 +828,126 @@ public void setDetectEncoding(final Boolean detectEncoding) { this.detectEncoding = detectEncoding; } + public Boolean getCommunityDbSupportEnabled() { + return this.communityDbSupportEnabled; + } + + public void setCommunityDbSupportEnabled(Boolean communityDbSupportEnabled) { + this.communityDbSupportEnabled = communityDbSupportEnabled; + } + + public Oracle getOracle() { + return this.oracle; + } + + public Postgresql getPostgresql() { + return this.postgresql; + } + + public Sqlserver getSqlserver() { + return this.sqlserver; + } + + /** + * {@code OracleConfigurationExtension} properties. + */ + public static class Oracle { + + /** + * Whether to enable support for Oracle SQL*Plus commands. Requires Flyway Teams. + */ + private Boolean sqlplus; + + /** + * Whether to issue a warning rather than an error when a not-yet-supported Oracle + * SQL*Plus statement is encountered. Requires Flyway Teams. + */ + private Boolean sqlplusWarn; + + /** + * Path of the Oracle Kerberos cache file. Requires Flyway Teams. + */ + private String kerberosCacheFile; + + /** + * Location of the Oracle Wallet, used to sign in to the database automatically. + * Requires Flyway Teams. + */ + private String walletLocation; + + public Boolean getSqlplus() { + return this.sqlplus; + } + + public void setSqlplus(Boolean sqlplus) { + this.sqlplus = sqlplus; + } + + public Boolean getSqlplusWarn() { + return this.sqlplusWarn; + } + + public void setSqlplusWarn(Boolean sqlplusWarn) { + this.sqlplusWarn = sqlplusWarn; + } + + public String getKerberosCacheFile() { + return this.kerberosCacheFile; + } + + public void setKerberosCacheFile(String kerberosCacheFile) { + this.kerberosCacheFile = kerberosCacheFile; + } + + public String getWalletLocation() { + return this.walletLocation; + } + + public void setWalletLocation(String walletLocation) { + this.walletLocation = walletLocation; + } + + } + + /** + * {@code PostgreSQLConfigurationExtension} properties. + */ + public static class Postgresql { + + /** + * Whether transactional advisory locks should be used. If set to false, + * session-level locks are used instead. + */ + private Boolean transactionalLock; + + public Boolean getTransactionalLock() { + return this.transactionalLock; + } + + public void setTransactionalLock(Boolean transactionalLock) { + this.transactionalLock = transactionalLock; + } + + } + + /** + * {@code SQLServerConfigurationExtension} properties. + */ + public static class Sqlserver { + + /** + * Path to the SQL Server Kerberos login file. Requires Flyway Teams. + */ + private String kerberosLoginFile; + + public String getKerberosLoginFile() { + return this.kerberosLoginFile; + } + + public void setKerberosLoginFile(String kerberosLoginFile) { + this.kerberosLoginFile = kerberosLoginFile; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/NativeImageResourceProvider.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/NativeImageResourceProvider.java index 63678acf9033..88fde2907c14 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/NativeImageResourceProvider.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/NativeImageResourceProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -96,8 +96,7 @@ public Collection getResources(String prefix, String[] suffixe ensureInitialized(); Predicate matchesPrefixAndSuffixes = (locatedResource) -> StringUtils .startsAndEndsWith(locatedResource.resource.getFilename(), prefix, suffixes); - List result = new ArrayList<>(); - result.addAll(this.scanner.getResources(prefix, suffixes)); + List result = new ArrayList<>(this.scanner.getResources(prefix, suffixes)); this.locatedResources.stream() .filter(matchesPrefixAndSuffixes) .map(this::asClassPathResource) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/NativeImageResourceProviderCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/NativeImageResourceProviderCustomizer.java index 1dd7b6e55ecb..615a33180cd3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/NativeImageResourceProviderCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/NativeImageResourceProviderCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,10 +35,8 @@ class NativeImageResourceProviderCustomizer extends ResourceProviderCustomizer { @Override public void customize(FluentConfiguration configuration) { if (configuration.getResourceProvider() == null) { - Scanner scanner = new Scanner<>(JavaMigration.class, - Arrays.asList(configuration.getLocations()), configuration.getClassLoader(), - configuration.getEncoding(), configuration.isDetectEncoding(), false, new ResourceNameCache(), - new LocationScannerCache(), configuration.isFailOnMissingLocations()); + Scanner scanner = new Scanner<>(JavaMigration.class, false, new ResourceNameCache(), + new LocationScannerCache(), configuration); NativeImageResourceProvider resourceProvider = new NativeImageResourceProvider(scanner, configuration.getClassLoader(), Arrays.asList(configuration.getLocations()), configuration.getEncoding(), configuration.isFailOnMissingLocations()); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/ResourceProviderCustomizerBeanRegistrationAotProcessor.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/ResourceProviderCustomizerBeanRegistrationAotProcessor.java index 919cfd3f4e68..6bdcccecd377 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/ResourceProviderCustomizerBeanRegistrationAotProcessor.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/ResourceProviderCustomizerBeanRegistrationAotProcessor.java @@ -16,8 +16,6 @@ package org.springframework.boot.autoconfigure.flyway; -import java.lang.reflect.Executable; - import javax.lang.model.element.Modifier; import org.springframework.aot.generate.GeneratedMethod; @@ -58,8 +56,7 @@ protected AotContribution(BeanRegistrationCodeFragments delegate, RegisteredBean @Override public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext, - BeanRegistrationCode beanRegistrationCode, Executable constructorOrFactoryMethod, - boolean allowDirectSupplierShortcut) { + BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) { GeneratedMethod generatedMethod = beanRegistrationCode.getMethods().add("getInstance", (method) -> { method.addJavadoc("Get the bean instance for '$L'.", this.registeredBean.getBeanName()); method.addModifiers(Modifier.PRIVATE, Modifier.STATIC); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerAutoConfiguration.java index 254d8d7d4a2b..79331b21dd4f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,8 +62,9 @@ public void checkTemplateLocationExists() { if (logger.isWarnEnabled() && this.properties.isCheckTemplateLocation()) { List locations = getLocations(); if (locations.stream().noneMatch(this::locationExists)) { - logger.warn("Cannot find template location(s): " + locations + " (please add some templates, " - + "check your FreeMarker configuration, or set " + String suffix = (locations.size() == 1) ? "" : "s"; + logger.warn("Cannot find template location" + suffix + ": " + locations + + " (please add some templates, " + "check your FreeMarker configuration, or set " + "spring.freemarker.check-template-location=false)"); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java index f23bc8a2aedb..9b9c408d7a58 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.Executor; import graphql.GraphQL; import graphql.execution.instrumentation.Instrumentation; @@ -32,10 +33,12 @@ import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.context.annotation.Bean; @@ -100,6 +103,9 @@ public GraphQlSource graphQlSource(ResourcePatternResolver resourcePatternResolv .exceptionResolvers(exceptionResolvers.orderedStream().toList()) .subscriptionExceptionResolvers(subscriptionExceptionResolvers.orderedStream().toList()) .instrumentation(instrumentations.orderedStream().toList()); + if (properties.getSchema().getInspection().isEnabled()) { + builder.inspectSchemaMappings(logger::info); + } if (!properties.getSchema().getIntrospection().isEnabled()) { Introspection.enabledJvmWide(false); } @@ -147,10 +153,12 @@ public ExecutionGraphQlService executionGraphQlService(GraphQlSource graphQlSour @Bean @ConditionalOnMissingBean - public AnnotatedControllerConfigurer annotatedControllerConfigurer() { + public AnnotatedControllerConfigurer annotatedControllerConfigurer( + @Qualifier(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME) ObjectProvider executorProvider) { AnnotatedControllerConfigurer controllerConfigurer = new AnnotatedControllerConfigurer(); controllerConfigurer .addFormatterRegistrar((registry) -> ApplicationConversionService.addBeans(registry, this.beanFactory)); + executorProvider.ifAvailable(controllerConfigurer::setExecutor); return controllerConfigurer; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlProperties.java index 13bb71c85f71..62e81f68a50c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,6 +79,8 @@ public static class Schema { */ private String[] fileExtensions = new String[] { ".graphqls", ".gqls" }; + private final Inspection inspection = new Inspection(); + private final Introspection introspection = new Introspection(); private final Printer printer = new Printer(); @@ -105,6 +107,10 @@ private String[] appendSlashIfNecessary(String[] locations) { .toArray(String[]::new); } + public Inspection getInspection() { + return this.inspection; + } + public Introspection getIntrospection() { return this.introspection; } @@ -113,6 +119,24 @@ public Printer getPrinter() { return this.printer; } + public static class Inspection { + + /** + * Whether schema should be compared to the application to detect missing + * mappings. + */ + private boolean enabled = true; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + } + public static class Introspection { /** @@ -193,6 +217,11 @@ public static class Websocket { */ private Duration connectionInitTimeout = Duration.ofSeconds(60); + /** + * Maximum idle period before a server keep-alive ping is sent to client. + */ + private Duration keepAlive = null; + public String getPath() { return this.path; } @@ -209,6 +238,14 @@ public void setConnectionInitTimeout(Duration connectionInitTimeout) { this.connectionInitTimeout = connectionInitTimeout; } + public Duration getKeepAlive() { + return this.keepAlive; + } + + public void setKeepAlive(Duration keepAlive) { + this.keepAlive = keepAlive; + } + } public static class Rsocket { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQueryByExampleAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQueryByExampleAutoConfiguration.java index 52df8e11cb43..42d79c0f31c9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQueryByExampleAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQueryByExampleAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.graphql.data; +import java.util.Collections; import java.util.List; import graphql.GraphQL; @@ -29,9 +30,9 @@ import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.data.repository.query.QueryByExampleExecutor; -import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor; import org.springframework.graphql.data.query.QueryByExampleDataFetcher; import org.springframework.graphql.execution.GraphQlSource; +import org.springframework.graphql.execution.RuntimeWiringConfigurer; /** * {@link EnableAutoConfiguration Auto-configuration} that creates a @@ -49,10 +50,10 @@ public class GraphQlQueryByExampleAutoConfiguration { @Bean - public GraphQlSourceBuilderCustomizer queryByExampleRegistrar(ObjectProvider> executors, - ObjectProvider> reactiveExecutors) { - return new GraphQlQuerydslSourceBuilderCustomizer<>(QueryByExampleDataFetcher::autoRegistrationConfigurer, - executors, reactiveExecutors); + public GraphQlSourceBuilderCustomizer queryByExampleRegistrar(ObjectProvider> executors) { + RuntimeWiringConfigurer configurer = QueryByExampleDataFetcher + .autoRegistrationConfigurer(executors.orderedStream().toList(), Collections.emptyList()); + return (builder) -> builder.configureRuntimeWiring(configurer); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslAutoConfiguration.java index c32b21b7811f..97e2debd0a4b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,10 @@ package org.springframework.boot.autoconfigure.graphql.data; +import java.util.Collections; import java.util.List; +import com.querydsl.core.Query; import graphql.GraphQL; import org.springframework.beans.factory.ObjectProvider; @@ -29,9 +31,9 @@ import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.data.querydsl.QuerydslPredicateExecutor; -import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor; import org.springframework.graphql.data.query.QuerydslDataFetcher; import org.springframework.graphql.execution.GraphQlSource; +import org.springframework.graphql.execution.RuntimeWiringConfigurer; /** * {@link EnableAutoConfiguration Auto-configuration} that creates a @@ -45,15 +47,15 @@ * @see QuerydslDataFetcher#autoRegistrationConfigurer(List, List) */ @AutoConfiguration(after = GraphQlAutoConfiguration.class) -@ConditionalOnClass({ GraphQL.class, QuerydslDataFetcher.class, QuerydslPredicateExecutor.class }) +@ConditionalOnClass({ GraphQL.class, Query.class, QuerydslDataFetcher.class, QuerydslPredicateExecutor.class }) @ConditionalOnBean(GraphQlSource.class) public class GraphQlQuerydslAutoConfiguration { @Bean - public GraphQlSourceBuilderCustomizer querydslRegistrar(ObjectProvider> executors, - ObjectProvider> reactiveExecutors) { - return new GraphQlQuerydslSourceBuilderCustomizer<>(QuerydslDataFetcher::autoRegistrationConfigurer, executors, - reactiveExecutors); + public GraphQlSourceBuilderCustomizer querydslRegistrar(ObjectProvider> executors) { + RuntimeWiringConfigurer configurer = QuerydslDataFetcher + .autoRegistrationConfigurer(executors.orderedStream().toList(), Collections.emptyList()); + return (builder) -> builder.configureRuntimeWiring(configurer); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslSourceBuilderCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslSourceBuilderCustomizer.java deleted file mode 100644 index ae0db51b20ab..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslSourceBuilderCustomizer.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.graphql.data; - -import java.util.Collections; -import java.util.List; -import java.util.function.BiFunction; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer; -import org.springframework.graphql.execution.GraphQlSource; -import org.springframework.graphql.execution.RuntimeWiringConfigurer; - -/** - * {@link GraphQlSourceBuilderCustomizer} to apply auto-configured QueryDSL - * {@link RuntimeWiringConfigurer RuntimeWiringConfigurers}. - * - * @param the executor type - * @param the reactive executor type - * @author Phillip Webb - * @author Rossen Stoyanchev - * @author Brian Clozel - */ -class GraphQlQuerydslSourceBuilderCustomizer implements GraphQlSourceBuilderCustomizer { - - private final BiFunction, List, RuntimeWiringConfigurer> wiringConfigurerFactory; - - private final List executors; - - private final List reactiveExecutors; - - GraphQlQuerydslSourceBuilderCustomizer( - BiFunction, List, RuntimeWiringConfigurer> wiringConfigurerFactory, ObjectProvider executors, - ObjectProvider reactiveExecutors) { - this.wiringConfigurerFactory = wiringConfigurerFactory; - this.executors = asList(executors); - this.reactiveExecutors = asList(reactiveExecutors); - } - - private static List asList(ObjectProvider provider) { - return (provider != null) ? provider.orderedStream().toList() : Collections.emptyList(); - } - - @Override - public void customize(GraphQlSource.SchemaResourceBuilder builder) { - if (!this.executors.isEmpty() || !this.reactiveExecutors.isEmpty()) { - builder.configureRuntimeWiring(this.wiringConfigurerFactory.apply(this.executors, this.reactiveExecutors)); - } - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQueryByExampleAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQueryByExampleAutoConfiguration.java index f28b17801ef1..6e784108e9da 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQueryByExampleAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQueryByExampleAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.graphql.data; +import java.util.Collections; import java.util.List; import graphql.GraphQL; @@ -28,10 +29,10 @@ import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration; import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer; import org.springframework.context.annotation.Bean; -import org.springframework.data.repository.query.QueryByExampleExecutor; import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor; import org.springframework.graphql.data.query.QueryByExampleDataFetcher; import org.springframework.graphql.execution.GraphQlSource; +import org.springframework.graphql.execution.RuntimeWiringConfigurer; /** * {@link EnableAutoConfiguration Auto-configuration} that creates a @@ -51,8 +52,9 @@ public class GraphQlReactiveQueryByExampleAutoConfiguration { @Bean public GraphQlSourceBuilderCustomizer reactiveQueryByExampleRegistrar( ObjectProvider> reactiveExecutors) { - return new GraphQlQuerydslSourceBuilderCustomizer<>(QueryByExampleDataFetcher::autoRegistrationConfigurer, - (ObjectProvider>) null, reactiveExecutors); + RuntimeWiringConfigurer configurer = QueryByExampleDataFetcher + .autoRegistrationConfigurer(Collections.emptyList(), reactiveExecutors.orderedStream().toList()); + return (builder) -> builder.configureRuntimeWiring(configurer); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQuerydslAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQuerydslAutoConfiguration.java index f12be0563ac7..14b81dcc7108 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQuerydslAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQuerydslAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,10 @@ package org.springframework.boot.autoconfigure.graphql.data; +import java.util.Collections; import java.util.List; +import com.querydsl.core.Query; import graphql.GraphQL; import org.springframework.beans.factory.ObjectProvider; @@ -28,10 +30,10 @@ import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration; import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer; import org.springframework.context.annotation.Bean; -import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor; import org.springframework.graphql.data.query.QuerydslDataFetcher; import org.springframework.graphql.execution.GraphQlSource; +import org.springframework.graphql.execution.RuntimeWiringConfigurer; /** * {@link EnableAutoConfiguration Auto-configuration} that creates a @@ -45,15 +47,16 @@ * @see QuerydslDataFetcher#autoRegistrationConfigurer(List, List) */ @AutoConfiguration(after = GraphQlAutoConfiguration.class) -@ConditionalOnClass({ GraphQL.class, QuerydslDataFetcher.class, ReactiveQuerydslPredicateExecutor.class }) +@ConditionalOnClass({ GraphQL.class, Query.class, QuerydslDataFetcher.class, ReactiveQuerydslPredicateExecutor.class }) @ConditionalOnBean(GraphQlSource.class) public class GraphQlReactiveQuerydslAutoConfiguration { @Bean public GraphQlSourceBuilderCustomizer reactiveQuerydslRegistrar( ObjectProvider> reactiveExecutors) { - return new GraphQlQuerydslSourceBuilderCustomizer<>(QuerydslDataFetcher::autoRegistrationConfigurer, - (ObjectProvider>) null, reactiveExecutors); + RuntimeWiringConfigurer configurer = QuerydslDataFetcher.autoRegistrationConfigurer(Collections.emptyList(), + reactiveExecutors.orderedStream().toList()); + return (builder) -> builder.configureRuntimeWiring(configurer); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java index 67677b20ddab..464919225828 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java @@ -47,19 +47,19 @@ import org.springframework.graphql.server.WebGraphQlHandler; import org.springframework.graphql.server.WebGraphQlInterceptor; import org.springframework.graphql.server.webflux.GraphQlHttpHandler; +import org.springframework.graphql.server.webflux.GraphQlRequestPredicates; +import org.springframework.graphql.server.webflux.GraphQlSseHandler; import org.springframework.graphql.server.webflux.GraphQlWebSocketHandler; import org.springframework.graphql.server.webflux.GraphiQlHandler; import org.springframework.graphql.server.webflux.SchemaHandler; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.config.CorsRegistry; import org.springframework.web.reactive.config.WebFluxConfigurer; -import org.springframework.web.reactive.function.server.RequestPredicate; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunctions; import org.springframework.web.reactive.function.server.ServerRequest; @@ -67,9 +67,6 @@ import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping; import org.springframework.web.reactive.socket.server.support.WebSocketUpgradeHandlerPredicate; -import static org.springframework.web.reactive.function.server.RequestPredicates.accept; -import static org.springframework.web.reactive.function.server.RequestPredicates.contentType; - /** * {@link EnableAutoConfiguration Auto-configuration} for enabling Spring GraphQL over * WebFlux. @@ -85,13 +82,15 @@ @ImportRuntimeHints(GraphQlWebFluxAutoConfiguration.GraphiQlResourceHints.class) public class GraphQlWebFluxAutoConfiguration { - @SuppressWarnings("removal") - private static final RequestPredicate SUPPORTS_MEDIATYPES = accept(MediaType.APPLICATION_GRAPHQL_RESPONSE, - MediaType.APPLICATION_JSON, MediaType.APPLICATION_GRAPHQL) - .and(contentType(MediaType.APPLICATION_JSON)); - private static final Log logger = LogFactory.getLog(GraphQlWebFluxAutoConfiguration.class); + @Bean + @ConditionalOnMissingBean + public WebGraphQlHandler webGraphQlHandler(ExecutionGraphQlService service, + ObjectProvider interceptors) { + return WebGraphQlHandler.builder(service).interceptors(interceptors.orderedStream().toList()).build(); + } + @Bean @ConditionalOnMissingBean public GraphQlHttpHandler graphQlHttpHandler(WebGraphQlHandler webGraphQlHandler) { @@ -100,20 +99,20 @@ public GraphQlHttpHandler graphQlHttpHandler(WebGraphQlHandler webGraphQlHandler @Bean @ConditionalOnMissingBean - public WebGraphQlHandler webGraphQlHandler(ExecutionGraphQlService service, - ObjectProvider interceptors) { - return WebGraphQlHandler.builder(service).interceptors(interceptors.orderedStream().toList()).build(); + public GraphQlSseHandler graphQlSseHandler(WebGraphQlHandler webGraphQlHandler) { + return new GraphQlSseHandler(webGraphQlHandler); } @Bean @Order(0) public RouterFunction graphQlRouterFunction(GraphQlHttpHandler httpHandler, - GraphQlSource graphQlSource, GraphQlProperties properties) { + GraphQlSseHandler sseHandler, GraphQlSource graphQlSource, GraphQlProperties properties) { String path = properties.getPath(); logger.info(LogMessage.format("GraphQL endpoint HTTP POST %s", path)); RouterFunctions.Builder builder = RouterFunctions.route(); + builder.route(GraphQlRequestPredicates.graphQlHttp(path), httpHandler::handleRequest); + builder.route(GraphQlRequestPredicates.graphQlSse(path), sseHandler::handleRequest); builder.GET(path, this::onlyAllowPost); - builder.POST(path, SUPPORTS_MEDIATYPES, httpHandler::handleRequest); if (properties.getGraphiql().isEnabled()) { GraphiQlHandler graphQlHandler = new GraphiQlHandler(path, properties.getWebsocket().getPath()); builder.GET(properties.getGraphiql().getPath(), graphQlHandler::handleRequest); @@ -164,7 +163,7 @@ public static class WebSocketConfiguration { public GraphQlWebSocketHandler graphQlWebSocketHandler(WebGraphQlHandler webGraphQlHandler, GraphQlProperties properties, ServerCodecConfigurer configurer) { return new GraphQlWebSocketHandler(webGraphQlHandler, configurer, - properties.getWebsocket().getConnectionInitTimeout()); + properties.getWebsocket().getConnectionInitTimeout(), properties.getWebsocket().getKeepAlive()); } @Bean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java index 6e3c55844ebb..0c44464042ac 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java @@ -49,6 +49,8 @@ import org.springframework.graphql.server.WebGraphQlHandler; import org.springframework.graphql.server.WebGraphQlInterceptor; import org.springframework.graphql.server.webmvc.GraphQlHttpHandler; +import org.springframework.graphql.server.webmvc.GraphQlRequestPredicates; +import org.springframework.graphql.server.webmvc.GraphQlSseHandler; import org.springframework.graphql.server.webmvc.GraphQlWebSocketHandler; import org.springframework.graphql.server.webmvc.GraphiQlHandler; import org.springframework.graphql.server.webmvc.SchemaHandler; @@ -62,7 +64,6 @@ import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import org.springframework.web.servlet.function.RequestPredicates; import org.springframework.web.servlet.function.RouterFunction; import org.springframework.web.servlet.function.RouterFunctions; import org.springframework.web.servlet.function.ServerRequest; @@ -88,16 +89,18 @@ public class GraphQlWebMvcAutoConfiguration { private static final Log logger = LogFactory.getLog(GraphQlWebMvcAutoConfiguration.class); - @SuppressWarnings("removal") - private static final MediaType[] SUPPORTED_MEDIA_TYPES = new MediaType[] { MediaType.APPLICATION_GRAPHQL_RESPONSE, - MediaType.APPLICATION_JSON, MediaType.APPLICATION_GRAPHQL }; - @Bean @ConditionalOnMissingBean public GraphQlHttpHandler graphQlHttpHandler(WebGraphQlHandler webGraphQlHandler) { return new GraphQlHttpHandler(webGraphQlHandler); } + @Bean + @ConditionalOnMissingBean + public GraphQlSseHandler graphQlSseHandler(WebGraphQlHandler webGraphQlHandler) { + return new GraphQlSseHandler(webGraphQlHandler); + } + @Bean @ConditionalOnMissingBean public WebGraphQlHandler webGraphQlHandler(ExecutionGraphQlService service, @@ -108,13 +111,13 @@ public WebGraphQlHandler webGraphQlHandler(ExecutionGraphQlService service, @Bean @Order(0) public RouterFunction graphQlRouterFunction(GraphQlHttpHandler httpHandler, - GraphQlSource graphQlSource, GraphQlProperties properties) { + GraphQlSseHandler sseHandler, GraphQlSource graphQlSource, GraphQlProperties properties) { String path = properties.getPath(); logger.info(LogMessage.format("GraphQL endpoint HTTP POST %s", path)); RouterFunctions.Builder builder = RouterFunctions.route(); + builder.route(GraphQlRequestPredicates.graphQlHttp(path), httpHandler::handleRequest); + builder.route(GraphQlRequestPredicates.graphQlSse(path), sseHandler::handleRequest); builder.GET(path, this::onlyAllowPost); - builder.POST(path, RequestPredicates.contentType(MediaType.APPLICATION_JSON) - .and(RequestPredicates.accept(SUPPORTED_MEDIA_TYPES)), httpHandler::handleRequest); if (properties.getGraphiql().isEnabled()) { GraphiQlHandler graphiQLHandler = new GraphiQlHandler(path, properties.getWebsocket().getPath()); builder.GET(properties.getGraphiql().getPath(), graphiQLHandler::handleRequest); @@ -166,7 +169,7 @@ public static class WebSocketConfiguration { public GraphQlWebSocketHandler graphQlWebSocketHandler(WebGraphQlHandler webGraphQlHandler, GraphQlProperties properties, HttpMessageConverters converters) { return new GraphQlWebSocketHandler(webGraphQlHandler, getJsonConverter(converters), - properties.getWebsocket().getConnectionInitTimeout()); + properties.getWebsocket().getConnectionInitTimeout(), properties.getWebsocket().getKeepAlive()); } private GenericHttpMessageConverter getJsonConverter(HttpMessageConverters converters) { @@ -195,7 +198,7 @@ public HandlerMapping graphQlWebSocketMapping(GraphQlWebSocketHandler handler, G mapping.setWebSocketUpgradeMatch(true); mapping.setUrlMap(Collections.singletonMap(path, handler.initWebSocketHttpRequestHandler(new DefaultHandshakeHandler()))); - mapping.setOrder(2); // Ahead of HTTP endpoint ("routerFunctionMapping" bean) + mapping.setOrder(-2); // Ahead of HTTP endpoint ("routerFunctionMapping" bean) return mapping; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonAutoConfiguration.java index f81ecacf007f..d8efe7a2b81b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonAutoConfiguration.java @@ -92,8 +92,8 @@ public void customize(GsonBuilder builder) { map.from(properties::getLongSerializationPolicy).to(builder::setLongSerializationPolicy); map.from(properties::getFieldNamingPolicy).to(builder::setFieldNamingPolicy); map.from(properties::getPrettyPrinting).whenTrue().toCall(builder::setPrettyPrinting); - map.from(properties::getLenient).whenTrue().toCall(builder::setLenient); - map.from(properties::getDisableHtmlEscaping).whenFalse().toCall(builder::disableHtmlEscaping); + map.from(properties::getStrictness).to(builder::setStrictness); + map.from(properties::getDisableHtmlEscaping).whenTrue().toCall(builder::disableHtmlEscaping); map.from(properties::getDateFormat).to(builder::setDateFormat); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonProperties.java index 14e944553880..bc32bd80a952 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,10 @@ import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.LongSerializationPolicy; +import com.google.gson.Strictness; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; /** * Configuration properties to configure {@link Gson}. @@ -75,9 +77,10 @@ public class GsonProperties { private Boolean prettyPrinting; /** - * Whether to be lenient about parsing JSON that doesn't conform to RFC 4627. + * Sets how strictly the RFC 8259 specification will be enforced when reading and + * writing JSON. */ - private Boolean lenient; + private Strictness strictness; /** * Whether to disable the escaping of HTML characters such as '<', '>', etc. @@ -153,12 +156,22 @@ public void setPrettyPrinting(Boolean prettyPrinting) { this.prettyPrinting = prettyPrinting; } + public Strictness getStrictness() { + return this.strictness; + } + + public void setStrictness(Strictness strictness) { + this.strictness = strictness; + } + + @Deprecated(since = "3.4.0", forRemoval = true) + @DeprecatedConfigurationProperty(replacement = "spring.gson.strictness", since = "3.4.0") public Boolean getLenient() { - return this.lenient; + return (this.strictness != null) && (this.strictness == Strictness.LENIENT); } public void setLenient(Boolean lenient) { - this.lenient = lenient; + setStrictness((lenient != null && lenient) ? Strictness.LENIENT : Strictness.STRICT); } public Boolean getDisableHtmlEscaping() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbAutoConfiguration.java deleted file mode 100644 index 5641d5182184..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbAutoConfiguration.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.influx; - -import okhttp3.OkHttpClient; -import org.influxdb.InfluxDB; -import org.influxdb.impl.InfluxDBImpl; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; - -/** - * {@link EnableAutoConfiguration Auto-configuration} for InfluxDB. - * - * @author Sergey Kuptsov - * @author Stephane Nicoll - * @author Eddú Meléndez - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - * @since 2.0.0 - */ -@AutoConfiguration -@ConditionalOnClass(InfluxDB.class) -@EnableConfigurationProperties(InfluxDbProperties.class) -@ConditionalOnProperty("spring.influx.url") -public class InfluxDbAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public InfluxDB influxDb(InfluxDbProperties properties, ObjectProvider builder, - ObjectProvider customizers) { - InfluxDB influxDb = new InfluxDBImpl(properties.getUrl().toString(), properties.getUser(), - properties.getPassword(), determineBuilder(builder.getIfAvailable())); - customizers.orderedStream().forEach((customizer) -> customizer.customize(influxDb)); - return influxDb; - } - - private static OkHttpClient.Builder determineBuilder(InfluxDbOkHttpClientBuilderProvider builder) { - if (builder != null) { - return builder.get(); - } - return new OkHttpClient.Builder(); - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbCustomizer.java deleted file mode 100644 index 9e46dd17fa3e..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbCustomizer.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.influx; - -import org.influxdb.InfluxDB; - -/** - * Callback interface that can be implemented by beans wishing to further customize - * {@code InfluxDB} whilst retaining default auto-configuration. - * - * @author Eddú Meléndez - * @since 2.5.0 - */ -@FunctionalInterface -public interface InfluxDbCustomizer { - - /** - * Customize the {@link InfluxDB}. - * @param influxDb the InfluxDB instance to customize - */ - void customize(InfluxDB influxDb); - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbOkHttpClientBuilderProvider.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbOkHttpClientBuilderProvider.java deleted file mode 100644 index 67dc383089de..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbOkHttpClientBuilderProvider.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.influx; - -import java.util.function.Supplier; - -import okhttp3.OkHttpClient; -import org.influxdb.InfluxDB; - -/** - * Provide the {@link okhttp3.OkHttpClient.Builder OkHttpClient.Builder} to use to - * customize the auto-configured {@link InfluxDB} instance. - * - * @author Stephane Nicoll - * @since 2.1.0 - */ -@FunctionalInterface -public interface InfluxDbOkHttpClientBuilderProvider extends Supplier { - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbProperties.java deleted file mode 100644 index d8a4c07d5b68..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbProperties.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.influx; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Configuration properties for InfluxDB. - * - * @author Sergey Kuptsov - * @author Stephane Nicoll - * @since 2.0.0 - */ -@ConfigurationProperties(prefix = "spring.influx") -public class InfluxDbProperties { - - /** - * URL of the InfluxDB instance to which to connect. - */ - private String url; - - /** - * Login user. - */ - private String user; - - /** - * Login password. - */ - private String password; - - public String getUrl() { - return this.url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getUser() { - return this.user; - } - - public void setUser(String user) { - this.user = user; - } - - public String getPassword() { - return this.password; - } - - public void setPassword(String password) { - this.password = password; - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/package-info.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/package-info.java deleted file mode 100644 index d2a8b09d5b39..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Auto-configuration for InfluxDB. - */ -package org.springframework.boot.autoconfigure.influx; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfiguration.java index 9ca2706b611e..b9624755beb6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException; -import org.springframework.boot.task.TaskSchedulerBuilder; +import org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @@ -103,6 +103,9 @@ public static org.springframework.integration.context.IntegrationProperties inte map.from(properties.getError().isIgnoreFailures()).to(integrationProperties::setErrorChannelIgnoreFailures); map.from(properties.getEndpoint().isThrowExceptionOnLateReply()) .to(integrationProperties::setMessagingTemplateThrowExceptionOnLateReply); + map.from(properties.getEndpoint().getDefaultTimeout()) + .as(Duration::toMillis) + .to(integrationProperties::setEndpointsDefaultTimeout); map.from(properties.getEndpoint().getReadOnlyHeaders()) .as(StringUtils::toStringArray) .to(integrationProperties::setReadOnlyHeaders); @@ -166,13 +169,13 @@ private Trigger createPeriodicTrigger(Duration period, Duration initialDelay, bo * scheduling explicitly. */ @Configuration(proxyBeanMethods = false) - @ConditionalOnBean(TaskSchedulerBuilder.class) + @ConditionalOnBean(ThreadPoolTaskSchedulerBuilder.class) @ConditionalOnMissingBean(name = IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME) protected static class IntegrationTaskSchedulerConfiguration { @Bean(name = IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME) - public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) { - return builder.build(); + public ThreadPoolTaskScheduler taskScheduler(ThreadPoolTaskSchedulerBuilder threadPoolTaskSchedulerBuilder) { + return threadPoolTaskSchedulerBuilder.build(); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationProperties.java index e853a98aa5f2..d5bffd5f1bd9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -141,6 +141,11 @@ public static class Endpoint { */ private List noAutoStartup = new ArrayList<>(); + /** + * Default timeout for blocking operations such as sending or receiving messages. + */ + private Duration defaultTimeout = Duration.ofSeconds(30); + public void setThrowExceptionOnLateReply(boolean throwExceptionOnLateReply) { this.throwExceptionOnLateReply = throwExceptionOnLateReply; } @@ -165,6 +170,14 @@ public void setNoAutoStartup(List noAutoStartup) { this.noAutoStartup = noAutoStartup; } + public Duration getDefaultTimeout() { + return this.defaultTimeout; + } + + public void setDefaultTimeout(Duration defaultTimeout) { + this.defaultTimeout = defaultTimeout; + } + } public static class Error { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationPropertiesEnvironmentPostProcessor.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationPropertiesEnvironmentPostProcessor.java index a4e265727086..d9c5f08162d1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationPropertiesEnvironmentPostProcessor.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationPropertiesEnvironmentPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -85,6 +85,7 @@ private static final class IntegrationPropertiesPropertySource extends PropertyS IntegrationProperties.CHANNELS_MAX_BROADCAST_SUBSCRIBERS); mappings.put(PREFIX + "error.require-subscribers", IntegrationProperties.ERROR_CHANNEL_REQUIRE_SUBSCRIBERS); mappings.put(PREFIX + "error.ignore-failures", IntegrationProperties.ERROR_CHANNEL_IGNORE_FAILURES); + mappings.put(PREFIX + "endpoint.default-timeout", IntegrationProperties.ENDPOINTS_DEFAULT_TIMEOUT); mappings.put(PREFIX + "endpoint.throw-exception-on-late-reply", IntegrationProperties.THROW_EXCEPTION_ON_LATE_REPLY); mappings.put(PREFIX + "endpoint.read-only-headers", IntegrationProperties.READ_ONLY_HEADERS); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java index a6aa97773ed3..5410786e4385 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java @@ -213,6 +213,8 @@ public void customize(Jackson2ObjectMapperBuilder builder) { configureFeatures(builder, this.jacksonProperties.getMapper()); configureFeatures(builder, this.jacksonProperties.getParser()); configureFeatures(builder, this.jacksonProperties.getGenerator()); + configureFeatures(builder, this.jacksonProperties.getDatatype().getEnum()); + configureFeatures(builder, this.jacksonProperties.getDatatype().getJsonNode()); configureDateFormat(builder); configurePropertyNamingStrategy(builder); configureModules(builder); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonProperties.java index 805604e98308..46162768d4f5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,8 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.cfg.EnumFeature; +import com.fasterxml.jackson.databind.cfg.JsonNodeFeature; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -38,6 +40,7 @@ * @author Andy Wilkinson * @author Marcel Overdijk * @author Johannes Edmeier + * @author Eddú Meléndez * @since 1.2.0 */ @ConfigurationProperties(prefix = "spring.jackson") @@ -114,6 +117,8 @@ public class JacksonProperties { */ private Locale locale; + private final Datatype datatype = new Datatype(); + public String getDateFormat() { return this.dateFormat; } @@ -194,6 +199,10 @@ public void setLocale(Locale locale) { this.locale = locale; } + public Datatype getDatatype() { + return this.datatype; + } + public enum ConstructorDetectorStrategy { /** @@ -215,7 +224,29 @@ public enum ConstructorDetectorStrategy { * Refuse to decide implicit mode and instead throw an InvalidDefinitionException * for ambiguous cases. */ - EXPLICIT_ONLY; + EXPLICIT_ONLY + + } + + public static class Datatype { + + /** + * Jackson on/off features for enums. + */ + private final Map enumFeatures = new EnumMap<>(EnumFeature.class); + + /** + * Jackson on/off features for JsonNodes. + */ + private final Map jsonNode = new EnumMap<>(JsonNodeFeature.class); + + public Map getEnum() { + return this.enumFeatures; + } + + public Map getJsonNode() { + return this.jsonNode; + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java index 5a41f5d0bdef..43e9620a462b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java @@ -51,13 +51,14 @@ * @author Phillip Webb * @author Stephane Nicoll * @author Kazuki Shimizu + * @author Olga Maciaszek-Sharma * @since 1.0.0 */ @AutoConfiguration(before = SqlInitializationAutoConfiguration.class) @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) @ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory") @EnableConfigurationProperties(DataSourceProperties.class) -@Import(DataSourcePoolMetadataProvidersConfiguration.class) +@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceCheckpointRestoreConfiguration.class }) public class DataSourceAutoConfiguration { @Configuration(proxyBeanMethods = false) @@ -156,6 +157,7 @@ private boolean hasDataSourceUrlProperty(ConditionContext context) { return StringUtils.hasText(environment.getProperty(DATASOURCE_URL_PROPERTY)); } catch (IllegalArgumentException ex) { + // NOTE: This should be PlaceholderResolutionException // Ignore unresolvable placeholder errors } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceCheckpointRestoreConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceCheckpointRestoreConfiguration.java new file mode 100644 index 000000000000..79d1a048b86d --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceCheckpointRestoreConfiguration.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jdbc; + +import javax.sql.DataSource; + +import com.zaxxer.hikari.HikariDataSource; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnCheckpointRestore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.jdbc.HikariCheckpointRestoreLifecycle; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Checkpoint-restore specific configuration. + * + * @author Olga Maciaszek-Sharma + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnCheckpointRestore +@ConditionalOnBean(DataSource.class) +class DataSourceCheckpointRestoreConfiguration { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(HikariDataSource.class) + static class Hikari { + + @Bean + @ConditionalOnMissingBean + HikariCheckpointRestoreLifecycle hikariCheckpointRestoreLifecycle(DataSource dataSource) { + return new HikariCheckpointRestoreLifecycle(dataSource); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java index 8dd321ee7cb4..1cebf37cd5e0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java @@ -172,7 +172,6 @@ PoolDataSourceImpl dataSource(DataSourceProperties properties, JdbcConnectionDet throws SQLException { PoolDataSourceImpl dataSource = createDataSource(connectionDetails, PoolDataSourceImpl.class, properties.getClassLoader()); - dataSource.setValidateConnectionOnBorrow(true); if (StringUtils.hasText(properties.getName())) { dataSource.setConnectionPoolName(properties.getName()); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfiguration.java index e144521271d1..b8b9615e2ccb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; +import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizationAutoConfiguration; import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -46,8 +47,9 @@ * @author Kazuki Shimizu * @since 1.0.0 */ -@AutoConfiguration(before = TransactionAutoConfiguration.class) -@ConditionalOnClass({ JdbcTemplate.class, TransactionManager.class }) +@AutoConfiguration(before = TransactionAutoConfiguration.class, + after = TransactionManagerCustomizationAutoConfiguration.class) +@ConditionalOnClass({ DataSource.class, JdbcTemplate.class, TransactionManager.class }) @AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE) @EnableConfigurationProperties(DataSourceProperties.class) public class DataSourceTransactionManagerAutoConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JdbcClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JdbcClientAutoConfiguration.java new file mode 100644 index 000000000000..9b78ee8e9d09 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JdbcClientAutoConfiguration.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jdbc; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.simple.JdbcClient; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for {@link JdbcClient}. + * + * @author Stephane Nicoll + * @since 3.2.0 + */ +@AutoConfiguration(after = JdbcTemplateAutoConfiguration.class) +@ConditionalOnSingleCandidate(NamedParameterJdbcTemplate.class) +@ConditionalOnMissingBean(JdbcClient.class) +@Import(DatabaseInitializationDependencyConfigurer.class) +public class JdbcClientAutoConfiguration { + + @Bean + JdbcClient jdbcClient(NamedParameterJdbcTemplate jdbcTemplate) { + return JdbcClient.create(jdbcTemplate); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/AcknowledgeMode.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/AcknowledgeMode.java new file mode 100644 index 000000000000..f3c3240d7e2d --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/AcknowledgeMode.java @@ -0,0 +1,105 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jms; + +import java.util.HashMap; +import java.util.Map; + +import jakarta.jms.Session; + +import org.springframework.jms.support.JmsAccessor; + +/** + * Acknowledge modes for a JMS Session. Supports the acknowledge modes defined by + * {@link jakarta.jms.Session} as well as other, non-standard modes. + * + *

+ * Note that {@link jakarta.jms.Session#SESSION_TRANSACTED} is not defined. It should be + * handled through a call to {@link JmsAccessor#setSessionTransacted(boolean)}. + * + * @author Andy Wilkinson + * @since 3.2.0 + */ +public final class AcknowledgeMode { + + private static final Map knownModes = new HashMap<>(3); + + /** + * Messages sent or received from the session are automatically acknowledged. This is + * the simplest mode and enables once-only message delivery guarantee. + */ + public static final AcknowledgeMode AUTO = new AcknowledgeMode(Session.AUTO_ACKNOWLEDGE); + + /** + * Messages are acknowledged once the message listener implementation has called + * {@link jakarta.jms.Message#acknowledge()}. This mode gives the application (rather + * than the JMS provider) complete control over message acknowledgement. + */ + public static final AcknowledgeMode CLIENT = new AcknowledgeMode(Session.CLIENT_ACKNOWLEDGE); + + /** + * Similar to auto acknowledgment except that said acknowledgment is lazy. As a + * consequence, the messages might be delivered more than once. This mode enables + * at-least-once message delivery guarantee. + */ + public static final AcknowledgeMode DUPS_OK = new AcknowledgeMode(Session.DUPS_OK_ACKNOWLEDGE); + + static { + knownModes.put("auto", AUTO); + knownModes.put("client", CLIENT); + knownModes.put("dupsok", DUPS_OK); + } + + private final int mode; + + private AcknowledgeMode(int mode) { + this.mode = mode; + } + + public int getMode() { + return this.mode; + } + + /** + * Creates an {@code AcknowledgeMode} of the given {@code mode}. The mode may be + * {@code auto}, {@code client}, {@code dupsok} or a non-standard acknowledge mode + * that can be {@link Integer#parseInt parsed as an integer}. + * @param mode the mode + * @return the acknowledge mode + */ + public static AcknowledgeMode of(String mode) { + String canonicalMode = canonicalize(mode); + AcknowledgeMode knownMode = knownModes.get(canonicalMode); + try { + return (knownMode != null) ? knownMode : new AcknowledgeMode(Integer.parseInt(canonicalMode)); + } + catch (NumberFormatException ex) { + throw new IllegalArgumentException("'" + mode + + "' is neither a known acknowledge mode (auto, client, or dups_ok) nor an integer value"); + } + } + + private static String canonicalize(String input) { + StringBuilder canonicalName = new StringBuilder(input.length()); + input.chars() + .filter(Character::isLetterOrDigit) + .map(Character::toLowerCase) + .forEach((c) -> canonicalName.append((char) c)); + return canonicalName.toString(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/DefaultJmsListenerContainerFactoryConfigurer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/DefaultJmsListenerContainerFactoryConfigurer.java index 931e8f653d98..66ab3db1ee6c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/DefaultJmsListenerContainerFactoryConfigurer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/DefaultJmsListenerContainerFactoryConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,9 +18,12 @@ import java.time.Duration; +import io.micrometer.observation.ObservationRegistry; import jakarta.jms.ConnectionFactory; import jakarta.jms.ExceptionListener; +import org.springframework.boot.autoconfigure.jms.JmsProperties.Listener.Session; +import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.jms.config.DefaultJmsListenerContainerFactory; import org.springframework.jms.support.converter.MessageConverter; import org.springframework.jms.support.destination.DestinationResolver; @@ -32,6 +35,8 @@ * * @author Stephane Nicoll * @author Eddú Meléndez + * @author Vedran Pavic + * @author Lasse Wulff * @since 1.3.3 */ public final class DefaultJmsListenerContainerFactoryConfigurer { @@ -46,6 +51,8 @@ public final class DefaultJmsListenerContainerFactoryConfigurer { private JmsProperties jmsProperties; + private ObservationRegistry observationRegistry; + /** * Set the {@link DestinationResolver} to use or {@code null} if no destination * resolver should be associated with the factory by default. @@ -90,6 +97,15 @@ void setJmsProperties(JmsProperties jmsProperties) { this.jmsProperties = jmsProperties; } + /** + * Set the {@link ObservationRegistry} to use. + * @param observationRegistry the {@link ObservationRegistry} + * @since 3.2.1 + */ + public void setObservationRegistry(ObservationRegistry observationRegistry) { + this.observationRegistry = observationRegistry; + } + /** * Configure the specified jms listener container factory. The factory can be further * tuned and default settings can be overridden. @@ -99,36 +115,26 @@ void setJmsProperties(JmsProperties jmsProperties) { public void configure(DefaultJmsListenerContainerFactory factory, ConnectionFactory connectionFactory) { Assert.notNull(factory, "Factory must not be null"); Assert.notNull(connectionFactory, "ConnectionFactory must not be null"); + JmsProperties.Listener listenerProperties = this.jmsProperties.getListener(); + Session sessionProperties = listenerProperties.getSession(); factory.setConnectionFactory(connectionFactory); - factory.setPubSubDomain(this.jmsProperties.isPubSubDomain()); - if (this.transactionManager != null) { - factory.setTransactionManager(this.transactionManager); - } - else { + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(this.jmsProperties::isPubSubDomain).to(factory::setPubSubDomain); + map.from(this.jmsProperties::isSubscriptionDurable).to(factory::setSubscriptionDurable); + map.from(this.jmsProperties::getClientId).to(factory::setClientId); + map.from(this.transactionManager).to(factory::setTransactionManager); + map.from(this.destinationResolver).to(factory::setDestinationResolver); + map.from(this.messageConverter).to(factory::setMessageConverter); + map.from(this.exceptionListener).to(factory::setExceptionListener); + map.from(sessionProperties.getAcknowledgeMode()::getMode).to(factory::setSessionAcknowledgeMode); + if (this.transactionManager == null && sessionProperties.getTransacted() == null) { factory.setSessionTransacted(true); } - if (this.destinationResolver != null) { - factory.setDestinationResolver(this.destinationResolver); - } - if (this.messageConverter != null) { - factory.setMessageConverter(this.messageConverter); - } - if (this.exceptionListener != null) { - factory.setExceptionListener(this.exceptionListener); - } - JmsProperties.Listener listener = this.jmsProperties.getListener(); - factory.setAutoStartup(listener.isAutoStartup()); - if (listener.getAcknowledgeMode() != null) { - factory.setSessionAcknowledgeMode(listener.getAcknowledgeMode().getMode()); - } - String concurrency = listener.formatConcurrency(); - if (concurrency != null) { - factory.setConcurrency(concurrency); - } - Duration receiveTimeout = listener.getReceiveTimeout(); - if (receiveTimeout != null) { - factory.setReceiveTimeout(receiveTimeout.toMillis()); - } + map.from(this.observationRegistry).to(factory::setObservationRegistry); + map.from(sessionProperties::getTransacted).to(factory::setSessionTransacted); + map.from(listenerProperties::isAutoStartup).to(factory::setAutoStartup); + map.from(listenerProperties::formatConcurrency).to(factory::setConcurrency); + map.from(listenerProperties::getReceiveTimeout).as(Duration::toMillis).to(factory::setReceiveTimeout); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsAnnotationDrivenConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsAnnotationDrivenConfiguration.java index c503fa51842c..6de5bc8fcf04 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsAnnotationDrivenConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsAnnotationDrivenConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.jms; +import io.micrometer.observation.ObservationRegistry; import jakarta.jms.ConnectionFactory; import jakarta.jms.ExceptionListener; @@ -24,6 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnJndi; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.boot.jms.ConnectionFactoryUnwrapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jms.annotation.EnableJms; @@ -53,15 +55,19 @@ class JmsAnnotationDrivenConfiguration { private final ObjectProvider exceptionListener; + private final ObjectProvider observationRegistry; + private final JmsProperties properties; JmsAnnotationDrivenConfiguration(ObjectProvider destinationResolver, ObjectProvider transactionManager, ObjectProvider messageConverter, - ObjectProvider exceptionListener, JmsProperties properties) { + ObjectProvider exceptionListener, + ObjectProvider observationRegistry, JmsProperties properties) { this.destinationResolver = destinationResolver; this.transactionManager = transactionManager; this.messageConverter = messageConverter; this.exceptionListener = exceptionListener; + this.observationRegistry = observationRegistry; this.properties = properties; } @@ -73,6 +79,7 @@ DefaultJmsListenerContainerFactoryConfigurer jmsListenerContainerFactoryConfigur configurer.setTransactionManager(this.transactionManager.getIfUnique()); configurer.setMessageConverter(this.messageConverter.getIfUnique()); configurer.setExceptionListener(this.exceptionListener.getIfUnique()); + configurer.setObservationRegistry(this.observationRegistry.getIfUnique()); configurer.setJmsProperties(this.properties); return configurer; } @@ -83,7 +90,7 @@ DefaultJmsListenerContainerFactoryConfigurer jmsListenerContainerFactoryConfigur DefaultJmsListenerContainerFactory jmsListenerContainerFactory( DefaultJmsListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); - configurer.configure(factory, connectionFactory); + configurer.configure(factory, ConnectionFactoryUnwrapper.unwrap(connectionFactory)); return factory; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfiguration.java index e256a0a63d47..44b049a657ed 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfiguration.java @@ -17,10 +17,16 @@ package org.springframework.boot.autoconfigure.jms; import java.time.Duration; +import java.util.List; +import io.micrometer.observation.ObservationRegistry; import jakarta.jms.ConnectionFactory; import jakarta.jms.Message; +import org.springframework.aot.hint.ExecutableMode; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.TypeReference; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -28,6 +34,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration.JmsRuntimeHints; import org.springframework.boot.autoconfigure.jms.JmsProperties.DeliveryMode; import org.springframework.boot.autoconfigure.jms.JmsProperties.Template; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -35,6 +42,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.jms.core.JmsMessageOperations; import org.springframework.jms.core.JmsMessagingTemplate; import org.springframework.jms.core.JmsOperations; @@ -47,6 +55,7 @@ * * @author Greg Turnquist * @author Stephane Nicoll + * @author Vedran Pavic * @since 1.0.0 */ @AutoConfiguration @@ -54,6 +63,7 @@ @ConditionalOnBean(ConnectionFactory.class) @EnableConfigurationProperties(JmsProperties.class) @Import(JmsAnnotationDrivenConfiguration.class) +@ImportRuntimeHints(JmsRuntimeHints.class) public class JmsAutoConfiguration { @Configuration(proxyBeanMethods = false) @@ -65,12 +75,16 @@ protected static class JmsTemplateConfiguration { private final ObjectProvider messageConverter; + private final ObjectProvider observationRegistry; + public JmsTemplateConfiguration(JmsProperties properties, ObjectProvider destinationResolver, - ObjectProvider messageConverter) { + ObjectProvider messageConverter, + ObjectProvider observationRegistry) { this.properties = properties; this.destinationResolver = destinationResolver; this.messageConverter = messageConverter; + this.observationRegistry = observationRegistry; } @Bean @@ -82,25 +96,22 @@ public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) { template.setPubSubDomain(this.properties.isPubSubDomain()); map.from(this.destinationResolver::getIfUnique).whenNonNull().to(template::setDestinationResolver); map.from(this.messageConverter::getIfUnique).whenNonNull().to(template::setMessageConverter); + map.from(this.observationRegistry::getIfUnique).whenNonNull().to(template::setObservationRegistry); mapTemplateProperties(this.properties.getTemplate(), template); return template; } private void mapTemplateProperties(Template properties, JmsTemplate template) { - PropertyMapper map = PropertyMapper.get(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties.getSession().getAcknowledgeMode()::getMode).to(template::setSessionAcknowledgeMode); + map.from(properties.getSession()::isTransacted).to(template::setSessionTransacted); map.from(properties::getDefaultDestination).whenNonNull().to(template::setDefaultDestinationName); map.from(properties::getDeliveryDelay).whenNonNull().as(Duration::toMillis).to(template::setDeliveryDelay); map.from(properties::determineQosEnabled).to(template::setExplicitQosEnabled); - map.from(properties::getDeliveryMode) - .whenNonNull() - .as(DeliveryMode::getValue) - .to(template::setDeliveryMode); + map.from(properties::getDeliveryMode).as(DeliveryMode::getValue).to(template::setDeliveryMode); map.from(properties::getPriority).whenNonNull().to(template::setPriority); map.from(properties::getTimeToLive).whenNonNull().as(Duration::toMillis).to(template::setTimeToLive); - map.from(properties::getReceiveTimeout) - .whenNonNull() - .as(Duration::toMillis) - .to(template::setReceiveTimeout); + map.from(properties::getReceiveTimeout).as(Duration::toMillis).to(template::setReceiveTimeout); } } @@ -126,4 +137,15 @@ private void mapTemplateProperties(Template properties, JmsMessagingTemplate mes } + static class JmsRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.reflection() + .registerType(TypeReference.of(AcknowledgeMode.class), (type) -> type.withMethod("of", + List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE)); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsProperties.java index ea524db6388e..7d87ec9169cb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.time.Duration; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; /** * Configuration properties for JMS. @@ -26,6 +27,8 @@ * @author Greg Turnquist * @author Phillip Webb * @author Stephane Nicoll + * @author Lasse Wulff + * @author Vedran Pavic * @since 1.0.0 */ @ConfigurationProperties(prefix = "spring.jms") @@ -42,6 +45,16 @@ public class JmsProperties { */ private String jndiName; + /** + * Whether the subscription is durable. + */ + private boolean subscriptionDurable = false; + + /** + * Client id of the connection. + */ + private String clientId; + private final Cache cache = new Cache(); private final Listener listener = new Listener(); @@ -56,6 +69,22 @@ public void setPubSubDomain(boolean pubSubDomain) { this.pubSubDomain = pubSubDomain; } + public boolean isSubscriptionDurable() { + return this.subscriptionDurable; + } + + public void setSubscriptionDurable(boolean subscriptionDurable) { + this.subscriptionDurable = subscriptionDurable; + } + + public String getClientId() { + return this.clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + public String getJndiName() { return this.jndiName; } @@ -139,17 +168,11 @@ public static class Listener { */ private boolean autoStartup = true; - /** - * Acknowledge mode of the container. By default, the listener is transacted with - * automatic acknowledgment. - */ - private AcknowledgeMode acknowledgeMode; - /** * Minimum number of concurrent consumers. When max-concurrency is not specified * the minimum will also be used as the maximum. */ - private Integer concurrency; + private Integer minConcurrency; /** * Maximum number of concurrent consumers. @@ -163,6 +186,8 @@ public static class Listener { */ private Duration receiveTimeout = Duration.ofSeconds(1); + private final Session session = new Session(); + public boolean isAutoStartup() { return this.autoStartup; } @@ -171,20 +196,34 @@ public void setAutoStartup(boolean autoStartup) { this.autoStartup = autoStartup; } + @Deprecated(since = "3.2.0", forRemoval = true) + @DeprecatedConfigurationProperty(replacement = "spring.jms.listener.session.acknowledge-mode", since = "3.2.0") public AcknowledgeMode getAcknowledgeMode() { - return this.acknowledgeMode; + return this.session.getAcknowledgeMode(); } + @Deprecated(since = "3.2.0", forRemoval = true) public void setAcknowledgeMode(AcknowledgeMode acknowledgeMode) { - this.acknowledgeMode = acknowledgeMode; + this.session.setAcknowledgeMode(acknowledgeMode); } + @DeprecatedConfigurationProperty(replacement = "spring.jms.listener.min-concurrency", since = "3.2.0") + @Deprecated(since = "3.2.0", forRemoval = true) public Integer getConcurrency() { - return this.concurrency; + return this.minConcurrency; } + @Deprecated(since = "3.2.0", forRemoval = true) public void setConcurrency(Integer concurrency) { - this.concurrency = concurrency; + this.minConcurrency = concurrency; + } + + public Integer getMinConcurrency() { + return this.minConcurrency; + } + + public void setMinConcurrency(Integer minConcurrency) { + this.minConcurrency = minConcurrency; } public Integer getMaxConcurrency() { @@ -196,10 +235,11 @@ public void setMaxConcurrency(Integer maxConcurrency) { } public String formatConcurrency() { - if (this.concurrency == null) { + if (this.minConcurrency == null) { return (this.maxConcurrency != null) ? "1-" + this.maxConcurrency : null; } - return this.concurrency + "-" + ((this.maxConcurrency != null) ? this.maxConcurrency : this.concurrency); + return this.minConcurrency + "-" + + ((this.maxConcurrency != null) ? this.maxConcurrency : this.minConcurrency); } public Duration getReceiveTimeout() { @@ -210,6 +250,41 @@ public void setReceiveTimeout(Duration receiveTimeout) { this.receiveTimeout = receiveTimeout; } + public Session getSession() { + return this.session; + } + + public static class Session { + + /** + * Acknowledge mode of the listener container. + */ + private AcknowledgeMode acknowledgeMode = AcknowledgeMode.AUTO; + + /** + * Whether the listener container should use transacted JMS sessions. Defaults + * to false in the presence of a JtaTransactionManager and true otherwise. + */ + private Boolean transacted; + + public AcknowledgeMode getAcknowledgeMode() { + return this.acknowledgeMode; + } + + public void setAcknowledgeMode(AcknowledgeMode acknowledgeMode) { + this.acknowledgeMode = acknowledgeMode; + } + + public Boolean getTransacted() { + return this.transacted; + } + + public void setTransacted(Boolean transacted) { + this.transacted = transacted; + } + + } + } public static class Template { @@ -254,6 +329,8 @@ public static class Template { */ private Duration receiveTimeout; + private final Session session = new Session(); + public String getDefaultDestination() { return this.defaultDestination; } @@ -317,45 +394,38 @@ public void setReceiveTimeout(Duration receiveTimeout) { this.receiveTimeout = receiveTimeout; } - } + public Session getSession() { + return this.session; + } - /** - * Translate the acknowledge modes defined on the {@link jakarta.jms.Session}. - * - *

- * {@link jakarta.jms.Session#SESSION_TRANSACTED} is not defined as we take care of - * this already through a call to {@code setSessionTransacted}. - */ - public enum AcknowledgeMode { + public static class Session { - /** - * Messages sent or received from the session are automatically acknowledged. This - * is the simplest mode and enables once-only message delivery guarantee. - */ - AUTO(1), + /** + * Acknowledge mode used when creating sessions. + */ + private AcknowledgeMode acknowledgeMode = AcknowledgeMode.AUTO; - /** - * Messages are acknowledged once the message listener implementation has called - * {@link jakarta.jms.Message#acknowledge()}. This mode gives the application - * (rather than the JMS provider) complete control over message acknowledgement. - */ - CLIENT(2), + /** + * Whether to use transacted sessions. + */ + private boolean transacted = false; - /** - * Similar to auto acknowledgment except that said acknowledgment is lazy. As a - * consequence, the messages might be delivered more than once. This mode enables - * at-least-once message delivery guarantee. - */ - DUPS_OK(3); + public AcknowledgeMode getAcknowledgeMode() { + return this.acknowledgeMode; + } - private final int mode; + public void setAcknowledgeMode(AcknowledgeMode acknowledgeMode) { + this.acknowledgeMode = acknowledgeMode; + } - AcknowledgeMode(int mode) { - this.mode = mode; - } + public boolean isTransacted() { + return this.transacted; + } + + public void setTransacted(boolean transacted) { + this.transacted = transacted; + } - public int getMode() { - return this.mode; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java index da642a7f8689..44c3cebe3082 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java @@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.jms.JmsProperties; import org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; /** @@ -35,6 +36,7 @@ * * @author Stephane Nicoll * @author Phillip Webb + * @author Eddú Meléndez * @since 3.1.0 */ @AutoConfiguration(before = JmsAutoConfiguration.class, after = JndiConnectionFactoryAutoConfiguration.class) @@ -44,4 +46,38 @@ @Import({ ActiveMQXAConnectionFactoryConfiguration.class, ActiveMQConnectionFactoryConfiguration.class }) public class ActiveMQAutoConfiguration { + @Bean + @ConditionalOnMissingBean(ActiveMQConnectionDetails.class) + ActiveMQConnectionDetails activemqConnectionDetails(ActiveMQProperties properties) { + return new PropertiesActiveMQConnectionDetails(properties); + } + + /** + * Adapts {@link ActiveMQProperties} to {@link ActiveMQConnectionDetails}. + */ + static class PropertiesActiveMQConnectionDetails implements ActiveMQConnectionDetails { + + private final ActiveMQProperties properties; + + PropertiesActiveMQConnectionDetails(ActiveMQProperties properties) { + this.properties = properties; + } + + @Override + public String getBrokerUrl() { + return this.properties.determineBrokerUrl(); + } + + @Override + public String getUser() { + return this.properties.getUser(); + } + + @Override + public String getPassword() { + return this.properties.getPassword(); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionDetails.java new file mode 100644 index 000000000000..9c095cfda901 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionDetails.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jms.activemq; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +/** + * Details required to establish a connection to an ActiveMQ service. + * + * @author Eddú Meléndez + * @author Stephane Nicoll + * @since 3.2.0 + */ +public interface ActiveMQConnectionDetails extends ConnectionDetails { + + /** + * Broker URL to use. + * @return the url of the broker + */ + String getBrokerUrl(); + + /** + * Login user to authenticate to the broker. + * @return the login user to authenticate to the broker or {@code null} + */ + String getUser(); + + /** + * Login to authenticate against the broker. + * @return the login to authenticate against the broker or {@code null} + */ + String getPassword(); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java index a55337e58a2c..897c69576c0d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,7 @@ * @author Phillip Webb * @author Andy Wilkinson * @author Aurélien Leboulanger + * @author Eddú Meléndez */ @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(ConnectionFactory.class) @@ -52,14 +53,19 @@ static class SimpleConnectionFactoryConfiguration { @Bean @ConditionalOnProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = "false") ActiveMQConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, - ObjectProvider factoryCustomizers) { - return createJmsConnectionFactory(properties, factoryCustomizers); + ObjectProvider factoryCustomizers, + ActiveMQConnectionDetails connectionDetails) { + return createJmsConnectionFactory(properties, factoryCustomizers, connectionDetails); } private static ActiveMQConnectionFactory createJmsConnectionFactory(ActiveMQProperties properties, - ObjectProvider factoryCustomizers) { - return new ActiveMQConnectionFactoryFactory(properties, factoryCustomizers.orderedStream().toList()) - .createConnectionFactory(ActiveMQConnectionFactory.class); + ObjectProvider factoryCustomizers, + ActiveMQConnectionDetails connectionDetails) { + ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(connectionDetails.getUser(), + connectionDetails.getPassword(), connectionDetails.getBrokerUrl()); + new ActiveMQConnectionFactoryConfigurer(properties, factoryCustomizers.orderedStream().toList()) + .configure(connectionFactory); + return connectionFactory; } @Configuration(proxyBeanMethods = false) @@ -70,10 +76,11 @@ static class CachingConnectionFactoryConfiguration { @Bean CachingConnectionFactory jmsConnectionFactory(JmsProperties jmsProperties, ActiveMQProperties properties, - ObjectProvider factoryCustomizers) { + ObjectProvider factoryCustomizers, + ActiveMQConnectionDetails connectionDetails) { JmsProperties.Cache cacheProperties = jmsProperties.getCache(); CachingConnectionFactory connectionFactory = new CachingConnectionFactory( - createJmsConnectionFactory(properties, factoryCustomizers)); + createJmsConnectionFactory(properties, factoryCustomizers, connectionDetails)); connectionFactory.setCacheConsumers(cacheProperties.isConsumers()); connectionFactory.setCacheProducers(cacheProperties.isProducers()); connectionFactory.setSessionCacheSize(cacheProperties.getSessionCacheSize()); @@ -91,10 +98,12 @@ static class PooledConnectionFactoryConfiguration { @Bean(destroyMethod = "stop") @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "true") JmsPoolConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, - ObjectProvider factoryCustomizers) { - ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(properties, - factoryCustomizers.orderedStream().toList()) - .createConnectionFactory(ActiveMQConnectionFactory.class); + ObjectProvider factoryCustomizers, + ActiveMQConnectionDetails connectionDetails) { + ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(connectionDetails.getUser(), + connectionDetails.getPassword(), connectionDetails.getBrokerUrl()); + new ActiveMQConnectionFactoryConfigurer(properties, factoryCustomizers.orderedStream().toList()) + .configure(connectionFactory); return new JmsPoolConnectionFactoryFactory(properties.getPool()) .createPooledConnectionFactory(connectionFactory); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfigurer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfigurer.java new file mode 100644 index 000000000000..aa9c027540f4 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfigurer.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jms.activemq; + +import java.util.Collections; +import java.util.List; + +import org.apache.activemq.ActiveMQConnectionFactory; + +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQProperties.Packages; +import org.springframework.util.Assert; + +/** + * Class to configure an {@link ActiveMQConnectionFactory} instance from properties + * defined in {@link ActiveMQProperties} and any + * {@link ActiveMQConnectionFactoryCustomizer customizers}. + * + * @author Phillip Webb + * @author Venil Noronha + * @author Eddú Meléndez + */ +class ActiveMQConnectionFactoryConfigurer { + + private final ActiveMQProperties properties; + + private final List factoryCustomizers; + + ActiveMQConnectionFactoryConfigurer(ActiveMQProperties properties, + List factoryCustomizers) { + Assert.notNull(properties, "Properties must not be null"); + this.properties = properties; + this.factoryCustomizers = (factoryCustomizers != null) ? factoryCustomizers : Collections.emptyList(); + } + + void configure(ActiveMQConnectionFactory factory) { + if (this.properties.getCloseTimeout() != null) { + factory.setCloseTimeout((int) this.properties.getCloseTimeout().toMillis()); + } + factory.setNonBlockingRedelivery(this.properties.isNonBlockingRedelivery()); + if (this.properties.getSendTimeout() != null) { + factory.setSendTimeout((int) this.properties.getSendTimeout().toMillis()); + } + Packages packages = this.properties.getPackages(); + if (packages.getTrustAll() != null) { + factory.setTrustAllPackages(packages.getTrustAll()); + } + if (!packages.getTrusted().isEmpty()) { + factory.setTrustedPackages(packages.getTrusted()); + } + customize(factory); + } + + private void customize(ActiveMQConnectionFactory connectionFactory) { + for (ActiveMQConnectionFactoryCustomizer factoryCustomizer : this.factoryCustomizers) { + factoryCustomizer.customize(connectionFactory); + } + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryFactory.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryFactory.java deleted file mode 100644 index b571860491f0..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryFactory.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.jms.activemq; - -import java.lang.reflect.InvocationTargetException; -import java.util.Collections; -import java.util.List; - -import org.apache.activemq.ActiveMQConnectionFactory; - -import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQProperties.Packages; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Factory to create a {@link ActiveMQConnectionFactory} instance from properties defined - * in {@link ActiveMQProperties}. - * - * @author Phillip Webb - * @author Venil Noronha - */ -class ActiveMQConnectionFactoryFactory { - - private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616"; - - private final ActiveMQProperties properties; - - private final List factoryCustomizers; - - ActiveMQConnectionFactoryFactory(ActiveMQProperties properties, - List factoryCustomizers) { - Assert.notNull(properties, "Properties must not be null"); - this.properties = properties; - this.factoryCustomizers = (factoryCustomizers != null) ? factoryCustomizers : Collections.emptyList(); - } - - T createConnectionFactory(Class factoryClass) { - try { - return doCreateConnectionFactory(factoryClass); - } - catch (Exception ex) { - throw new IllegalStateException("Unable to create ActiveMQConnectionFactory", ex); - } - } - - private T doCreateConnectionFactory(Class factoryClass) throws Exception { - T factory = createConnectionFactoryInstance(factoryClass); - if (this.properties.getCloseTimeout() != null) { - factory.setCloseTimeout((int) this.properties.getCloseTimeout().toMillis()); - } - factory.setNonBlockingRedelivery(this.properties.isNonBlockingRedelivery()); - if (this.properties.getSendTimeout() != null) { - factory.setSendTimeout((int) this.properties.getSendTimeout().toMillis()); - } - Packages packages = this.properties.getPackages(); - if (packages.getTrustAll() != null) { - factory.setTrustAllPackages(packages.getTrustAll()); - } - if (!packages.getTrusted().isEmpty()) { - factory.setTrustedPackages(packages.getTrusted()); - } - customize(factory); - return factory; - } - - private T createConnectionFactoryInstance(Class factoryClass) - throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { - String brokerUrl = determineBrokerUrl(); - String user = this.properties.getUser(); - String password = this.properties.getPassword(); - if (StringUtils.hasLength(user) && StringUtils.hasLength(password)) { - return factoryClass.getConstructor(String.class, String.class, String.class) - .newInstance(user, password, brokerUrl); - } - return factoryClass.getConstructor(String.class).newInstance(brokerUrl); - } - - private void customize(ActiveMQConnectionFactory connectionFactory) { - for (ActiveMQConnectionFactoryCustomizer factoryCustomizer : this.factoryCustomizers) { - factoryCustomizer.customize(connectionFactory); - } - } - - String determineBrokerUrl() { - if (this.properties.getBrokerUrl() != null) { - return this.properties.getBrokerUrl(); - } - return DEFAULT_NETWORK_BROKER_URL; - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java index 48b72e88935c..2877479a08e6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java @@ -31,11 +31,14 @@ * @author Stephane Nicoll * @author Aurélien Leboulanger * @author Venil Noronha + * @author Eddú Meléndez * @since 3.1.0 */ @ConfigurationProperties(prefix = "spring.activemq") public class ActiveMQProperties { + private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616"; + /** * URL of the ActiveMQ broker. Auto-generated by default. */ @@ -128,6 +131,13 @@ public Packages getPackages() { return this.packages; } + String determineBrokerUrl() { + if (this.brokerUrl != null) { + return this.brokerUrl; + } + return DEFAULT_NETWORK_BROKER_URL; + } + public static class Packages { /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQXAConnectionFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQXAConnectionFactoryConfiguration.java index 4a7cbd214cea..d03fabc1192a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQXAConnectionFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQXAConnectionFactoryConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ * * @author Phillip Webb * @author Aurélien Leboulanger + * @author Eddú Meléndez */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(TransactionManager.class) @@ -46,11 +47,12 @@ class ActiveMQXAConnectionFactoryConfiguration { @Primary @Bean(name = { "jmsConnectionFactory", "xaJmsConnectionFactory" }) ConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, - ObjectProvider factoryCustomizers, XAConnectionFactoryWrapper wrapper) - throws Exception { - ActiveMQXAConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(properties, - factoryCustomizers.orderedStream().toList()) - .createConnectionFactory(ActiveMQXAConnectionFactory.class); + ObjectProvider factoryCustomizers, XAConnectionFactoryWrapper wrapper, + ActiveMQConnectionDetails connectionDetails) throws Exception { + ActiveMQXAConnectionFactory connectionFactory = new ActiveMQXAConnectionFactory(connectionDetails.getUser(), + connectionDetails.getPassword(), connectionDetails.getBrokerUrl()); + new ActiveMQConnectionFactoryConfigurer(properties, factoryCustomizers.orderedStream().toList()) + .configure(connectionFactory); return wrapper.wrapConnectionFactory(connectionFactory); } @@ -58,9 +60,13 @@ ConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "false", matchIfMissing = true) ActiveMQConnectionFactory nonXaJmsConnectionFactory(ActiveMQProperties properties, - ObjectProvider factoryCustomizers) { - return new ActiveMQConnectionFactoryFactory(properties, factoryCustomizers.orderedStream().toList()) - .createConnectionFactory(ActiveMQConnectionFactory.class); + ObjectProvider factoryCustomizers, + ActiveMQConnectionDetails connectionDetails) { + ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(connectionDetails.getUser(), + connectionDetails.getPassword(), connectionDetails.getBrokerUrl()); + new ActiveMQConnectionFactoryConfigurer(properties, factoryCustomizers.orderedStream().toList()) + .configure(connectionFactory); + return connectionFactory; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfiguration.java index 1087934873b3..a4732f0dd581 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.jms.JmsProperties; import org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; /** @@ -48,4 +49,43 @@ ArtemisConnectionFactoryConfiguration.class }) public class ArtemisAutoConfiguration { + @Bean + @ConditionalOnMissingBean(ArtemisConnectionDetails.class) + ArtemisConnectionDetails artemisConnectionDetails(ArtemisProperties properties) { + return new PropertiesArtemisConnectionDetails(properties); + } + + /** + * Adapts {@link ArtemisProperties} to {@link ArtemisConnectionDetails}. + */ + static class PropertiesArtemisConnectionDetails implements ArtemisConnectionDetails { + + private final ArtemisProperties properties; + + PropertiesArtemisConnectionDetails(ArtemisProperties properties) { + this.properties = properties; + } + + @Override + public ArtemisMode getMode() { + return this.properties.getMode(); + } + + @Override + public String getBrokerUrl() { + return this.properties.getBrokerUrl(); + } + + @Override + public String getUser() { + return this.properties.getUser(); + } + + @Override + public String getPassword() { + return this.properties.getPassword(); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionDetails.java new file mode 100644 index 000000000000..dea123a3c186 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionDetails.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jms.artemis; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +/** + * Details required to establish a connection to an Artemis service. + * + * @author Eddú Meléndez + * @since 3.3.0 + */ +public interface ArtemisConnectionDetails extends ConnectionDetails { + + /** + * Artemis deployment mode, auto-detected by default. + * @return the Artemis deployment mode, auto-detected by default + */ + ArtemisMode getMode(); + + /** + * Artemis broker url. + * @return the Artemis broker url + */ + String getBrokerUrl(); + + /** + * Login user of the broker. + * @return the login user of the broker + */ + String getUser(); + + /** + * Login password of the broker. + * @return the login password of the broker + */ + String getPassword(); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java index 98a305be8f38..5f4f879431d6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,13 +49,14 @@ static class SimpleConnectionFactoryConfiguration { @Bean(name = "jmsConnectionFactory") @ConditionalOnProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = "false") - ActiveMQConnectionFactory jmsConnectionFactory(ArtemisProperties properties, ListableBeanFactory beanFactory) { - return createJmsConnectionFactory(properties, beanFactory); + ActiveMQConnectionFactory jmsConnectionFactory(ArtemisProperties properties, ListableBeanFactory beanFactory, + ArtemisConnectionDetails connectionDetails) { + return createJmsConnectionFactory(properties, connectionDetails, beanFactory); } private static ActiveMQConnectionFactory createJmsConnectionFactory(ArtemisProperties properties, - ListableBeanFactory beanFactory) { - return new ArtemisConnectionFactoryFactory(beanFactory, properties) + ArtemisConnectionDetails connectionDetails, ListableBeanFactory beanFactory) { + return new ArtemisConnectionFactoryFactory(beanFactory, properties, connectionDetails) .createConnectionFactory(ActiveMQConnectionFactory.class); } @@ -67,10 +68,11 @@ static class CachingConnectionFactoryConfiguration { @Bean(name = "jmsConnectionFactory") CachingConnectionFactory cachingJmsConnectionFactory(JmsProperties jmsProperties, - ArtemisProperties properties, ListableBeanFactory beanFactory) { + ArtemisProperties properties, ArtemisConnectionDetails connectionDetails, + ListableBeanFactory beanFactory) { JmsProperties.Cache cacheProperties = jmsProperties.getCache(); CachingConnectionFactory connectionFactory = new CachingConnectionFactory( - createJmsConnectionFactory(properties, beanFactory)); + createJmsConnectionFactory(properties, connectionDetails, beanFactory)); connectionFactory.setCacheConsumers(cacheProperties.isConsumers()); connectionFactory.setCacheProducers(cacheProperties.isProducers()); connectionFactory.setSessionCacheSize(cacheProperties.getSessionCacheSize()); @@ -87,8 +89,10 @@ CachingConnectionFactory cachingJmsConnectionFactory(JmsProperties jmsProperties static class PooledConnectionFactoryConfiguration { @Bean(destroyMethod = "stop") - JmsPoolConnectionFactory jmsConnectionFactory(ListableBeanFactory beanFactory, ArtemisProperties properties) { - ActiveMQConnectionFactory connectionFactory = new ArtemisConnectionFactoryFactory(beanFactory, properties) + JmsPoolConnectionFactory jmsConnectionFactory(ListableBeanFactory beanFactory, ArtemisProperties properties, + ArtemisConnectionDetails connectionDetails) { + ActiveMQConnectionFactory connectionFactory = new ArtemisConnectionFactoryFactory(beanFactory, properties, + connectionDetails) .createConnectionFactory(ActiveMQConnectionFactory.class); return new JmsPoolConnectionFactoryFactory(properties.getPool()) .createPooledConnectionFactory(connectionFactory); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryFactory.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryFactory.java index 344f8ace981c..70e74e4b3e44 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryFactory.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,13 +47,18 @@ class ArtemisConnectionFactoryFactory { private final ArtemisProperties properties; + private final ArtemisConnectionDetails connectionDetails; + private final ListableBeanFactory beanFactory; - ArtemisConnectionFactoryFactory(ListableBeanFactory beanFactory, ArtemisProperties properties) { + ArtemisConnectionFactoryFactory(ListableBeanFactory beanFactory, ArtemisProperties properties, + ArtemisConnectionDetails connectionDetails) { Assert.notNull(beanFactory, "BeanFactory must not be null"); Assert.notNull(properties, "Properties must not be null"); + Assert.notNull(connectionDetails, "ConnectionDetails must not be null"); this.beanFactory = beanFactory; this.properties = properties; + this.connectionDetails = connectionDetails; } T createConnectionFactory(Class factoryClass) { @@ -80,7 +85,7 @@ private void startEmbeddedJms() { } private T doCreateConnectionFactory(Class factoryClass) throws Exception { - ArtemisMode mode = this.properties.getMode(); + ArtemisMode mode = this.connectionDetails.getMode(); if (mode == null) { mode = deduceMode(); } @@ -127,17 +132,17 @@ private T createEmbeddedConnectionFactory( private T createNativeConnectionFactory(Class factoryClass) throws Exception { T connectionFactory = newNativeConnectionFactory(factoryClass); - String user = this.properties.getUser(); + String user = this.connectionDetails.getUser(); if (StringUtils.hasText(user)) { connectionFactory.setUser(user); - connectionFactory.setPassword(this.properties.getPassword()); + connectionFactory.setPassword(this.connectionDetails.getPassword()); } return connectionFactory; } private T newNativeConnectionFactory(Class factoryClass) throws Exception { - String brokerUrl = StringUtils.hasText(this.properties.getBrokerUrl()) ? this.properties.getBrokerUrl() - : DEFAULT_BROKER_URL; + String brokerUrl = StringUtils.hasText(this.connectionDetails.getBrokerUrl()) + ? this.connectionDetails.getBrokerUrl() : DEFAULT_BROKER_URL; Constructor constructor = factoryClass.getConstructor(String.class); return constructor.newInstance(brokerUrl); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedConfigurationFactory.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedConfigurationFactory.java index ee5aca349897..1c1e505f8108 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedConfigurationFactory.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedConfigurationFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -69,16 +69,15 @@ Configuration createConfiguration() { configuration.setClusterPassword(this.properties.getClusterPassword()); configuration.addAddressConfiguration(createAddressConfiguration("DLQ")); configuration.addAddressConfiguration(createAddressConfiguration("ExpiryQueue")); - configuration.addAddressSetting("#", - new AddressSettings().setDeadLetterAddress(SimpleString.toSimpleString("DLQ")) - .setExpiryAddress(SimpleString.toSimpleString("ExpiryQueue"))); + configuration.addAddressSetting("#", new AddressSettings().setDeadLetterAddress(SimpleString.of("DLQ")) + .setExpiryAddress(SimpleString.of("ExpiryQueue"))); return configuration; } private CoreAddressConfiguration createAddressConfiguration(String name) { return new CoreAddressConfiguration().setName(name) .addRoutingType(RoutingType.ANYCAST) - .addQueueConfiguration(new QueueConfiguration(name).setRoutingType(RoutingType.ANYCAST).setAddress(name)); + .addQueueConfiguration(QueueConfiguration.of(name).setRoutingType(RoutingType.ANYCAST).setAddress(name)); } private String getDataDir() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedServerConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedServerConfiguration.java index 6517739b6f1a..a3d2128eade8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedServerConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedServerConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,7 +68,8 @@ EmbeddedActiveMQ embeddedActiveMq(org.apache.activemq.artemis.core.config.Config String queueName = queueConfiguration.getName(); configuration.addAddressConfiguration(new CoreAddressConfiguration().setName(queueName) .addRoutingType(RoutingType.ANYCAST) - .addQueueConfiguration(new QueueConfiguration(queueName).setAddress(queueName) + .addQueueConfiguration(QueueConfiguration.of(queueName) + .setAddress(queueName) .setFilterString(queueConfiguration.getSelector()) .setDurable(queueConfiguration.isDurable()) .setRoutingType(RoutingType.ANYCAST))); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisXAConnectionFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisXAConnectionFactoryConfiguration.java index b296ae14b892..89e7823ca226 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisXAConnectionFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisXAConnectionFactoryConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,15 +44,16 @@ class ArtemisXAConnectionFactoryConfiguration { @Primary @Bean(name = { "jmsConnectionFactory", "xaJmsConnectionFactory" }) ConnectionFactory jmsConnectionFactory(ListableBeanFactory beanFactory, ArtemisProperties properties, - XAConnectionFactoryWrapper wrapper) throws Exception { - return wrapper.wrapConnectionFactory(new ArtemisConnectionFactoryFactory(beanFactory, properties) - .createConnectionFactory(ActiveMQXAConnectionFactory.class)); + ArtemisConnectionDetails connectionDetails, XAConnectionFactoryWrapper wrapper) throws Exception { + return wrapper + .wrapConnectionFactory(new ArtemisConnectionFactoryFactory(beanFactory, properties, connectionDetails) + .createConnectionFactory(ActiveMQXAConnectionFactory.class)); } @Bean - ActiveMQXAConnectionFactory nonXaJmsConnectionFactory(ListableBeanFactory beanFactory, - ArtemisProperties properties) { - return new ArtemisConnectionFactoryFactory(beanFactory, properties) + ActiveMQXAConnectionFactory nonXaJmsConnectionFactory(ListableBeanFactory beanFactory, ArtemisProperties properties, + ArtemisConnectionDetails connectionDetails) { + return new ArtemisConnectionFactoryFactory(beanFactory, properties, connectionDetails) .createConnectionFactory(ActiveMQXAConnectionFactory.class); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/DefaultExceptionTranslatorExecuteListener.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/DefaultExceptionTranslatorExecuteListener.java new file mode 100644 index 000000000000..199a62dda0e0 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/DefaultExceptionTranslatorExecuteListener.java @@ -0,0 +1,126 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jooq; + +import java.sql.SQLException; +import java.util.function.Function; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jooq.ExecuteContext; +import org.jooq.SQLDialect; + +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator; +import org.springframework.jdbc.support.SQLExceptionTranslator; +import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator; +import org.springframework.util.Assert; + +/** + * Default implementation of {@link ExceptionTranslatorExecuteListener} that delegates to + * an {@link SQLExceptionTranslator}. + * + * @author Lukas Eder + * @author Andreas Ahlenstorf + * @author Phillip Webb + * @author Stephane Nicoll + */ +final class DefaultExceptionTranslatorExecuteListener implements ExceptionTranslatorExecuteListener { + + // Based on the jOOQ-spring-example from https://github.com/jOOQ/jOOQ + + private static final Log defaultLogger = LogFactory.getLog(ExceptionTranslatorExecuteListener.class); + + private final Log logger; + + private Function translatorFactory; + + DefaultExceptionTranslatorExecuteListener() { + this(defaultLogger, new DefaultTranslatorFactory()); + } + + DefaultExceptionTranslatorExecuteListener(Function translatorFactory) { + this(defaultLogger, translatorFactory); + } + + DefaultExceptionTranslatorExecuteListener(Log logger) { + this(logger, new DefaultTranslatorFactory()); + } + + private DefaultExceptionTranslatorExecuteListener(Log logger, + Function translatorFactory) { + Assert.notNull(translatorFactory, "TranslatorFactory must not be null"); + this.logger = logger; + this.translatorFactory = translatorFactory; + } + + @Override + public void exception(ExecuteContext context) { + SQLExceptionTranslator translator = this.translatorFactory.apply(context); + // The exception() callback is not only triggered for SQL exceptions but also for + // "normal" exceptions. In those cases sqlException() returns null. + SQLException exception = context.sqlException(); + while (exception != null) { + handle(context, translator, exception); + exception = exception.getNextException(); + } + } + + /** + * Handle a single exception in the chain. SQLExceptions might be nested multiple + * levels deep. The outermost exception is usually the least interesting one ("Call + * getNextException to see the cause."). Therefore the innermost exception is + * propagated and all other exceptions are logged. + * @param context the execute context + * @param translator the exception translator + * @param exception the exception + */ + private void handle(ExecuteContext context, SQLExceptionTranslator translator, SQLException exception) { + DataAccessException translated = translator.translate("jOOQ", context.sql(), exception); + if (exception.getNextException() != null) { + this.logger.error("Execution of SQL statement failed.", (translated != null) ? translated : exception); + return; + } + if (translated != null) { + context.exception(translated); + } + } + + /** + * Default {@link SQLExceptionTranslator} factory that creates the translator based on + * the Spring DB name. + */ + private static final class DefaultTranslatorFactory implements Function { + + @Override + public SQLExceptionTranslator apply(ExecuteContext context) { + return apply(context.configuration().dialect()); + } + + private SQLExceptionTranslator apply(SQLDialect dialect) { + String dbName = getSpringDbName(dialect); + return (dbName != null) ? new SQLErrorCodeSQLExceptionTranslator(dbName) + : new SQLStateSQLExceptionTranslator(); + } + + private String getSpringDbName(SQLDialect dialect) { + return (dialect != null && dialect.thirdParty() != null) ? dialect.thirdParty().springDbName() : null; + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/ExceptionTranslatorExecuteListener.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/ExceptionTranslatorExecuteListener.java new file mode 100644 index 000000000000..eca548dfc334 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/ExceptionTranslatorExecuteListener.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jooq; + +import java.sql.SQLException; +import java.util.function.Function; + +import org.jooq.ExecuteContext; +import org.jooq.ExecuteListener; +import org.jooq.impl.DefaultExecuteListenerProvider; + +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.support.SQLExceptionTranslator; + +/** + * An {@link ExecuteListener} used by the auto-configured + * {@link DefaultExecuteListenerProvider} to translate exceptions in the + * {@link ExecuteContext}. Most commonly used to translate {@link SQLException + * SQLExceptions} to Spring-specific {@link DataAccessException DataAccessExceptions} by + * adapting an existing {@link SQLExceptionTranslator}. + * + * @author Dennis Melzer + * @since 3.3.0 + * @see #DEFAULT + * @see #of(Function) + */ +public interface ExceptionTranslatorExecuteListener extends ExecuteListener { + + /** + * Default {@link ExceptionTranslatorExecuteListener} suitable for most applications. + */ + ExceptionTranslatorExecuteListener DEFAULT = new DefaultExceptionTranslatorExecuteListener(); + + /** + * Creates a new {@link ExceptionTranslatorExecuteListener} backed by an + * {@link SQLExceptionTranslator}. + * @param translatorFactory factory function used to create the + * {@link SQLExceptionTranslator} + * @return a new {@link ExceptionTranslatorExecuteListener} instance + */ + static ExceptionTranslatorExecuteListener of(Function translatorFactory) { + return new DefaultExceptionTranslatorExecuteListener(translatorFactory); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfiguration.java index 72944ddf3f63..4580ed021ccd 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,8 +70,15 @@ public SpringTransactionProvider transactionProvider(PlatformTransactionManager @Bean @Order(0) - public DefaultExecuteListenerProvider jooqExceptionTranslatorExecuteListenerProvider() { - return new DefaultExecuteListenerProvider(new JooqExceptionTranslator()); + public DefaultExecuteListenerProvider jooqExceptionTranslatorExecuteListenerProvider( + ExceptionTranslatorExecuteListener exceptionTranslatorExecuteListener) { + return new DefaultExecuteListenerProvider(exceptionTranslatorExecuteListener); + } + + @Bean + @ConditionalOnMissingBean(ExceptionTranslatorExecuteListener.class) + public ExceptionTranslatorExecuteListener jooqExceptionTranslator() { + return ExceptionTranslatorExecuteListener.DEFAULT; } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqExceptionTranslator.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqExceptionTranslator.java index 383da48c975b..102bd59b0566 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqExceptionTranslator.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqExceptionTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,80 +18,33 @@ import java.sql.SQLException; -import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jooq.ExecuteContext; import org.jooq.ExecuteListener; -import org.jooq.SQLDialect; import org.springframework.dao.DataAccessException; -import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator; -import org.springframework.jdbc.support.SQLExceptionTranslator; -import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator; /** - * Transforms {@link java.sql.SQLException} into a Spring-specific - * {@link DataAccessException}. + * Transforms {@link SQLException} into a Spring-specific {@link DataAccessException}. * * @author Lukas Eder * @author Andreas Ahlenstorf * @author Phillip Webb * @author Stephane Nicoll * @since 1.5.10 + * @deprecated since 3.3.0 for removal in 3.5.0 in favor of + * {@link ExceptionTranslatorExecuteListener#DEFAULT} or + * {@link ExceptionTranslatorExecuteListener#of} */ +@Deprecated(since = "3.3.0", forRemoval = true) public class JooqExceptionTranslator implements ExecuteListener { - // Based on the jOOQ-spring-example from https://github.com/jOOQ/jOOQ - - private static final Log logger = LogFactory.getLog(JooqExceptionTranslator.class); + private final DefaultExceptionTranslatorExecuteListener delegate = new DefaultExceptionTranslatorExecuteListener( + LogFactory.getLog(JooqExceptionTranslator.class)); @Override public void exception(ExecuteContext context) { - SQLExceptionTranslator translator = getTranslator(context); - // The exception() callback is not only triggered for SQL exceptions but also for - // "normal" exceptions. In those cases sqlException() returns null. - SQLException exception = context.sqlException(); - while (exception != null) { - handle(context, translator, exception); - exception = exception.getNextException(); - } - } - - private SQLExceptionTranslator getTranslator(ExecuteContext context) { - SQLDialect dialect = context.configuration().dialect(); - if (dialect != null && dialect.thirdParty() != null) { - String dbName = dialect.thirdParty().springDbName(); - if (dbName != null) { - return new SQLErrorCodeSQLExceptionTranslator(dbName); - } - } - return new SQLStateSQLExceptionTranslator(); - } - - /** - * Handle a single exception in the chain. SQLExceptions might be nested multiple - * levels deep. The outermost exception is usually the least interesting one ("Call - * getNextException to see the cause."). Therefore the innermost exception is - * propagated and all other exceptions are logged. - * @param context the execute context - * @param translator the exception translator - * @param exception the exception - */ - private void handle(ExecuteContext context, SQLExceptionTranslator translator, SQLException exception) { - DataAccessException translated = translate(context, translator, exception); - if (exception.getNextException() == null) { - if (translated != null) { - context.exception(translated); - } - } - else { - logger.error("Execution of SQL statement failed.", (translated != null) ? translated : exception); - } - } - - private DataAccessException translate(ExecuteContext context, SQLExceptionTranslator translator, - SQLException exception) { - return translator.translate("jOOQ", context.sql(), exception); + this.delegate.exception(context); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SqlDialectLookup.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SqlDialectLookup.java index b91b72b68258..1e7504195d56 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SqlDialectLookup.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SqlDialectLookup.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,8 @@ package org.springframework.boot.autoconfigure.jooq; -import java.sql.DatabaseMetaData; +import java.sql.Connection; +import java.sql.SQLException; import javax.sql.DataSource; @@ -25,14 +26,12 @@ import org.jooq.SQLDialect; import org.jooq.tools.jdbc.JDBCUtils; -import org.springframework.jdbc.support.JdbcUtils; -import org.springframework.jdbc.support.MetaDataAccessException; - /** * Utility to lookup well known {@link SQLDialect SQLDialects} from a {@link DataSource}. * * @author Michael Simons * @author Lukas Eder + * @author Ramil Saetov */ final class SqlDialectLookup { @@ -47,18 +46,11 @@ private SqlDialectLookup() { * @return the most suitable {@link SQLDialect} */ static SQLDialect getDialect(DataSource dataSource) { - if (dataSource == null) { - return SQLDialect.DEFAULT; - } - try { - String url = JdbcUtils.extractDatabaseMetaData(dataSource, DatabaseMetaData::getURL); - SQLDialect sqlDialect = JDBCUtils.dialect(url); - if (sqlDialect != null) { - return sqlDialect; - } + try (Connection connection = (dataSource != null) ? dataSource.getConnection() : null) { + return JDBCUtils.dialect(connection); } - catch (MetaDataAccessException ex) { - logger.warn("Unable to determine jdbc url from datasource", ex); + catch (SQLException ex) { + logger.warn("Unable to determine dialect from datasource", ex); } return SQLDialect.DEFAULT; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/ConcurrentKafkaListenerContainerFactoryConfigurer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/ConcurrentKafkaListenerContainerFactoryConfigurer.java index 8ea525a151b5..d4c4ebf55d34 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/ConcurrentKafkaListenerContainerFactoryConfigurer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/ConcurrentKafkaListenerContainerFactoryConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,11 @@ package org.springframework.boot.autoconfigure.kafka; import java.time.Duration; +import java.util.function.Function; import org.springframework.boot.autoconfigure.kafka.KafkaProperties.Listener; import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; import org.springframework.kafka.core.ConsumerFactory; import org.springframework.kafka.core.KafkaTemplate; @@ -28,6 +30,7 @@ import org.springframework.kafka.listener.CommonErrorHandler; import org.springframework.kafka.listener.ConsumerAwareRebalanceListener; import org.springframework.kafka.listener.ContainerProperties; +import org.springframework.kafka.listener.MessageListenerContainer; import org.springframework.kafka.listener.RecordInterceptor; import org.springframework.kafka.listener.adapter.RecordFilterStrategy; import org.springframework.kafka.support.converter.BatchMessageConverter; @@ -40,6 +43,7 @@ * @author Gary Russell * @author Eddú Meléndez * @author Thomas Kåsene + * @author Moritz Halbritter * @since 1.5.0 */ public class ConcurrentKafkaListenerContainerFactoryConfigurer { @@ -66,6 +70,10 @@ public class ConcurrentKafkaListenerContainerFactoryConfigurer { private BatchInterceptor batchInterceptor; + private Function threadNameSupplier; + + private SimpleAsyncTaskExecutor listenerTaskExecutor; + /** * Set the {@link KafkaProperties} to use. * @param properties the properties @@ -156,6 +164,22 @@ void setBatchInterceptor(BatchInterceptor batchInterceptor) { this.batchInterceptor = batchInterceptor; } + /** + * Set the thread name supplier to use. + * @param threadNameSupplier the thread name supplier to use + */ + void setThreadNameSupplier(Function threadNameSupplier) { + this.threadNameSupplier = threadNameSupplier; + } + + /** + * Set the executor for threads that poll the consumer. + * @param listenerTaskExecutor task executor + */ + void setListenerTaskExecutor(SimpleAsyncTaskExecutor listenerTaskExecutor) { + this.listenerTaskExecutor = listenerTaskExecutor; + } + /** * Configure the specified Kafka listener container factory. The factory can be * further tuned and default settings can be overridden. @@ -186,6 +210,8 @@ private void configureListenerFactory(ConcurrentKafkaListenerContainerFactory batchInterceptor; + private final Function threadNameSupplier; + KafkaAnnotationDrivenConfiguration(KafkaProperties properties, ObjectProvider recordMessageConverter, ObjectProvider> recordFilterStrategy, @@ -83,7 +95,8 @@ class KafkaAnnotationDrivenConfiguration { ObjectProvider commonErrorHandler, ObjectProvider> afterRollbackProcessor, ObjectProvider> recordInterceptor, - ObjectProvider> batchInterceptor) { + ObjectProvider> batchInterceptor, + ObjectProvider> threadNameSupplier) { this.properties = properties; this.recordMessageConverter = recordMessageConverter.getIfUnique(); this.recordFilterStrategy = recordFilterStrategy.getIfUnique(); @@ -96,11 +109,28 @@ class KafkaAnnotationDrivenConfiguration { this.afterRollbackProcessor = afterRollbackProcessor.getIfUnique(); this.recordInterceptor = recordInterceptor.getIfUnique(); this.batchInterceptor = batchInterceptor.getIfUnique(); + this.threadNameSupplier = threadNameSupplier.getIfUnique(); } @Bean @ConditionalOnMissingBean + @ConditionalOnThreading(Threading.PLATFORM) ConcurrentKafkaListenerContainerFactoryConfigurer kafkaListenerContainerFactoryConfigurer() { + return configurer(); + } + + @Bean(name = "kafkaListenerContainerFactoryConfigurer") + @ConditionalOnMissingBean + @ConditionalOnThreading(Threading.VIRTUAL) + ConcurrentKafkaListenerContainerFactoryConfigurer kafkaListenerContainerFactoryConfigurerVirtualThreads() { + ConcurrentKafkaListenerContainerFactoryConfigurer configurer = configurer(); + SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("kafka-"); + executor.setVirtualThreads(true); + configurer.setListenerTaskExecutor(executor); + return configurer; + } + + private ConcurrentKafkaListenerContainerFactoryConfigurer configurer() { ConcurrentKafkaListenerContainerFactoryConfigurer configurer = new ConcurrentKafkaListenerContainerFactoryConfigurer(); configurer.setKafkaProperties(this.properties); configurer.setBatchMessageConverter(this.batchMessageConverter); @@ -113,6 +143,7 @@ ConcurrentKafkaListenerContainerFactoryConfigurer kafkaListenerContainerFactoryC configurer.setAfterRollbackProcessor(this.afterRollbackProcessor); configurer.setRecordInterceptor(this.recordInterceptor); configurer.setBatchInterceptor(this.batchInterceptor); + configurer.setThreadNameSupplier(this.threadNameSupplier); return configurer; } @@ -121,10 +152,11 @@ ConcurrentKafkaListenerContainerFactoryConfigurer kafkaListenerContainerFactoryC ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory( ConcurrentKafkaListenerContainerFactoryConfigurer configurer, ObjectProvider> kafkaConsumerFactory, - ObjectProvider>> kafkaContainerCustomizer) { + ObjectProvider>> kafkaContainerCustomizer, + ObjectProvider sslBundles) { ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); - configurer.configure(factory, kafkaConsumerFactory - .getIfAvailable(() -> new DefaultKafkaConsumerFactory<>(this.properties.buildConsumerProperties()))); + configurer.configure(factory, kafkaConsumerFactory.getIfAvailable(() -> new DefaultKafkaConsumerFactory<>( + this.properties.buildConsumerProperties(sslBundles.getIfAvailable())))); kafkaContainerCustomizer.ifAvailable(factory::setContainerCustomizer); return factory; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaAutoConfiguration.java index 9d73f58cd56c..d2944a6a6942 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,9 +32,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.kafka.KafkaProperties.Jaas; -import org.springframework.boot.autoconfigure.kafka.KafkaProperties.Retry.Topic; +import org.springframework.boot.autoconfigure.kafka.KafkaProperties.Retry.Topic.Backoff; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.ssl.SslBundles; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.kafka.core.ConsumerFactory; @@ -64,6 +65,8 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Andy Wilkinson + * @author Scott Frederick * @since 1.5.0 */ @AutoConfiguration @@ -95,6 +98,7 @@ PropertiesKafkaConnectionDetails kafkaConnectionDetails(KafkaProperties properti map.from(kafkaProducerListener).to(kafkaTemplate::setProducerListener); map.from(this.properties.getTemplate().getDefaultTopic()).to(kafkaTemplate::setDefaultTopic); map.from(this.properties.getTemplate().getTransactionIdPrefix()).to(kafkaTemplate::setTransactionIdPrefix); + map.from(this.properties.getTemplate().isObservationEnabled()).to(kafkaTemplate::setObservationEnabled); return kafkaTemplate; } @@ -107,8 +111,8 @@ public LoggingProducerListener kafkaProducerListener() { @Bean @ConditionalOnMissingBean(ConsumerFactory.class) public DefaultKafkaConsumerFactory kafkaConsumerFactory(KafkaConnectionDetails connectionDetails, - ObjectProvider customizers) { - Map properties = this.properties.buildConsumerProperties(); + ObjectProvider customizers, ObjectProvider sslBundles) { + Map properties = this.properties.buildConsumerProperties(sslBundles.getIfAvailable()); applyKafkaConnectionDetailsForConsumer(properties, connectionDetails); DefaultKafkaConsumerFactory factory = new DefaultKafkaConsumerFactory<>(properties); customizers.orderedStream().forEach((customizer) -> customizer.customize(factory)); @@ -118,8 +122,8 @@ public LoggingProducerListener kafkaProducerListener() { @Bean @ConditionalOnMissingBean(ProducerFactory.class) public DefaultKafkaProducerFactory kafkaProducerFactory(KafkaConnectionDetails connectionDetails, - ObjectProvider customizers) { - Map properties = this.properties.buildProducerProperties(); + ObjectProvider customizers, ObjectProvider sslBundles) { + Map properties = this.properties.buildProducerProperties(sslBundles.getIfAvailable()); applyKafkaConnectionDetailsForProducer(properties, connectionDetails); DefaultKafkaProducerFactory factory = new DefaultKafkaProducerFactory<>(properties); String transactionIdPrefix = this.properties.getProducer().getTransactionIdPrefix(); @@ -155,8 +159,8 @@ public KafkaJaasLoginModuleInitializer kafkaJaasInitializer() throws IOException @Bean @ConditionalOnMissingBean - public KafkaAdmin kafkaAdmin(KafkaConnectionDetails connectionDetails) { - Map properties = this.properties.buildAdminProperties(); + public KafkaAdmin kafkaAdmin(KafkaConnectionDetails connectionDetails, ObjectProvider sslBundles) { + Map properties = this.properties.buildAdminProperties(sslBundles.getIfAvailable()); applyKafkaConnectionDetailsForAdmin(properties, connectionDetails); KafkaAdmin kafkaAdmin = new KafkaAdmin(properties); KafkaProperties.Admin admin = this.properties.getAdmin(); @@ -182,7 +186,7 @@ public RetryTopicConfiguration kafkaRetryTopicConfiguration(KafkaTemplate .useSingleTopicForSameIntervals() .suffixTopicsWithIndexValues() .doNotAutoCreateRetryTopics(); - setBackOffPolicy(builder, retryTopic); + setBackOffPolicy(builder, retryTopic.getBackoff()); return builder.create(kafkaTemplate); } @@ -210,15 +214,15 @@ private void applyKafkaConnectionDetailsForAdmin(Map properties, } } - private static void setBackOffPolicy(RetryTopicConfigurationBuilder builder, Topic retryTopic) { - long delay = (retryTopic.getDelay() != null) ? retryTopic.getDelay().toMillis() : 0; + private static void setBackOffPolicy(RetryTopicConfigurationBuilder builder, Backoff retryTopicBackoff) { + long delay = (retryTopicBackoff.getDelay() != null) ? retryTopicBackoff.getDelay().toMillis() : 0; if (delay > 0) { PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); BackOffPolicyBuilder backOffPolicy = BackOffPolicyBuilder.newBuilder(); map.from(delay).to(backOffPolicy::delay); - map.from(retryTopic.getMaxDelay()).as(Duration::toMillis).to(backOffPolicy::maxDelay); - map.from(retryTopic.getMultiplier()).to(backOffPolicy::multiplier); - map.from(retryTopic.isRandomBackOff()).to(backOffPolicy::random); + map.from(retryTopicBackoff.getMaxDelay()).as(Duration::toMillis).to(backOffPolicy::maxDelay); + map.from(retryTopicBackoff.getMultiplier()).to(backOffPolicy::multiplier); + map.from(retryTopicBackoff.isRandom()).to(backOffPolicy::random); builder.customBackoff((SleepingBackOffPolicy) backOffPolicy.build()); } else { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaProperties.java index cb6869b98666..2a849fcda83c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaProperties.java @@ -38,6 +38,8 @@ import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException; import org.springframework.boot.convert.DurationUnit; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; import org.springframework.core.io.Resource; import org.springframework.kafka.listener.ContainerProperties.AckMode; import org.springframework.kafka.security.jaas.KafkaJaasLoginModuleInitializer; @@ -55,6 +57,8 @@ * @author Artem Bilan * @author Nakul Mishra * @author Tomaz Fernandes + * @author Andy Wilkinson + * @author Scott Frederick * @since 1.5.0 */ @ConfigurationProperties(prefix = "spring.kafka") @@ -157,7 +161,7 @@ public Retry getRetry() { return this.retry; } - private Map buildCommonProperties() { + private Map buildCommonProperties(SslBundles sslBundles) { Map properties = new HashMap<>(); if (this.bootstrapServers != null) { properties.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, this.bootstrapServers); @@ -165,7 +169,7 @@ private Map buildCommonProperties() { if (this.clientId != null) { properties.put(CommonClientConfigs.CLIENT_ID_CONFIG, this.clientId); } - properties.putAll(this.ssl.buildProperties()); + properties.putAll(this.ssl.buildProperties(sslBundles)); properties.putAll(this.security.buildProperties()); if (!CollectionUtils.isEmpty(this.properties)) { properties.putAll(this.properties); @@ -177,13 +181,14 @@ private Map buildCommonProperties() { * Create an initial map of consumer properties from the state of this instance. *

* This allows you to add additional properties, if necessary, and override the - * default kafkaConsumerFactory bean. + * default {@code kafkaConsumerFactory} bean. + * @param sslBundles bundles providing SSL trust material * @return the consumer properties initialized with the customizations defined on this * instance */ - public Map buildConsumerProperties() { - Map properties = buildCommonProperties(); - properties.putAll(this.consumer.buildProperties()); + public Map buildConsumerProperties(SslBundles sslBundles) { + Map properties = buildCommonProperties(sslBundles); + properties.putAll(this.consumer.buildProperties(sslBundles)); return properties; } @@ -191,13 +196,14 @@ public Map buildConsumerProperties() { * Create an initial map of producer properties from the state of this instance. *

* This allows you to add additional properties, if necessary, and override the - * default kafkaProducerFactory bean. + * default {@code kafkaProducerFactory} bean. + * @param sslBundles bundles providing SSL trust material * @return the producer properties initialized with the customizations defined on this * instance */ - public Map buildProducerProperties() { - Map properties = buildCommonProperties(); - properties.putAll(this.producer.buildProperties()); + public Map buildProducerProperties(SslBundles sslBundles) { + Map properties = buildCommonProperties(sslBundles); + properties.putAll(this.producer.buildProperties(sslBundles)); return properties; } @@ -205,13 +211,14 @@ public Map buildProducerProperties() { * Create an initial map of admin properties from the state of this instance. *

* This allows you to add additional properties, if necessary, and override the - * default kafkaAdmin bean. + * default {@code kafkaAdmin} bean. + * @param sslBundles bundles providing SSL trust material * @return the admin properties initialized with the customizations defined on this * instance */ - public Map buildAdminProperties() { - Map properties = buildCommonProperties(); - properties.putAll(this.admin.buildProperties()); + public Map buildAdminProperties(SslBundles sslBundles) { + Map properties = buildCommonProperties(sslBundles); + properties.putAll(this.admin.buildProperties(sslBundles)); return properties; } @@ -219,12 +226,13 @@ public Map buildAdminProperties() { * Create an initial map of streams properties from the state of this instance. *

* This allows you to add additional properties, if necessary. + * @param sslBundles bundles providing SSL trust material * @return the streams properties initialized with the customizations defined on this * instance */ - public Map buildStreamsProperties() { - Map properties = buildCommonProperties(); - properties.putAll(this.streams.buildProperties()); + public Map buildStreamsProperties(SslBundles sslBundles) { + Map properties = buildCommonProperties(sslBundles); + properties.putAll(this.streams.buildProperties(sslBundles)); return properties; } @@ -426,7 +434,7 @@ public Map getProperties() { return this.properties; } - public Map buildProperties() { + public Map buildProperties(SslBundles sslBundles) { Properties properties = new Properties(); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(this::getAutoCommitInterval) @@ -451,7 +459,7 @@ public Map buildProperties() { map.from(this::getKeyDeserializer).to(properties.in(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG)); map.from(this::getValueDeserializer).to(properties.in(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG)); map.from(this::getMaxPollRecords).to(properties.in(ConsumerConfig.MAX_POLL_RECORDS_CONFIG)); - return properties.with(this.ssl, this.security, this.properties); + return properties.with(this.ssl, this.security, this.properties, sslBundles); } } @@ -613,7 +621,7 @@ public Map getProperties() { return this.properties; } - public Map buildProperties() { + public Map buildProperties(SslBundles sslBundles) { Properties properties = new Properties(); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(this::getAcks).to(properties.in(ProducerConfig.ACKS_CONFIG)); @@ -627,7 +635,7 @@ public Map buildProperties() { map.from(this::getKeySerializer).to(properties.in(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG)); map.from(this::getRetries).to(properties.in(ProducerConfig.RETRIES_CONFIG)); map.from(this::getValueSerializer).to(properties.in(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG)); - return properties.with(this.ssl, this.security, this.properties); + return properties.with(this.ssl, this.security, this.properties, sslBundles); } } @@ -734,11 +742,11 @@ public Map getProperties() { return this.properties; } - public Map buildProperties() { + public Map buildProperties(SslBundles sslBundles) { Properties properties = new Properties(); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(this::getClientId).to(properties.in(ProducerConfig.CLIENT_ID_CONFIG)); - return properties.with(this.ssl, this.security, this.properties); + return properties.with(this.ssl, this.security, this.properties, sslBundles); } } @@ -770,11 +778,6 @@ public static class Streams { */ private List bootstrapServers; - /** - * Maximum memory size to be used for buffering across all threads. - */ - private DataSize cacheMaxSizeBuffering; - /** * Maximum size of the in-memory state store cache across all threads. */ @@ -837,17 +840,6 @@ public void setBootstrapServers(List bootstrapServers) { this.bootstrapServers = bootstrapServers; } - @DeprecatedConfigurationProperty(replacement = "spring.kafka.streams.state-store-cache-max-size") - @Deprecated(since = "3.1.0", forRemoval = true) - public DataSize getCacheMaxSizeBuffering() { - return this.cacheMaxSizeBuffering; - } - - @Deprecated(since = "3.1.0", forRemoval = true) - public void setCacheMaxSizeBuffering(DataSize cacheMaxSizeBuffering) { - this.cacheMaxSizeBuffering = cacheMaxSizeBuffering; - } - public DataSize getStateStoreCacheMaxSize() { return this.stateStoreCacheMaxSize; } @@ -884,21 +876,18 @@ public Map getProperties() { return this.properties; } - public Map buildProperties() { + public Map buildProperties(SslBundles sslBundles) { Properties properties = new Properties(); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(this::getApplicationId).to(properties.in("application.id")); map.from(this::getBootstrapServers).to(properties.in(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG)); - map.from(this::getCacheMaxSizeBuffering) - .asInt(DataSize::toBytes) - .to(properties.in("cache.max.bytes.buffering")); map.from(this::getStateStoreCacheMaxSize) .asInt(DataSize::toBytes) .to(properties.in("statestore.cache.max.bytes")); map.from(this::getClientId).to(properties.in(CommonClientConfigs.CLIENT_ID_CONFIG)); map.from(this::getReplicationFactor).to(properties.in("replication.factor")); map.from(this::getStateDir).to(properties.in("state.dir")); - return properties.with(this.ssl, this.security, this.properties); + return properties.with(this.ssl, this.security, this.properties, sslBundles); } } @@ -916,6 +905,11 @@ public static class Template { */ private String transactionIdPrefix; + /** + * Whether to enable observation. + */ + private boolean observationEnabled; + public String getDefaultTopic() { return this.defaultTopic; } @@ -932,6 +926,14 @@ public void setTransactionIdPrefix(String transactionIdPrefix) { this.transactionIdPrefix = transactionIdPrefix; } + public boolean isObservationEnabled() { + return this.observationEnabled; + } + + public void setObservationEnabled(boolean observationEnabled) { + this.observationEnabled = observationEnabled; + } + } public static class Listener { @@ -1043,6 +1045,17 @@ public enum Type { */ private boolean autoStartup = true; + /** + * Whether to instruct the container to change the consumer thread name during + * initialization. + */ + private Boolean changeConsumerThreadName; + + /** + * Whether to enable observation. + */ + private boolean observationEnabled; + public Type getType() { return this.type; } @@ -1179,10 +1192,31 @@ public void setAutoStartup(boolean autoStartup) { this.autoStartup = autoStartup; } + public Boolean getChangeConsumerThreadName() { + return this.changeConsumerThreadName; + } + + public void setChangeConsumerThreadName(Boolean changeConsumerThreadName) { + this.changeConsumerThreadName = changeConsumerThreadName; + } + + public boolean isObservationEnabled() { + return this.observationEnabled; + } + + public void setObservationEnabled(boolean observationEnabled) { + this.observationEnabled = observationEnabled; + } + } public static class Ssl { + /** + * Name of the SSL bundle to use. + */ + private String bundle; + /** * Password of the private key in either key store key or key store file. */ @@ -1238,6 +1272,14 @@ public static class Ssl { */ private String protocol; + public String getBundle() { + return this.bundle; + } + + public void setBundle(String bundle) { + this.bundle = bundle; + } + public String getKeyPassword() { return this.keyPassword; } @@ -1326,26 +1368,39 @@ public void setProtocol(String protocol) { this.protocol = protocol; } + @Deprecated(since = "3.2.0", forRemoval = true) public Map buildProperties() { + return buildProperties(null); + } + + public Map buildProperties(SslBundles sslBundles) { validate(); Properties properties = new Properties(); - PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); - map.from(this::getKeyPassword).to(properties.in(SslConfigs.SSL_KEY_PASSWORD_CONFIG)); - map.from(this::getKeyStoreCertificateChain) - .to(properties.in(SslConfigs.SSL_KEYSTORE_CERTIFICATE_CHAIN_CONFIG)); - map.from(this::getKeyStoreKey).to(properties.in(SslConfigs.SSL_KEYSTORE_KEY_CONFIG)); - map.from(this::getKeyStoreLocation) - .as(this::resourceToPath) - .to(properties.in(SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG)); - map.from(this::getKeyStorePassword).to(properties.in(SslConfigs.SSL_KEYSTORE_PASSWORD_CONFIG)); - map.from(this::getKeyStoreType).to(properties.in(SslConfigs.SSL_KEYSTORE_TYPE_CONFIG)); - map.from(this::getTrustStoreCertificates).to(properties.in(SslConfigs.SSL_TRUSTSTORE_CERTIFICATES_CONFIG)); - map.from(this::getTrustStoreLocation) - .as(this::resourceToPath) - .to(properties.in(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG)); - map.from(this::getTrustStorePassword).to(properties.in(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG)); - map.from(this::getTrustStoreType).to(properties.in(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG)); - map.from(this::getProtocol).to(properties.in(SslConfigs.SSL_PROTOCOL_CONFIG)); + if (getBundle() != null) { + properties.in(SslConfigs.SSL_ENGINE_FACTORY_CLASS_CONFIG) + .accept(SslBundleSslEngineFactory.class.getName()); + properties.in(SslBundle.class.getName()).accept(sslBundles.getBundle(getBundle())); + } + else { + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(this::getKeyPassword).to(properties.in(SslConfigs.SSL_KEY_PASSWORD_CONFIG)); + map.from(this::getKeyStoreCertificateChain) + .to(properties.in(SslConfigs.SSL_KEYSTORE_CERTIFICATE_CHAIN_CONFIG)); + map.from(this::getKeyStoreKey).to(properties.in(SslConfigs.SSL_KEYSTORE_KEY_CONFIG)); + map.from(this::getKeyStoreLocation) + .as(this::resourceToPath) + .to(properties.in(SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG)); + map.from(this::getKeyStorePassword).to(properties.in(SslConfigs.SSL_KEYSTORE_PASSWORD_CONFIG)); + map.from(this::getKeyStoreType).to(properties.in(SslConfigs.SSL_KEYSTORE_TYPE_CONFIG)); + map.from(this::getTrustStoreCertificates) + .to(properties.in(SslConfigs.SSL_TRUSTSTORE_CERTIFICATES_CONFIG)); + map.from(this::getTrustStoreLocation) + .as(this::resourceToPath) + .to(properties.in(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG)); + map.from(this::getTrustStorePassword).to(properties.in(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG)); + map.from(this::getTrustStoreType).to(properties.in(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG)); + map.from(this::getProtocol).to(properties.in(SslConfigs.SSL_PROTOCOL_CONFIG)); + } return properties; } @@ -1358,6 +1413,22 @@ private void validate() { entries.put("spring.kafka.ssl.trust-store-certificates", getTrustStoreCertificates()); entries.put("spring.kafka.ssl.trust-store-location", getTrustStoreLocation()); }); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + entries.put("spring.kafka.ssl.bundle", getBundle()); + entries.put("spring.kafka.ssl.key-store-key", getKeyStoreKey()); + }); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + entries.put("spring.kafka.ssl.bundle", getBundle()); + entries.put("spring.kafka.ssl.key-store-location", getKeyStoreLocation()); + }); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + entries.put("spring.kafka.ssl.bundle", getBundle()); + entries.put("spring.kafka.ssl.trust-store-certificates", getTrustStoreCertificates()); + }); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + entries.put("spring.kafka.ssl.bundle", getBundle()); + entries.put("spring.kafka.ssl.trust-store-location", getTrustStoreLocation()); + }); } private String resourceToPath(Resource resource) { @@ -1477,28 +1548,6 @@ public static class Topic { */ private int attempts = 3; - /** - * Canonical backoff period. Used as an initial value in the exponential case, - * and as a minimum value in the uniform case. - */ - private Duration delay = Duration.ofSeconds(1); - - /** - * Multiplier to use for generating the next backoff delay. - */ - private double multiplier = 0.0; - - /** - * Maximum wait between retries. If less than the delay then the default of 30 - * seconds is applied. - */ - private Duration maxDelay = Duration.ZERO; - - /** - * Whether to have the backoff delays. - */ - private boolean randomBackOff = false; - public boolean isEnabled() { return this.enabled; } @@ -1515,36 +1564,113 @@ public void setAttempts(int attempts) { this.attempts = attempts; } + @DeprecatedConfigurationProperty(replacement = "spring.kafka.retry.topic.backoff.delay", since = "3.4.0") + @Deprecated(since = "3.4.0", forRemoval = true) public Duration getDelay() { - return this.delay; + return getBackoff().getDelay(); } + @Deprecated(since = "3.4.0", forRemoval = true) public void setDelay(Duration delay) { - this.delay = delay; + getBackoff().setDelay(delay); } + @DeprecatedConfigurationProperty(replacement = "spring.kafka.retry.topic.backoff.multiplier", + since = "3.4.0") + @Deprecated(since = "3.4.0", forRemoval = true) public double getMultiplier() { - return this.multiplier; + return getBackoff().getMultiplier(); } + @Deprecated(since = "3.4.0", forRemoval = true) public void setMultiplier(double multiplier) { - this.multiplier = multiplier; + getBackoff().setMultiplier(multiplier); } + @DeprecatedConfigurationProperty(replacement = "spring.kafka.retry.topic.backoff.maxDelay", since = "3.4.0") + @Deprecated(since = "3.4.0", forRemoval = true) public Duration getMaxDelay() { - return this.maxDelay; + return getBackoff().getMaxDelay(); } + @Deprecated(since = "3.4.0", forRemoval = true) public void setMaxDelay(Duration maxDelay) { - this.maxDelay = maxDelay; + getBackoff().setMaxDelay(maxDelay); } + @DeprecatedConfigurationProperty(replacement = "spring.kafka.retry.topic.backoff.random", since = "3.4.0") + @Deprecated(since = "3.4.0", forRemoval = true) public boolean isRandomBackOff() { - return this.randomBackOff; + return getBackoff().isRandom(); } + @Deprecated(since = "3.4.0", forRemoval = true) public void setRandomBackOff(boolean randomBackOff) { - this.randomBackOff = randomBackOff; + getBackoff().setRandom(randomBackOff); + } + + private final Backoff backoff = new Backoff(); + + public Backoff getBackoff() { + return this.backoff; + } + + public static class Backoff { + + /** + * Canonical backoff period. Used as an initial value in the exponential + * case, and as a minimum value in the uniform case. + */ + private Duration delay = Duration.ofSeconds(1); + + /** + * Multiplier to use for generating the next backoff delay. + */ + private double multiplier = 0.0; + + /** + * Maximum wait between retries. If less than the delay then the default + * of 30 seconds is applied. + */ + private Duration maxDelay = Duration.ZERO; + + /** + * Whether to have the backoff delays. + */ + private boolean random = false; + + public Duration getDelay() { + return this.delay; + } + + public void setDelay(Duration delay) { + this.delay = delay; + } + + public double getMultiplier() { + return this.multiplier; + } + + public void setMultiplier(double multiplier) { + this.multiplier = multiplier; + } + + public Duration getMaxDelay() { + return this.maxDelay; + } + + public void setMaxDelay(Duration maxDelay) { + this.maxDelay = maxDelay; + } + + public boolean isRandom() { + return this.random; + } + + public void setRandom(boolean random) { + this.random = random; + } + } } @@ -1613,8 +1739,8 @@ java.util.function.Consumer in(String key) { return (value) -> put(key, value); } - Properties with(Ssl ssl, Security security, Map properties) { - putAll(ssl.buildProperties()); + Properties with(Ssl ssl, Security security, Map properties, SslBundles sslBundles) { + putAll(ssl.buildProperties(sslBundles)); putAll(security.buildProperties()); putAll(properties); return this; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaStreamsAnnotationDrivenConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaStreamsAnnotationDrivenConfiguration.java index 28e35d3699f4..701384326303 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaStreamsAnnotationDrivenConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaStreamsAnnotationDrivenConfiguration.java @@ -30,6 +30,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; +import org.springframework.boot.ssl.SslBundles; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; @@ -46,6 +47,7 @@ * @author Eddú Meléndez * @author Moritz Halbritter * @author Andy Wilkinson + * @author Scott Frederick */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(StreamsBuilder.class) @@ -61,8 +63,8 @@ class KafkaStreamsAnnotationDrivenConfiguration { @ConditionalOnMissingBean @Bean(KafkaStreamsDefaultConfiguration.DEFAULT_STREAMS_CONFIG_BEAN_NAME) KafkaStreamsConfiguration defaultKafkaStreamsConfig(Environment environment, - KafkaConnectionDetails connectionDetails) { - Map properties = this.properties.buildStreamsProperties(); + KafkaConnectionDetails connectionDetails, ObjectProvider sslBundles) { + Map properties = this.properties.buildStreamsProperties(sslBundles.getIfAvailable()); applyKafkaConnectionDetailsForStreams(properties, connectionDetails); if (this.properties.getStreams().getApplicationId() == null) { String applicationName = environment.getProperty("spring.application.name"); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/SslBundleSslEngineFactory.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/SslBundleSslEngineFactory.java new file mode 100644 index 000000000000..5c5e93ebf56a --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/SslBundleSslEngineFactory.java @@ -0,0 +1,95 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.kafka; + +import java.io.IOException; +import java.security.KeyStore; +import java.util.Map; +import java.util.Set; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; + +import org.apache.kafka.common.security.auth.SslEngineFactory; + +import org.springframework.boot.ssl.SslBundle; + +/** + * An {@link SslEngineFactory} that configures creates an {@link SSLEngine} from an + * {@link SslBundle}. + * + * @author Andy Wilkinson + * @author Scott Frederick + * @since 3.2.0 + */ +public class SslBundleSslEngineFactory implements SslEngineFactory { + + private static final String SSL_BUNDLE_CONFIG_NAME = SslBundle.class.getName(); + + private Map configs; + + private volatile SslBundle sslBundle; + + @Override + public void configure(Map configs) { + this.configs = configs; + this.sslBundle = (SslBundle) configs.get(SSL_BUNDLE_CONFIG_NAME); + } + + @Override + public void close() throws IOException { + + } + + @Override + public SSLEngine createClientSslEngine(String peerHost, int peerPort, String endpointIdentification) { + SSLEngine sslEngine = this.sslBundle.createSslContext().createSSLEngine(peerHost, peerPort); + sslEngine.setUseClientMode(true); + SSLParameters sslParams = sslEngine.getSSLParameters(); + sslParams.setEndpointIdentificationAlgorithm(endpointIdentification); + sslEngine.setSSLParameters(sslParams); + return sslEngine; + } + + @Override + public SSLEngine createServerSslEngine(String peerHost, int peerPort) { + SSLEngine sslEngine = this.sslBundle.createSslContext().createSSLEngine(peerHost, peerPort); + sslEngine.setUseClientMode(false); + return sslEngine; + } + + @Override + public boolean shouldBeRebuilt(Map nextConfigs) { + return !nextConfigs.equals(this.configs); + } + + @Override + public Set reconfigurableConfigs() { + return Set.of(SSL_BUNDLE_CONFIG_NAME); + } + + @Override + public KeyStore keystore() { + return this.sslBundle.getStores().getKeyStore(); + } + + @Override + public KeyStore truststore() { + return this.sslBundle.getStores().getTrustStore(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ldap/LdapAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ldap/LdapAutoConfiguration.java index 0144f9bf99ae..205af4c3e0ac 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ldap/LdapAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ldap/LdapAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,18 +46,25 @@ @EnableConfigurationProperties(LdapProperties.class) public class LdapAutoConfiguration { + @Bean + @ConditionalOnMissingBean(LdapConnectionDetails.class) + PropertiesLdapConnectionDetails propertiesLdapConnectionDetails(LdapProperties properties, + Environment environment) { + return new PropertiesLdapConnectionDetails(properties, environment); + } + @Bean @ConditionalOnMissingBean - public LdapContextSource ldapContextSource(LdapProperties properties, Environment environment, + public LdapContextSource ldapContextSource(LdapConnectionDetails connectionDetails, LdapProperties properties, ObjectProvider dirContextAuthenticationStrategy) { LdapContextSource source = new LdapContextSource(); dirContextAuthenticationStrategy.ifUnique(source::setAuthenticationStrategy); PropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenNonNull(); - propertyMapper.from(properties.getUsername()).to(source::setUserDn); - propertyMapper.from(properties.getPassword()).to(source::setPassword); + propertyMapper.from(connectionDetails.getUsername()).to(source::setUserDn); + propertyMapper.from(connectionDetails.getPassword()).to(source::setPassword); propertyMapper.from(properties.getAnonymousReadOnly()).to(source::setAnonymousReadOnly); - propertyMapper.from(properties.getBase()).to(source::setBase); - propertyMapper.from(properties.determineUrls(environment)).to(source::setUrls); + propertyMapper.from(connectionDetails.getBase()).to(source::setBase); + propertyMapper.from(connectionDetails.getUrls()).to(source::setUrls); propertyMapper.from(properties.getBaseEnvironment()) .to((baseEnvironment) -> source.setBaseEnvironmentProperties(Collections.unmodifiableMap(baseEnvironment))); return source; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ldap/LdapConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ldap/LdapConnectionDetails.java new file mode 100644 index 000000000000..efec54659440 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ldap/LdapConnectionDetails.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.ldap; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +/** + * Details required to establish a connection to an LDAP service. + * + * @author Philipp Kessler + * @since 3.3.0 + */ +public interface LdapConnectionDetails extends ConnectionDetails { + + /** + * LDAP URLs of the server. + * @return the LDAP URLs to use + */ + String[] getUrls(); + + /** + * Base suffix from which all operations should originate. + * @return base suffix + */ + default String getBase() { + return null; + } + + /** + * Login username of the server. + * @return login username + */ + default String getUsername() { + return null; + } + + /** + * Login password of the server. + * @return login password + */ + default String getPassword() { + return null; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ldap/PropertiesLdapConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ldap/PropertiesLdapConnectionDetails.java new file mode 100644 index 000000000000..41c9835e4b3f --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ldap/PropertiesLdapConnectionDetails.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.ldap; + +import org.springframework.core.env.Environment; + +/** + * Adapts {@link LdapProperties} to {@link LdapConnectionDetails}. + * + * @author Philipp Kessler + */ +class PropertiesLdapConnectionDetails implements LdapConnectionDetails { + + private final LdapProperties properties; + + private final Environment environment; + + PropertiesLdapConnectionDetails(LdapProperties properties, Environment environment) { + this.properties = properties; + this.environment = environment; + } + + @Override + public String[] getUrls() { + return this.properties.determineUrls(this.environment); + } + + @Override + public String getBase() { + return this.properties.getBase(); + } + + @Override + public String getUsername() { + return this.properties.getUsername(); + } + + @Override + public String getPassword() { + return this.properties.getPassword(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java index f19ab38f011b..e82a99b479de 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,13 @@ import javax.sql.DataSource; +import liquibase.Liquibase; +import liquibase.UpdateSummaryEnum; +import liquibase.UpdateSummaryOutputEnum; import liquibase.change.DatabaseChange; +import liquibase.integration.spring.Customizer; import liquibase.integration.spring.SpringLiquibase; +import liquibase.ui.UIServiceEnum; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; @@ -63,6 +68,7 @@ * @author Ferenc Gratzer * @author Evgeniy Cheban * @author Moritz Halbritter + * @author Ahmed Ashour * @since 1.1.0 */ @AutoConfiguration(after = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class }) @@ -87,15 +93,14 @@ public static class LiquibaseConfiguration { @Bean @ConditionalOnMissingBean(LiquibaseConnectionDetails.class) - PropertiesLiquibaseConnectionDetails liquibaseConnectionDetails(LiquibaseProperties properties, - ObjectProvider jdbcConnectionDetails) { + PropertiesLiquibaseConnectionDetails liquibaseConnectionDetails(LiquibaseProperties properties) { return new PropertiesLiquibaseConnectionDetails(properties); } @Bean - public SpringLiquibase liquibase(ObjectProvider dataSource, + SpringLiquibase liquibase(ObjectProvider dataSource, @LiquibaseDataSource ObjectProvider liquibaseDataSource, LiquibaseProperties properties, - LiquibaseConnectionDetails connectionDetails) { + ObjectProvider customizers, LiquibaseConnectionDetails connectionDetails) { SpringLiquibase liquibase = createSpringLiquibase(liquibaseDataSource.getIfAvailable(), dataSource.getIfUnique(), connectionDetails); liquibase.setChangeLog(properties.getChangeLog()); @@ -113,6 +118,17 @@ public SpringLiquibase liquibase(ObjectProvider dataSource, liquibase.setRollbackFile(properties.getRollbackFile()); liquibase.setTestRollbackOnUpdate(properties.isTestRollbackOnUpdate()); liquibase.setTag(properties.getTag()); + if (properties.getShowSummary() != null) { + liquibase.setShowSummary(UpdateSummaryEnum.valueOf(properties.getShowSummary().name())); + } + if (properties.getShowSummaryOutput() != null) { + liquibase + .setShowSummaryOutput(UpdateSummaryOutputEnum.valueOf(properties.getShowSummaryOutput().name())); + } + if (properties.getUiService() != null) { + liquibase.setUiService(UIServiceEnum.valueOf(properties.getUiService().name())); + } + customizers.orderedStream().forEach((customizer) -> customizer.customize(liquibase)); return liquibase; } @@ -161,6 +177,17 @@ private void applyConnectionDetails(LiquibaseConnectionDetails connectionDetails } + @ConditionalOnClass(Customizer.class) + static class CustomizerConfiguration { + + @Bean + @ConditionalOnBean(Customizer.class) + SpringLiquibaseCustomizer customizerSpringLiquibaseCustomizer(Customizer customizer) { + return (springLiquibase) -> springLiquibase.setCustomizer(customizer); + } + + } + static final class LiquibaseDataSourceCondition extends AnyNestedCondition { LiquibaseDataSourceCondition() { @@ -227,4 +254,15 @@ public String getDriverClassName() { } + @FunctionalInterface + private interface SpringLiquibaseCustomizer { + + /** + * Customize the given {@link SpringLiquibase} instance. + * @param springLiquibase the instance to configure + */ + void customize(SpringLiquibase springLiquibase); + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseProperties.java index 478897d053d9..b263b1a4dbe6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,12 @@ import java.io.File; import java.util.Map; +import liquibase.UpdateSummaryEnum; +import liquibase.UpdateSummaryOutputEnum; import liquibase.integration.spring.SpringLiquibase; +import liquibase.ui.UIServiceEnum; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.util.Assert; /** @@ -136,6 +138,21 @@ public class LiquibaseProperties { */ private String tag; + /** + * Whether to print a summary of the update operation. + */ + private ShowSummary showSummary; + + /** + * Where to print a summary of the update operation. + */ + private ShowSummaryOutput showSummaryOutput; + + /** + * Which UIService to use. + */ + private UiService uiService; + public String getChangeLog() { return this.changeLog; } @@ -257,17 +274,6 @@ public void setLabelFilter(String labelFilter) { this.labelFilter = labelFilter; } - @Deprecated(since = "3.0.0", forRemoval = true) - @DeprecatedConfigurationProperty(replacement = "spring.liquibase.label-filter") - public String getLabels() { - return getLabelFilter(); - } - - @Deprecated(since = "3.0.0", forRemoval = true) - public void setLabels(String labels) { - setLabelFilter(labels); - } - public Map getParameters() { return this.parameters; } @@ -300,4 +306,99 @@ public void setTag(String tag) { this.tag = tag; } + public ShowSummary getShowSummary() { + return this.showSummary; + } + + public void setShowSummary(ShowSummary showSummary) { + this.showSummary = showSummary; + } + + public ShowSummaryOutput getShowSummaryOutput() { + return this.showSummaryOutput; + } + + public void setShowSummaryOutput(ShowSummaryOutput showSummaryOutput) { + this.showSummaryOutput = showSummaryOutput; + } + + public UiService getUiService() { + return this.uiService; + } + + public void setUiService(UiService uiService) { + this.uiService = uiService; + } + + /** + * Enumeration of types of summary to show. Values are the same as those on + * {@link UpdateSummaryEnum}. To maximize backwards compatibility, the Liquibase enum + * is not used directly. + * + * @since 3.2.1 + */ + public enum ShowSummary { + + /** + * Do not show a summary. + */ + OFF, + + /** + * Show a summary. + */ + SUMMARY, + + /** + * Show a verbose summary. + */ + VERBOSE + + } + + /** + * Enumeration of destinations to which the summary should be output. Values are the + * same as those on {@link UpdateSummaryOutputEnum}. To maximize backwards + * compatibility, the Liquibase enum is not used directly. + * + * @since 3.2.1 + */ + public enum ShowSummaryOutput { + + /** + * Log the summary. + */ + LOG, + + /** + * Output the summary to the console. + */ + CONSOLE, + + /** + * Log the summary and output it to the console. + */ + ALL + + } + + /** + * Enumeration of types of UIService. Values are the same as those on + * {@link UIServiceEnum}. To maximize backwards compatibility, the Liquibase enum is + * not used directly. + */ + public enum UiService { + + /** + * Console-based UIService. + */ + CONSOLE, + + /** + * Logging-based UIService. + */ + LOGGER + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportLogger.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportLogger.java index 49a3036fd67d..1af820991ab5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportLogger.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportLogger.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -80,7 +80,7 @@ else if (isCrashReport) { private void logMessage(String logLevel) { this.logger.info(String.format("%n%nError starting ApplicationContext. To display the " - + "condition evaluation report re-run your application with '" + logLevel + "' enabled.")); + + "condition evaluation report re-run your application with '%s' enabled.", logLevel)); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailProperties.java index 5fecb40a6179..8ac597ab6497 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailProperties.java @@ -76,6 +76,11 @@ public class MailProperties { */ private String jndiName; + /** + * SSL configuration. + */ + private final Ssl ssl = new Ssl(); + public String getHost() { return this.host; } @@ -136,4 +141,45 @@ public String getJndiName() { return this.jndiName; } + public Ssl getSsl() { + return this.ssl; + } + + public static class Ssl { + + /** + * Whether to enable SSL support. If enabled, {@code mail..ssl.enable} + * property is set to {@code true}. + */ + private boolean enabled = false; + + /** + * SSL bundle name. If not null, {@code mail..ssl.socketFactory} + * property is set to a {@code SSLSocketFactory} obtained from the corresponding + * SSL bundle. + *

+ * Note that the {@code STARTTLS} command can use the corresponding + * {@code SSLSocketFactory}, even if {@code mail..ssl.enable} property + * is not set. + */ + private String bundle; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getBundle() { + return this.bundle; + } + + public void setBundle(String bundle) { + this.bundle = bundle; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailSenderPropertiesConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailSenderPropertiesConfiguration.java index 5c5bd795949e..7466dcd9d77f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailSenderPropertiesConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailSenderPropertiesConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,13 +19,18 @@ import java.util.Map; import java.util.Properties; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.mail.MailProperties.Ssl; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.mail.MailSender; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.springframework.util.StringUtils; /** * Auto-configure a {@link MailSender} based on properties configuration. @@ -40,13 +45,13 @@ class MailSenderPropertiesConfiguration { @Bean @ConditionalOnMissingBean(JavaMailSender.class) - JavaMailSenderImpl mailSender(MailProperties properties) { + JavaMailSenderImpl mailSender(MailProperties properties, ObjectProvider sslBundles) { JavaMailSenderImpl sender = new JavaMailSenderImpl(); - applyProperties(properties, sender); + applyProperties(properties, sender, sslBundles.getIfAvailable()); return sender; } - private void applyProperties(MailProperties properties, JavaMailSenderImpl sender) { + private void applyProperties(MailProperties properties, JavaMailSenderImpl sender, SslBundles sslBundles) { sender.setHost(properties.getHost()); if (properties.getPort() != null) { sender.setPort(properties.getPort()); @@ -57,8 +62,22 @@ private void applyProperties(MailProperties properties, JavaMailSenderImpl sende if (properties.getDefaultEncoding() != null) { sender.setDefaultEncoding(properties.getDefaultEncoding().name()); } - if (!properties.getProperties().isEmpty()) { - sender.setJavaMailProperties(asProperties(properties.getProperties())); + Properties javaMailProperties = asProperties(properties.getProperties()); + String protocol = properties.getProtocol(); + if (!StringUtils.hasLength(protocol)) { + protocol = "smtp"; + } + Ssl ssl = properties.getSsl(); + if (ssl.isEnabled()) { + javaMailProperties.setProperty("mail." + protocol + ".ssl.enable", "true"); + } + if (ssl.getBundle() != null) { + SslBundle sslBundle = sslBundles.getBundle(ssl.getBundle()); + javaMailProperties.put("mail." + protocol + ".ssl.socketFactory", + sslBundle.createSslContext().getSocketFactory()); + } + if (!javaMailProperties.isEmpty()) { + sender.setJavaMailProperties(javaMailProperties); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesClientSettingsBuilderCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesClientSettingsBuilderCustomizer.java deleted file mode 100644 index 691064cd5e69..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesClientSettingsBuilderCustomizer.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.mongo; - -import java.util.ArrayList; -import java.util.List; - -import com.mongodb.ConnectionString; -import com.mongodb.MongoClientSettings; -import com.mongodb.MongoCredential; -import com.mongodb.ServerAddress; - -import org.springframework.core.Ordered; -import org.springframework.util.CollectionUtils; - -/** - * A {@link MongoClientSettingsBuilderCustomizer} that applies properties from a - * {@link MongoProperties} to a {@link MongoClientSettings}. - * - * @author Scott Frederick - * @author Safeer Ansari - * @since 2.4.0 - * @deprecated since 3.1.0 for removal in 3.3.0 in favor of - * {@link StandardMongoClientSettingsBuilderCustomizer} - */ -@Deprecated(since = "3.1.0", forRemoval = true) -public class MongoPropertiesClientSettingsBuilderCustomizer implements MongoClientSettingsBuilderCustomizer, Ordered { - - private final MongoProperties properties; - - private int order = 0; - - public MongoPropertiesClientSettingsBuilderCustomizer(MongoProperties properties) { - this.properties = properties; - } - - @Override - public void customize(MongoClientSettings.Builder settingsBuilder) { - applyUuidRepresentation(settingsBuilder); - applyHostAndPort(settingsBuilder); - applyCredentials(settingsBuilder); - applyReplicaSet(settingsBuilder); - } - - private void applyUuidRepresentation(MongoClientSettings.Builder settingsBuilder) { - settingsBuilder.uuidRepresentation(this.properties.getUuidRepresentation()); - } - - private void applyHostAndPort(MongoClientSettings.Builder settings) { - if (this.properties.getUri() != null) { - settings.applyConnectionString(new ConnectionString(this.properties.getUri())); - return; - } - if (this.properties.getHost() != null || this.properties.getPort() != null) { - String host = getOrDefault(this.properties.getHost(), "localhost"); - int port = getOrDefault(this.properties.getPort(), MongoProperties.DEFAULT_PORT); - List serverAddresses = new ArrayList<>(); - serverAddresses.add(new ServerAddress(host, port)); - if (!CollectionUtils.isEmpty(this.properties.getAdditionalHosts())) { - this.properties.getAdditionalHosts().stream().map(ServerAddress::new).forEach(serverAddresses::add); - } - settings.applyToClusterSettings((cluster) -> cluster.hosts(serverAddresses)); - return; - } - settings.applyConnectionString(new ConnectionString(MongoProperties.DEFAULT_URI)); - } - - private void applyCredentials(MongoClientSettings.Builder builder) { - if (this.properties.getUri() == null && this.properties.getUsername() != null - && this.properties.getPassword() != null) { - String database = (this.properties.getAuthenticationDatabase() != null) - ? this.properties.getAuthenticationDatabase() : this.properties.getMongoClientDatabase(); - builder.credential((MongoCredential.createCredential(this.properties.getUsername(), database, - this.properties.getPassword()))); - } - } - - private void applyReplicaSet(MongoClientSettings.Builder builder) { - if (this.properties.getReplicaSetName() != null) { - builder.applyToClusterSettings( - (cluster) -> cluster.requiredReplicaSetName(this.properties.getReplicaSetName())); - } - } - - private V getOrDefault(V value, V defaultValue) { - return (value != null) ? value : defaultValue; - } - - @Override - public int getOrder() { - return this.order; - } - - /** - * Set the order value of this object. - * @param order the new order value - * @see #getOrder() - */ - public void setOrder(int order) { - this.order = order; - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfiguration.java index 1157e002945a..746bbe4587ff 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import com.mongodb.MongoClientSettings; import com.mongodb.MongoClientSettings.Builder; -import com.mongodb.connection.netty.NettyStreamFactoryFactory; +import com.mongodb.connection.TransportSettings; import com.mongodb.reactivestreams.client.MongoClient; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; @@ -113,11 +113,10 @@ static final class NettyDriverMongoClientSettingsBuilderCustomizer @Override public void customize(Builder builder) { - if (!isStreamFactoryFactoryDefined(this.settings.getIfAvailable())) { + if (!isCustomTransportConfiguration(this.settings.getIfAvailable())) { NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup(); this.eventLoopGroup = eventLoopGroup; - builder - .streamFactoryFactory(NettyStreamFactoryFactory.builder().eventLoopGroup(eventLoopGroup).build()); + builder.transportSettings(TransportSettings.nettyBuilder().eventLoopGroup(eventLoopGroup).build()); } } @@ -130,8 +129,8 @@ public void destroy() { } } - private boolean isStreamFactoryFactoryDefined(MongoClientSettings settings) { - return settings != null && settings.getStreamFactoryFactory() != null; + private boolean isCustomTransportConfiguration(MongoClientSettings settings) { + return settings != null && settings.getTransportSettings() != null; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfiguration.java index 2392227b2015..ed4e87c7e2fb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfiguration.java @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit; import org.neo4j.driver.AuthToken; +import org.neo4j.driver.AuthTokenManager; import org.neo4j.driver.AuthTokens; import org.neo4j.driver.Config; import org.neo4j.driver.Config.TrustStrategy; @@ -63,8 +64,9 @@ public class Neo4jAutoConfiguration { @Bean @ConditionalOnMissingBean(Neo4jConnectionDetails.class) - PropertiesNeo4jConnectionDetails neo4jConnectionDetails(Neo4jProperties properties) { - return new PropertiesNeo4jConnectionDetails(properties); + PropertiesNeo4jConnectionDetails neo4jConnectionDetails(Neo4jProperties properties, + ObjectProvider authTokenManager) { + return new PropertiesNeo4jConnectionDetails(properties, authTokenManager.getIfUnique()); } @Bean @@ -72,9 +74,14 @@ PropertiesNeo4jConnectionDetails neo4jConnectionDetails(Neo4jProperties properti public Driver neo4jDriver(Neo4jProperties properties, Environment environment, Neo4jConnectionDetails connectionDetails, ObjectProvider configBuilderCustomizers) { - AuthToken authToken = connectionDetails.getAuthToken(); + Config config = mapDriverConfig(properties, connectionDetails, configBuilderCustomizers.orderedStream().toList()); + AuthTokenManager authTokenManager = connectionDetails.getAuthTokenManager(); + if (authTokenManager != null) { + return GraphDatabase.driver(connectionDetails.getUri(), authTokenManager, config); + } + AuthToken authToken = connectionDetails.getAuthToken(); return GraphDatabase.driver(connectionDetails.getUri(), authToken, config); } @@ -179,8 +186,11 @@ static class PropertiesNeo4jConnectionDetails implements Neo4jConnectionDetails private final Neo4jProperties properties; - PropertiesNeo4jConnectionDetails(Neo4jProperties properties) { + private final AuthTokenManager authTokenManager; + + PropertiesNeo4jConnectionDetails(Neo4jProperties properties, AuthTokenManager authTokenManager) { this.properties = properties; + this.authTokenManager = authTokenManager; } @Override @@ -209,6 +219,11 @@ public AuthToken getAuthToken() { return AuthTokens.none(); } + @Override + public AuthTokenManager getAuthTokenManager() { + return this.authTokenManager; + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jConnectionDetails.java index 17a950ebd8bd..f10a122338b9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jConnectionDetails.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jConnectionDetails.java @@ -19,6 +19,7 @@ import java.net.URI; import org.neo4j.driver.AuthToken; +import org.neo4j.driver.AuthTokenManager; import org.neo4j.driver.AuthTokens; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; @@ -49,4 +50,14 @@ default AuthToken getAuthToken() { return AuthTokens.none(); } + /** + * Returns the {@link AuthTokenManager} to use for authentication. Defaults to + * {@code null} in which case the {@link #getAuthToken() auth token} should be used. + * @return the auth token manager + * @since 3.2.0 + */ + default AuthTokenManager getAuthTokenManager() { + return null; + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java index b073e0c7c3ee..807a0b74b2ad 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; +import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizationAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Import; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; @@ -38,7 +39,8 @@ * @author Andy Wilkinson * @since 1.0.0 */ -@AutoConfiguration(after = DataSourceAutoConfiguration.class, +@AutoConfiguration( + after = { DataSourceAutoConfiguration.class, TransactionManagerCustomizationAutoConfiguration.class }, before = { TransactionAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class }) @ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManager.class, SessionImplementor.class }) @EnableConfigurationProperties(JpaProperties.class) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateProperties.java index d155a00c2b00..d35e6f41046f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -148,9 +148,9 @@ public void setPhysicalStrategy(String physicalStrategy) { private void applyNamingStrategies(Map properties) { applyNamingStrategy(properties, AvailableSettings.IMPLICIT_NAMING_STRATEGY, this.implicitStrategy, - () -> SpringImplicitNamingStrategy.class.getName()); + SpringImplicitNamingStrategy.class::getName); applyNamingStrategy(properties, AvailableSettings.PHYSICAL_NAMING_STRATEGY, this.physicalStrategy, - () -> CamelCaseToUnderscoresNamingStrategy.class.getName()); + CamelCaseToUnderscoresNamingStrategy.class::getName); } private void applyNamingStrategy(Map properties, String key, Object strategy, diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java index 0520ff094866..3916e51929dd 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,6 +46,7 @@ import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.JpaVendorAdapter; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.persistenceunit.ManagedClassNameFilter; import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes; import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypesScanner; import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager; @@ -69,6 +70,7 @@ * @author Andy Wilkinson * @author Kazuki Shimizu * @author Eddú Meléndez + * @author Yanming Zhou * @since 1.0.0 */ @Configuration(proxyBeanMethods = false) @@ -194,9 +196,11 @@ static class PersistenceManagedTypesConfiguration { @Bean @Primary @ConditionalOnMissingBean - static PersistenceManagedTypes persistenceManagedTypes(BeanFactory beanFactory, ResourceLoader resourceLoader) { + static PersistenceManagedTypes persistenceManagedTypes(BeanFactory beanFactory, ResourceLoader resourceLoader, + ObjectProvider managedClassNameFilter) { String[] packagesToScan = getPackagesToScan(beanFactory); - return new PersistenceManagedTypesScanner(resourceLoader).scan(packagesToScan); + return new PersistenceManagedTypesScanner(resourceLoader, managedClassNameFilter.getIfAvailable()) + .scan(packagesToScan); } private static String[] getPackagesToScan(BeanFactory beanFactory) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/DeadLetterPolicyMapper.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/DeadLetterPolicyMapper.java new file mode 100644 index 000000000000..fc4a4f64b6bc --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/DeadLetterPolicyMapper.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import org.apache.pulsar.client.api.DeadLetterPolicy; +import org.apache.pulsar.client.api.DeadLetterPolicy.DeadLetterPolicyBuilder; + +import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.util.Assert; + +/** + * Helper class used to map {@link PulsarProperties.Consumer.DeadLetterPolicy dead letter + * policy properties}. + * + * @author Chris Bono + * @author Phillip Webb + */ +final class DeadLetterPolicyMapper { + + private DeadLetterPolicyMapper() { + } + + static DeadLetterPolicy map(PulsarProperties.Consumer.DeadLetterPolicy policy) { + Assert.state(policy.getMaxRedeliverCount() > 0, + "Pulsar DeadLetterPolicy must have a positive 'max-redelivery-count' property value"); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + DeadLetterPolicyBuilder builder = DeadLetterPolicy.builder(); + map.from(policy::getMaxRedeliverCount).to(builder::maxRedeliverCount); + map.from(policy::getRetryLetterTopic).to(builder::retryLetterTopic); + map.from(policy::getDeadLetterTopic).to(builder::deadLetterTopic); + map.from(policy::getInitialSubscriptionName).to(builder::initialSubscriptionName); + return builder.build(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PropertiesPulsarConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PropertiesPulsarConnectionDetails.java new file mode 100644 index 000000000000..51ed0fadc322 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PropertiesPulsarConnectionDetails.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +/** + * Adapts {@link PulsarProperties} to {@link PulsarConnectionDetails}. + * + * @author Chris Bono + */ +class PropertiesPulsarConnectionDetails implements PulsarConnectionDetails { + + private final PulsarProperties pulsarProperties; + + PropertiesPulsarConnectionDetails(PulsarProperties pulsarProperties) { + this.pulsarProperties = pulsarProperties; + } + + @Override + public String getBrokerUrl() { + return this.pulsarProperties.getClient().getServiceUrl(); + } + + @Override + public String getAdminUrl() { + return this.pulsarProperties.getAdmin().getServiceUrl(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarAutoConfiguration.java new file mode 100644 index 000000000000..92c66e216824 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarAutoConfiguration.java @@ -0,0 +1,220 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.pulsar.client.api.ConsumerBuilder; +import org.apache.pulsar.client.api.ProducerBuilder; +import org.apache.pulsar.client.api.PulsarClient; +import org.apache.pulsar.client.api.ReaderBuilder; +import org.apache.pulsar.client.api.interceptor.ProducerInterceptor; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.thread.Threading; +import org.springframework.boot.util.LambdaSafe; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.env.Environment; +import org.springframework.core.task.VirtualThreadTaskExecutor; +import org.springframework.pulsar.annotation.EnablePulsar; +import org.springframework.pulsar.config.ConcurrentPulsarListenerContainerFactory; +import org.springframework.pulsar.config.DefaultPulsarReaderContainerFactory; +import org.springframework.pulsar.config.PulsarAnnotationSupportBeanNames; +import org.springframework.pulsar.core.CachingPulsarProducerFactory; +import org.springframework.pulsar.core.ConsumerBuilderCustomizer; +import org.springframework.pulsar.core.DefaultPulsarConsumerFactory; +import org.springframework.pulsar.core.DefaultPulsarProducerFactory; +import org.springframework.pulsar.core.DefaultPulsarReaderFactory; +import org.springframework.pulsar.core.ProducerBuilderCustomizer; +import org.springframework.pulsar.core.PulsarConsumerFactory; +import org.springframework.pulsar.core.PulsarProducerFactory; +import org.springframework.pulsar.core.PulsarReaderFactory; +import org.springframework.pulsar.core.PulsarTemplate; +import org.springframework.pulsar.core.ReaderBuilderCustomizer; +import org.springframework.pulsar.core.SchemaResolver; +import org.springframework.pulsar.core.TopicResolver; +import org.springframework.pulsar.listener.PulsarContainerProperties; +import org.springframework.pulsar.reader.PulsarReaderContainerProperties; +import org.springframework.pulsar.transaction.PulsarAwareTransactionManager; +import org.springframework.pulsar.transaction.PulsarTransactionManager; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Apache Pulsar. + * + * @author Chris Bono + * @author Soby Chacko + * @author Alexander Preuß + * @author Phillip Webb + * @author Jonas Geiregat + * @since 3.2.0 + */ +@AutoConfiguration +@ConditionalOnClass({ PulsarClient.class, PulsarTemplate.class }) +@Import(PulsarConfiguration.class) +public class PulsarAutoConfiguration { + + private final PulsarProperties properties; + + private final PulsarPropertiesMapper propertiesMapper; + + PulsarAutoConfiguration(PulsarProperties properties) { + this.properties = properties; + this.propertiesMapper = new PulsarPropertiesMapper(properties); + } + + @Bean + @ConditionalOnMissingBean(PulsarProducerFactory.class) + @ConditionalOnProperty(name = "spring.pulsar.producer.cache.enabled", havingValue = "false") + DefaultPulsarProducerFactory pulsarProducerFactory(PulsarClient pulsarClient, TopicResolver topicResolver, + ObjectProvider> customizersProvider) { + List> lambdaSafeCustomizers = lambdaSafeProducerBuilderCustomizers( + customizersProvider); + return new DefaultPulsarProducerFactory<>(pulsarClient, this.properties.getProducer().getTopicName(), + lambdaSafeCustomizers, topicResolver); + } + + @Bean + @ConditionalOnMissingBean(PulsarProducerFactory.class) + @ConditionalOnProperty(name = "spring.pulsar.producer.cache.enabled", havingValue = "true", matchIfMissing = true) + CachingPulsarProducerFactory cachingPulsarProducerFactory(PulsarClient pulsarClient, TopicResolver topicResolver, + ObjectProvider> customizersProvider) { + PulsarProperties.Producer.Cache cacheProperties = this.properties.getProducer().getCache(); + List> lambdaSafeCustomizers = lambdaSafeProducerBuilderCustomizers( + customizersProvider); + return new CachingPulsarProducerFactory<>(pulsarClient, this.properties.getProducer().getTopicName(), + lambdaSafeCustomizers, topicResolver, cacheProperties.getExpireAfterAccess(), + cacheProperties.getMaximumSize(), cacheProperties.getInitialCapacity()); + } + + private List> lambdaSafeProducerBuilderCustomizers( + ObjectProvider> customizersProvider) { + List> customizers = new ArrayList<>(); + customizers.add(this.propertiesMapper::customizeProducerBuilder); + customizers.addAll(customizersProvider.orderedStream().toList()); + return List.of((builder) -> applyProducerBuilderCustomizers(customizers, builder)); + } + + @SuppressWarnings("unchecked") + private void applyProducerBuilderCustomizers(List> customizers, + ProducerBuilder builder) { + LambdaSafe.callbacks(ProducerBuilderCustomizer.class, customizers, builder) + .invoke((customizer) -> customizer.customize(builder)); + } + + @Bean + @ConditionalOnMissingBean + PulsarTemplate pulsarTemplate(PulsarProducerFactory pulsarProducerFactory, + ObjectProvider producerInterceptors, SchemaResolver schemaResolver, + TopicResolver topicResolver) { + PulsarTemplate template = new PulsarTemplate<>(pulsarProducerFactory, + producerInterceptors.orderedStream().toList(), schemaResolver, topicResolver, + this.properties.getTemplate().isObservationsEnabled()); + this.propertiesMapper.customizeTemplate(template); + return template; + } + + @Bean + @ConditionalOnMissingBean(PulsarConsumerFactory.class) + DefaultPulsarConsumerFactory pulsarConsumerFactory(PulsarClient pulsarClient, + ObjectProvider> customizersProvider) { + List> customizers = new ArrayList<>(); + customizers.add(this.propertiesMapper::customizeConsumerBuilder); + customizers.addAll(customizersProvider.orderedStream().toList()); + List> lambdaSafeCustomizers = List + .of((builder) -> applyConsumerBuilderCustomizers(customizers, builder)); + return new DefaultPulsarConsumerFactory<>(pulsarClient, lambdaSafeCustomizers); + } + + @Bean + @ConditionalOnMissingBean(PulsarAwareTransactionManager.class) + @ConditionalOnProperty(prefix = "spring.pulsar.transaction", name = "enabled") + public PulsarTransactionManager pulsarTransactionManager(PulsarClient pulsarClient) { + return new PulsarTransactionManager(pulsarClient); + } + + @SuppressWarnings("unchecked") + private void applyConsumerBuilderCustomizers(List> customizers, + ConsumerBuilder builder) { + LambdaSafe.callbacks(ConsumerBuilderCustomizer.class, customizers, builder) + .invoke((customizer) -> customizer.customize(builder)); + } + + @Bean + @ConditionalOnMissingBean(name = "pulsarListenerContainerFactory") + ConcurrentPulsarListenerContainerFactory pulsarListenerContainerFactory( + PulsarConsumerFactory pulsarConsumerFactory, SchemaResolver schemaResolver, + TopicResolver topicResolver, ObjectProvider pulsarTransactionManager, + Environment environment) { + PulsarContainerProperties containerProperties = new PulsarContainerProperties(); + containerProperties.setSchemaResolver(schemaResolver); + containerProperties.setTopicResolver(topicResolver); + if (Threading.VIRTUAL.isActive(environment)) { + containerProperties.setConsumerTaskExecutor(new VirtualThreadTaskExecutor("pulsar-consumer-")); + } + pulsarTransactionManager.ifUnique(containerProperties.transactions()::setTransactionManager); + this.propertiesMapper.customizeContainerProperties(containerProperties); + return new ConcurrentPulsarListenerContainerFactory<>(pulsarConsumerFactory, containerProperties); + } + + @Bean + @ConditionalOnMissingBean(PulsarReaderFactory.class) + DefaultPulsarReaderFactory pulsarReaderFactory(PulsarClient pulsarClient, + ObjectProvider> customizersProvider) { + List> customizers = new ArrayList<>(); + customizers.add(this.propertiesMapper::customizeReaderBuilder); + customizers.addAll(customizersProvider.orderedStream().toList()); + List> lambdaSafeCustomizers = List + .of((builder) -> applyReaderBuilderCustomizers(customizers, builder)); + return new DefaultPulsarReaderFactory<>(pulsarClient, lambdaSafeCustomizers); + } + + @SuppressWarnings("unchecked") + private void applyReaderBuilderCustomizers(List> customizers, ReaderBuilder builder) { + LambdaSafe.callbacks(ReaderBuilderCustomizer.class, customizers, builder) + .invoke((customizer) -> customizer.customize(builder)); + } + + @Bean + @ConditionalOnMissingBean(name = "pulsarReaderContainerFactory") + DefaultPulsarReaderContainerFactory pulsarReaderContainerFactory(PulsarReaderFactory pulsarReaderFactory, + SchemaResolver schemaResolver, Environment environment) { + PulsarReaderContainerProperties readerContainerProperties = new PulsarReaderContainerProperties(); + readerContainerProperties.setSchemaResolver(schemaResolver); + if (Threading.VIRTUAL.isActive(environment)) { + readerContainerProperties.setReaderTaskExecutor(new VirtualThreadTaskExecutor("pulsar-reader-")); + } + this.propertiesMapper.customizeReaderContainerProperties(readerContainerProperties); + return new DefaultPulsarReaderContainerFactory<>(pulsarReaderFactory, readerContainerProperties); + } + + @Configuration(proxyBeanMethods = false) + @EnablePulsar + @ConditionalOnMissingBean(name = { PulsarAnnotationSupportBeanNames.PULSAR_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME, + PulsarAnnotationSupportBeanNames.PULSAR_READER_ANNOTATION_PROCESSOR_BEAN_NAME }) + static class EnablePulsarConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarConfiguration.java new file mode 100644 index 000000000000..64efd410ccfa --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarConfiguration.java @@ -0,0 +1,180 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.pulsar.client.admin.PulsarAdminBuilder; +import org.apache.pulsar.client.api.ClientBuilder; +import org.apache.pulsar.client.api.PulsarClient; +import org.apache.pulsar.client.api.PulsarClientException; +import org.apache.pulsar.client.api.Schema; +import org.apache.pulsar.common.schema.SchemaType; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.pulsar.PulsarProperties.Defaults.SchemaInfo; +import org.springframework.boot.autoconfigure.pulsar.PulsarProperties.Defaults.TypeMapping; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.util.LambdaSafe; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.pulsar.core.DefaultPulsarClientFactory; +import org.springframework.pulsar.core.DefaultSchemaResolver; +import org.springframework.pulsar.core.DefaultTopicResolver; +import org.springframework.pulsar.core.PulsarAdminBuilderCustomizer; +import org.springframework.pulsar.core.PulsarAdministration; +import org.springframework.pulsar.core.PulsarClientBuilderCustomizer; +import org.springframework.pulsar.core.PulsarClientFactory; +import org.springframework.pulsar.core.SchemaResolver; +import org.springframework.pulsar.core.SchemaResolver.SchemaResolverCustomizer; +import org.springframework.pulsar.core.TopicResolver; +import org.springframework.pulsar.function.PulsarFunction; +import org.springframework.pulsar.function.PulsarFunctionAdministration; +import org.springframework.pulsar.function.PulsarSink; +import org.springframework.pulsar.function.PulsarSource; + +/** + * Common configuration used by both {@link PulsarAutoConfiguration} and + * {@link PulsarReactiveAutoConfiguration}. A separate configuration class is used so that + * {@link PulsarAutoConfiguration} can be excluded for reactive only application. + * + * @author Chris Bono + * @author Phillip Webb + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(PulsarProperties.class) +class PulsarConfiguration { + + private final PulsarProperties properties; + + private final PulsarPropertiesMapper propertiesMapper; + + PulsarConfiguration(PulsarProperties properties) { + this.properties = properties; + this.propertiesMapper = new PulsarPropertiesMapper(properties); + } + + @Bean + @ConditionalOnMissingBean(PulsarConnectionDetails.class) + PropertiesPulsarConnectionDetails pulsarConnectionDetails() { + return new PropertiesPulsarConnectionDetails(this.properties); + } + + @Bean + @ConditionalOnMissingBean(PulsarClientFactory.class) + DefaultPulsarClientFactory pulsarClientFactory(PulsarConnectionDetails connectionDetails, + ObjectProvider customizersProvider) { + List allCustomizers = new ArrayList<>(); + allCustomizers.add((builder) -> this.propertiesMapper.customizeClientBuilder(builder, connectionDetails)); + allCustomizers.addAll(customizersProvider.orderedStream().toList()); + DefaultPulsarClientFactory clientFactory = new DefaultPulsarClientFactory( + (clientBuilder) -> applyClientBuilderCustomizers(allCustomizers, clientBuilder)); + return clientFactory; + } + + private void applyClientBuilderCustomizers(List customizers, + ClientBuilder clientBuilder) { + customizers.forEach((customizer) -> customizer.customize(clientBuilder)); + } + + @Bean + @ConditionalOnMissingBean + PulsarClient pulsarClient(PulsarClientFactory clientFactory) throws PulsarClientException { + return clientFactory.createClient(); + } + + @Bean + @ConditionalOnMissingBean + PulsarAdministration pulsarAdministration(PulsarConnectionDetails connectionDetails, + ObjectProvider pulsarAdminBuilderCustomizers) { + List allCustomizers = new ArrayList<>(); + allCustomizers.add((builder) -> this.propertiesMapper.customizeAdminBuilder(builder, connectionDetails)); + allCustomizers.addAll(pulsarAdminBuilderCustomizers.orderedStream().toList()); + return new PulsarAdministration((adminBuilder) -> applyAdminBuilderCustomizers(allCustomizers, adminBuilder)); + } + + private void applyAdminBuilderCustomizers(List customizers, + PulsarAdminBuilder adminBuilder) { + customizers.forEach((customizer) -> customizer.customize(adminBuilder)); + } + + @Bean + @ConditionalOnMissingBean(SchemaResolver.class) + DefaultSchemaResolver pulsarSchemaResolver(ObjectProvider> schemaResolverCustomizers) { + DefaultSchemaResolver schemaResolver = new DefaultSchemaResolver(); + addCustomSchemaMappings(schemaResolver, this.properties.getDefaults().getTypeMappings()); + applySchemaResolverCustomizers(schemaResolverCustomizers.orderedStream().toList(), schemaResolver); + return schemaResolver; + } + + private void addCustomSchemaMappings(DefaultSchemaResolver schemaResolver, List typeMappings) { + if (typeMappings != null) { + typeMappings.forEach((typeMapping) -> addCustomSchemaMapping(schemaResolver, typeMapping)); + } + } + + private void addCustomSchemaMapping(DefaultSchemaResolver schemaResolver, TypeMapping typeMapping) { + SchemaInfo schemaInfo = typeMapping.schemaInfo(); + if (schemaInfo != null) { + Class messageType = typeMapping.messageType(); + SchemaType schemaType = schemaInfo.schemaType(); + Class messageKeyType = schemaInfo.messageKeyType(); + Schema schema = schemaResolver.resolveSchema(schemaType, messageType, messageKeyType).orElseThrow(); + schemaResolver.addCustomSchemaMapping(typeMapping.messageType(), schema); + } + } + + @SuppressWarnings("unchecked") + private void applySchemaResolverCustomizers(List> customizers, + DefaultSchemaResolver schemaResolver) { + LambdaSafe.callbacks(SchemaResolverCustomizer.class, customizers, schemaResolver) + .invoke((customizer) -> customizer.customize(schemaResolver)); + } + + @Bean + @ConditionalOnMissingBean(TopicResolver.class) + DefaultTopicResolver pulsarTopicResolver() { + DefaultTopicResolver topicResolver = new DefaultTopicResolver(); + List typeMappings = this.properties.getDefaults().getTypeMappings(); + if (typeMappings != null) { + typeMappings.forEach((typeMapping) -> addCustomTopicMapping(topicResolver, typeMapping)); + } + return topicResolver; + } + + private void addCustomTopicMapping(DefaultTopicResolver topicResolver, TypeMapping typeMapping) { + String topicName = typeMapping.topicName(); + if (topicName != null) { + topicResolver.addCustomTopicMapping(typeMapping.messageType(), topicName); + } + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(name = "spring.pulsar.function.enabled", havingValue = "true", matchIfMissing = true) + PulsarFunctionAdministration pulsarFunctionAdministration(PulsarAdministration pulsarAdministration, + ObjectProvider pulsarFunctions, ObjectProvider pulsarSinks, + ObjectProvider pulsarSources) { + PulsarProperties.Function properties = this.properties.getFunction(); + return new PulsarFunctionAdministration(pulsarAdministration, pulsarFunctions, pulsarSinks, pulsarSources, + properties.isFailFast(), properties.isPropagateFailures(), properties.isPropagateStopFailures()); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarConnectionDetails.java new file mode 100644 index 000000000000..1d21f5802e46 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarConnectionDetails.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +/** + * Details required to establish a connection to a Pulsar service. + * + * @author Chris Bono + * @since 3.2.0 + */ +public interface PulsarConnectionDetails extends ConnectionDetails { + + /** + * URL used to connect to the broker. + * @return the service URL + */ + String getBrokerUrl(); + + /** + * URL user to connect to the admin endpoint. + * @return the admin URL + */ + String getAdminUrl(); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarProperties.java new file mode 100644 index 000000000000..458aebb814a2 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarProperties.java @@ -0,0 +1,1023 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.pulsar.client.api.AutoClusterFailoverBuilder.FailoverPolicy; +import org.apache.pulsar.client.api.CompressionType; +import org.apache.pulsar.client.api.HashingScheme; +import org.apache.pulsar.client.api.MessageRoutingMode; +import org.apache.pulsar.client.api.ProducerAccessMode; +import org.apache.pulsar.client.api.RegexSubscriptionMode; +import org.apache.pulsar.client.api.SubscriptionInitialPosition; +import org.apache.pulsar.client.api.SubscriptionMode; +import org.apache.pulsar.client.api.SubscriptionType; +import org.apache.pulsar.common.schema.SchemaType; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.util.Assert; + +/** + * Configuration properties Apache Pulsar. + * + * @author Chris Bono + * @author Phillip Webb + * @author Swamy Mavuri + * @since 3.2.0 + */ +@ConfigurationProperties("spring.pulsar") +public class PulsarProperties { + + private final Client client = new Client(); + + private final Admin admin = new Admin(); + + private final Defaults defaults = new Defaults(); + + private final Function function = new Function(); + + private final Producer producer = new Producer(); + + private final Consumer consumer = new Consumer(); + + private final Listener listener = new Listener(); + + private final Reader reader = new Reader(); + + private final Template template = new Template(); + + private final Transaction transaction = new Transaction(); + + public Client getClient() { + return this.client; + } + + public Admin getAdmin() { + return this.admin; + } + + public Defaults getDefaults() { + return this.defaults; + } + + public Producer getProducer() { + return this.producer; + } + + public Consumer getConsumer() { + return this.consumer; + } + + public Listener getListener() { + return this.listener; + } + + public Reader getReader() { + return this.reader; + } + + public Function getFunction() { + return this.function; + } + + public Template getTemplate() { + return this.template; + } + + public Transaction getTransaction() { + return this.transaction; + } + + public static class Client { + + /** + * Pulsar service URL in the format '(pulsar|pulsar+ssl)://host:port'. + */ + private String serviceUrl = "pulsar://localhost:6650"; + + /** + * Client operation timeout. + */ + private Duration operationTimeout = Duration.ofSeconds(30); + + /** + * Client lookup timeout. + */ + private Duration lookupTimeout; + + /** + * Duration to wait for a connection to a broker to be established. + */ + private Duration connectionTimeout = Duration.ofSeconds(10); + + /** + * Authentication settings. + */ + private final Authentication authentication = new Authentication(); + + /** + * Failover settings. + */ + private final Failover failover = new Failover(); + + public String getServiceUrl() { + return this.serviceUrl; + } + + public void setServiceUrl(String serviceUrl) { + this.serviceUrl = serviceUrl; + } + + public Duration getOperationTimeout() { + return this.operationTimeout; + } + + public void setOperationTimeout(Duration operationTimeout) { + this.operationTimeout = operationTimeout; + } + + public Duration getLookupTimeout() { + return this.lookupTimeout; + } + + public void setLookupTimeout(Duration lookupTimeout) { + this.lookupTimeout = lookupTimeout; + } + + public Duration getConnectionTimeout() { + return this.connectionTimeout; + } + + public void setConnectionTimeout(Duration connectionTimeout) { + this.connectionTimeout = connectionTimeout; + } + + public Authentication getAuthentication() { + return this.authentication; + } + + public Failover getFailover() { + return this.failover; + } + + } + + public static class Admin { + + /** + * Pulsar web URL for the admin endpoint in the format '(http|https)://host:port'. + */ + private String serviceUrl = "http://localhost:8080"; + + /** + * Duration to wait for a connection to server to be established. + */ + private Duration connectionTimeout = Duration.ofMinutes(1); + + /** + * Server response read time out for any request. + */ + private Duration readTimeout = Duration.ofMinutes(1); + + /** + * Server request time out for any request. + */ + private Duration requestTimeout = Duration.ofMinutes(5); + + /** + * Authentication settings. + */ + private final Authentication authentication = new Authentication(); + + public String getServiceUrl() { + return this.serviceUrl; + } + + public void setServiceUrl(String serviceUrl) { + this.serviceUrl = serviceUrl; + } + + public Duration getConnectionTimeout() { + return this.connectionTimeout; + } + + public void setConnectionTimeout(Duration connectionTimeout) { + this.connectionTimeout = connectionTimeout; + } + + public Duration getReadTimeout() { + return this.readTimeout; + } + + public void setReadTimeout(Duration readTimeout) { + this.readTimeout = readTimeout; + } + + public Duration getRequestTimeout() { + return this.requestTimeout; + } + + public void setRequestTimeout(Duration requestTimeout) { + this.requestTimeout = requestTimeout; + } + + public Authentication getAuthentication() { + return this.authentication; + } + + } + + public static class Defaults { + + /** + * List of mappings from message type to topic name and schema info to use as a + * defaults when a topic name and/or schema is not explicitly specified when + * producing or consuming messages of the mapped type. + */ + private List typeMappings = new ArrayList<>(); + + public List getTypeMappings() { + return this.typeMappings; + } + + public void setTypeMappings(List typeMappings) { + this.typeMappings = typeMappings; + } + + /** + * A mapping from message type to topic and/or schema info to use (at least one of + * {@code topicName} or {@code schemaInfo} must be specified. + * + * @param messageType the message type + * @param topicName the topic name + * @param schemaInfo the schema info + */ + public record TypeMapping(Class messageType, String topicName, SchemaInfo schemaInfo) { + + public TypeMapping { + Assert.notNull(messageType, "messageType must not be null"); + Assert.isTrue(topicName != null || schemaInfo != null, + "At least one of topicName or schemaInfo must not be null"); + } + + } + + /** + * Represents a schema - holds enough information to construct an actual schema + * instance. + * + * @param schemaType schema type + * @param messageKeyType message key type (required for key value type) + */ + public record SchemaInfo(SchemaType schemaType, Class messageKeyType) { + + public SchemaInfo { + Assert.notNull(schemaType, "schemaType must not be null"); + Assert.isTrue(schemaType != SchemaType.NONE, "schemaType 'NONE' not supported"); + Assert.isTrue(messageKeyType == null || schemaType == SchemaType.KEY_VALUE, + "messageKeyType can only be set when schemaType is KEY_VALUE"); + } + + } + + } + + public static class Function { + + /** + * Whether to stop processing further function creates/updates when a failure + * occurs. + */ + private boolean failFast = true; + + /** + * Whether to throw an exception if any failure is encountered during server + * startup while creating/updating functions. + */ + private boolean propagateFailures = true; + + /** + * Whether to throw an exception if any failure is encountered during server + * shutdown while enforcing stop policy on functions. + */ + private boolean propagateStopFailures = false; + + public boolean isFailFast() { + return this.failFast; + } + + public void setFailFast(boolean failFast) { + this.failFast = failFast; + } + + public boolean isPropagateFailures() { + return this.propagateFailures; + } + + public void setPropagateFailures(boolean propagateFailures) { + this.propagateFailures = propagateFailures; + } + + public boolean isPropagateStopFailures() { + return this.propagateStopFailures; + } + + public void setPropagateStopFailures(boolean propagateStopFailures) { + this.propagateStopFailures = propagateStopFailures; + } + + } + + public static class Producer { + + /** + * Name for the producer. If not assigned, a unique name is generated. + */ + private String name; + + /** + * Topic the producer will publish to. + */ + private String topicName; + + /** + * Time before a message has to be acknowledged by the broker. + */ + private Duration sendTimeout = Duration.ofSeconds(30); + + /** + * Message routing mode for a partitioned producer. + */ + private MessageRoutingMode messageRoutingMode = MessageRoutingMode.RoundRobinPartition; + + /** + * Message hashing scheme to choose the partition to which the message is + * published. + */ + private HashingScheme hashingScheme = HashingScheme.JavaStringHash; + + /** + * Whether to automatically batch messages. + */ + private boolean batchingEnabled = true; + + /** + * Whether to split large-size messages into multiple chunks. + */ + private boolean chunkingEnabled; + + /** + * Message compression type. + */ + private CompressionType compressionType; + + /** + * Type of access to the topic the producer requires. + */ + private ProducerAccessMode accessMode = ProducerAccessMode.Shared; + + private final Cache cache = new Cache(); + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getTopicName() { + return this.topicName; + } + + public void setTopicName(String topicName) { + this.topicName = topicName; + } + + public Duration getSendTimeout() { + return this.sendTimeout; + } + + public void setSendTimeout(Duration sendTimeout) { + this.sendTimeout = sendTimeout; + } + + public MessageRoutingMode getMessageRoutingMode() { + return this.messageRoutingMode; + } + + public void setMessageRoutingMode(MessageRoutingMode messageRoutingMode) { + this.messageRoutingMode = messageRoutingMode; + } + + public HashingScheme getHashingScheme() { + return this.hashingScheme; + } + + public void setHashingScheme(HashingScheme hashingScheme) { + this.hashingScheme = hashingScheme; + } + + public boolean isBatchingEnabled() { + return this.batchingEnabled; + } + + public void setBatchingEnabled(boolean batchingEnabled) { + this.batchingEnabled = batchingEnabled; + } + + public boolean isChunkingEnabled() { + return this.chunkingEnabled; + } + + public void setChunkingEnabled(boolean chunkingEnabled) { + this.chunkingEnabled = chunkingEnabled; + } + + public CompressionType getCompressionType() { + return this.compressionType; + } + + public void setCompressionType(CompressionType compressionType) { + this.compressionType = compressionType; + } + + public ProducerAccessMode getAccessMode() { + return this.accessMode; + } + + public void setAccessMode(ProducerAccessMode accessMode) { + this.accessMode = accessMode; + } + + public Cache getCache() { + return this.cache; + } + + public static class Cache { + + /** + * Time period to expire unused entries in the cache. + */ + private Duration expireAfterAccess = Duration.ofMinutes(1); + + /** + * Maximum size of cache (entries). + */ + private long maximumSize = 1000L; + + /** + * Initial size of cache. + */ + private int initialCapacity = 50; + + public Duration getExpireAfterAccess() { + return this.expireAfterAccess; + } + + public void setExpireAfterAccess(Duration expireAfterAccess) { + this.expireAfterAccess = expireAfterAccess; + } + + public long getMaximumSize() { + return this.maximumSize; + } + + public void setMaximumSize(long maximumSize) { + this.maximumSize = maximumSize; + } + + public int getInitialCapacity() { + return this.initialCapacity; + } + + public void setInitialCapacity(int initialCapacity) { + this.initialCapacity = initialCapacity; + } + + } + + } + + public static class Consumer { + + /** + * Consumer name to identify a particular consumer from the topic stats. + */ + private String name; + + /** + * Topics the consumer subscribes to. + */ + private List topics; + + /** + * Pattern for topics the consumer subscribes to. + */ + private Pattern topicsPattern; + + /** + * Priority level for shared subscription consumers. + */ + private int priorityLevel = 0; + + /** + * Whether to read messages from the compacted topic rather than the full message + * backlog. + */ + private boolean readCompacted = false; + + /** + * Dead letter policy to use. + */ + @NestedConfigurationProperty + private DeadLetterPolicy deadLetterPolicy; + + /** + * Consumer subscription properties. + */ + private final Subscription subscription = new Subscription(); + + /** + * Whether to auto retry messages. + */ + private boolean retryEnable = false; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public Consumer.Subscription getSubscription() { + return this.subscription; + } + + public List getTopics() { + return this.topics; + } + + public void setTopics(List topics) { + this.topics = topics; + } + + public Pattern getTopicsPattern() { + return this.topicsPattern; + } + + public void setTopicsPattern(Pattern topicsPattern) { + this.topicsPattern = topicsPattern; + } + + public int getPriorityLevel() { + return this.priorityLevel; + } + + public void setPriorityLevel(int priorityLevel) { + this.priorityLevel = priorityLevel; + } + + public boolean isReadCompacted() { + return this.readCompacted; + } + + public void setReadCompacted(boolean readCompacted) { + this.readCompacted = readCompacted; + } + + public DeadLetterPolicy getDeadLetterPolicy() { + return this.deadLetterPolicy; + } + + public void setDeadLetterPolicy(DeadLetterPolicy deadLetterPolicy) { + this.deadLetterPolicy = deadLetterPolicy; + } + + public boolean isRetryEnable() { + return this.retryEnable; + } + + public void setRetryEnable(boolean retryEnable) { + this.retryEnable = retryEnable; + } + + public static class Subscription { + + /** + * Subscription name for the consumer. + */ + private String name; + + /** + * Position where to initialize a newly created subscription. + */ + private SubscriptionInitialPosition initialPosition = SubscriptionInitialPosition.Latest; + + /** + * Subscription mode to be used when subscribing to the topic. + */ + private SubscriptionMode mode = SubscriptionMode.Durable; + + /** + * Determines which type of topics (persistent, non-persistent, or all) the + * consumer should be subscribed to when using pattern subscriptions. + */ + private RegexSubscriptionMode topicsMode = RegexSubscriptionMode.PersistentOnly; + + /** + * Subscription type to be used when subscribing to a topic. + */ + private SubscriptionType type = SubscriptionType.Exclusive; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public SubscriptionInitialPosition getInitialPosition() { + return this.initialPosition; + } + + public void setInitialPosition(SubscriptionInitialPosition initialPosition) { + this.initialPosition = initialPosition; + } + + public SubscriptionMode getMode() { + return this.mode; + } + + public void setMode(SubscriptionMode mode) { + this.mode = mode; + } + + public RegexSubscriptionMode getTopicsMode() { + return this.topicsMode; + } + + public void setTopicsMode(RegexSubscriptionMode topicsMode) { + this.topicsMode = topicsMode; + } + + public SubscriptionType getType() { + return this.type; + } + + public void setType(SubscriptionType type) { + this.type = type; + } + + } + + public static class DeadLetterPolicy { + + /** + * Maximum number of times that a message will be redelivered before being + * sent to the dead letter queue. + */ + private int maxRedeliverCount; + + /** + * Name of the retry topic where the failing messages will be sent. + */ + private String retryLetterTopic; + + /** + * Name of the dead topic where the failing messages will be sent. + */ + private String deadLetterTopic; + + /** + * Name of the initial subscription of the dead letter topic. When not set, + * the initial subscription will not be created. However, when the property is + * set then the broker's 'allowAutoSubscriptionCreation' must be enabled or + * the DLQ producer will fail. + */ + private String initialSubscriptionName; + + public int getMaxRedeliverCount() { + return this.maxRedeliverCount; + } + + public void setMaxRedeliverCount(int maxRedeliverCount) { + this.maxRedeliverCount = maxRedeliverCount; + } + + public String getRetryLetterTopic() { + return this.retryLetterTopic; + } + + public void setRetryLetterTopic(String retryLetterTopic) { + this.retryLetterTopic = retryLetterTopic; + } + + public String getDeadLetterTopic() { + return this.deadLetterTopic; + } + + public void setDeadLetterTopic(String deadLetterTopic) { + this.deadLetterTopic = deadLetterTopic; + } + + public String getInitialSubscriptionName() { + return this.initialSubscriptionName; + } + + public void setInitialSubscriptionName(String initialSubscriptionName) { + this.initialSubscriptionName = initialSubscriptionName; + } + + } + + } + + public static class Listener { + + /** + * SchemaType of the consumed messages. + */ + private SchemaType schemaType; + + /** + * Whether to record observations for when the Observations API is available and + * the client supports it. + */ + private boolean observationEnabled; + + public SchemaType getSchemaType() { + return this.schemaType; + } + + public void setSchemaType(SchemaType schemaType) { + this.schemaType = schemaType; + } + + public boolean isObservationEnabled() { + return this.observationEnabled; + } + + public void setObservationEnabled(boolean observationEnabled) { + this.observationEnabled = observationEnabled; + } + + } + + public static class Reader { + + /** + * Reader name. + */ + private String name; + + /** + * Topics the reader subscribes to. + */ + private List topics; + + /** + * Subscription name. + */ + private String subscriptionName; + + /** + * Prefix of subscription role. + */ + private String subscriptionRolePrefix; + + /** + * Whether to read messages from a compacted topic rather than a full message + * backlog of a topic. + */ + private boolean readCompacted; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public List getTopics() { + return this.topics; + } + + public void setTopics(List topics) { + this.topics = topics; + } + + public String getSubscriptionName() { + return this.subscriptionName; + } + + public void setSubscriptionName(String subscriptionName) { + this.subscriptionName = subscriptionName; + } + + public String getSubscriptionRolePrefix() { + return this.subscriptionRolePrefix; + } + + public void setSubscriptionRolePrefix(String subscriptionRolePrefix) { + this.subscriptionRolePrefix = subscriptionRolePrefix; + } + + public boolean isReadCompacted() { + return this.readCompacted; + } + + public void setReadCompacted(boolean readCompacted) { + this.readCompacted = readCompacted; + } + + } + + public static class Template { + + /** + * Whether to record observations for when the Observations API is available. + */ + private boolean observationsEnabled; + + public boolean isObservationsEnabled() { + return this.observationsEnabled; + } + + public void setObservationsEnabled(boolean observationsEnabled) { + this.observationsEnabled = observationsEnabled; + } + + } + + public static class Transaction { + + /** + * Whether transaction support is enabled. + */ + private boolean enabled; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + } + + public static class Authentication { + + /** + * Fully qualified class name of the authentication plugin. + */ + private String pluginClassName; + + /** + * Authentication parameter(s) as a map of parameter names to parameter values. + */ + private Map param = new LinkedHashMap<>(); + + public String getPluginClassName() { + return this.pluginClassName; + } + + public void setPluginClassName(String pluginClassName) { + this.pluginClassName = pluginClassName; + } + + public Map getParam() { + return this.param; + } + + public void setParam(Map param) { + this.param = param; + } + + } + + public static class Failover { + + /** + * Cluster failover policy. + */ + private FailoverPolicy policy = FailoverPolicy.ORDER; + + /** + * Delay before the Pulsar client switches from the primary cluster to the backup + * cluster. + */ + private Duration delay; + + /** + * Delay before the Pulsar client switches from the backup cluster to the primary + * cluster. + */ + private Duration switchBackDelay; + + /** + * Frequency of performing a probe task. + */ + private Duration checkInterval; + + /** + * List of backup clusters. The backup cluster is chosen in the sequence of the + * given list. If all backup clusters are available, the Pulsar client chooses the + * first backup cluster. + */ + private List backupClusters = new ArrayList<>(); + + public FailoverPolicy getPolicy() { + return this.policy; + } + + public void setPolicy(FailoverPolicy policy) { + this.policy = policy; + } + + public Duration getDelay() { + return this.delay; + } + + public void setDelay(Duration delay) { + this.delay = delay; + } + + public Duration getSwitchBackDelay() { + return this.switchBackDelay; + } + + public void setSwitchBackDelay(Duration switchBackDelay) { + this.switchBackDelay = switchBackDelay; + } + + public Duration getCheckInterval() { + return this.checkInterval; + } + + public void setCheckInterval(Duration checkInterval) { + this.checkInterval = checkInterval; + } + + public List getBackupClusters() { + return this.backupClusters; + } + + public void setBackupClusters(List backupClusters) { + this.backupClusters = backupClusters; + } + + public static class BackupCluster { + + /** + * Pulsar service URL in the format '(pulsar|pulsar+ssl)://host:port'. + */ + private String serviceUrl = "pulsar://localhost:6650"; + + /** + * Authentication settings. + */ + private final Authentication authentication = new Authentication(); + + public String getServiceUrl() { + return this.serviceUrl; + } + + public void setServiceUrl(String serviceUrl) { + this.serviceUrl = serviceUrl; + } + + public Authentication getAuthentication() { + return this.authentication; + } + + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarPropertiesMapper.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarPropertiesMapper.java new file mode 100644 index 000000000000..36f8d80cdb9a --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarPropertiesMapper.java @@ -0,0 +1,224 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import org.apache.pulsar.client.admin.PulsarAdminBuilder; +import org.apache.pulsar.client.api.Authentication; +import org.apache.pulsar.client.api.AuthenticationFactory; +import org.apache.pulsar.client.api.AutoClusterFailoverBuilder; +import org.apache.pulsar.client.api.ClientBuilder; +import org.apache.pulsar.client.api.ConsumerBuilder; +import org.apache.pulsar.client.api.ProducerBuilder; +import org.apache.pulsar.client.api.PulsarClientException.UnsupportedAuthenticationException; +import org.apache.pulsar.client.api.ReaderBuilder; +import org.apache.pulsar.client.api.ServiceUrlProvider; +import org.apache.pulsar.client.impl.AutoClusterFailover.AutoClusterFailoverBuilderImpl; + +import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.json.JsonWriter; +import org.springframework.pulsar.core.PulsarTemplate; +import org.springframework.pulsar.listener.PulsarContainerProperties; +import org.springframework.pulsar.reader.PulsarReaderContainerProperties; +import org.springframework.util.StringUtils; + +/** + * Helper class used to map {@link PulsarProperties} to various builder customizers. + * + * @author Chris Bono + * @author Phillip Webb + * @author Swamy Mavuri + */ +final class PulsarPropertiesMapper { + + private static final JsonWriter> jsonWriter = JsonWriter + .of((members) -> members.addSelf().as(TreeMap::new).usingPairs(Map::forEach)); + + private final PulsarProperties properties; + + PulsarPropertiesMapper(PulsarProperties properties) { + this.properties = properties; + } + + void customizeClientBuilder(ClientBuilder clientBuilder, PulsarConnectionDetails connectionDetails) { + PulsarProperties.Client properties = this.properties.getClient(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getConnectionTimeout).to(timeoutProperty(clientBuilder::connectionTimeout)); + map.from(properties::getOperationTimeout).to(timeoutProperty(clientBuilder::operationTimeout)); + map.from(properties::getLookupTimeout).to(timeoutProperty(clientBuilder::lookupTimeout)); + map.from(this.properties.getTransaction()::isEnabled).whenTrue().to(clientBuilder::enableTransaction); + customizeAuthentication(properties.getAuthentication(), clientBuilder::authentication); + customizeServiceUrlProviderBuilder(clientBuilder::serviceUrl, clientBuilder::serviceUrlProvider, properties, + connectionDetails); + } + + private void customizeServiceUrlProviderBuilder(Consumer serviceUrlConsumer, + Consumer serviceUrlProviderConsumer, PulsarProperties.Client properties, + PulsarConnectionDetails connectionDetails) { + PulsarProperties.Failover failoverProperties = properties.getFailover(); + if (failoverProperties.getBackupClusters().isEmpty()) { + serviceUrlConsumer.accept(connectionDetails.getBrokerUrl()); + return; + } + Map secondaryAuths = getSecondaryAuths(failoverProperties); + AutoClusterFailoverBuilder autoClusterFailoverBuilder = new AutoClusterFailoverBuilderImpl(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(connectionDetails::getBrokerUrl).to(autoClusterFailoverBuilder::primary); + map.from(secondaryAuths::keySet).as(ArrayList::new).to(autoClusterFailoverBuilder::secondary); + map.from(failoverProperties::getPolicy).to(autoClusterFailoverBuilder::failoverPolicy); + map.from(failoverProperties::getDelay).to(timeoutProperty(autoClusterFailoverBuilder::failoverDelay)); + map.from(failoverProperties::getSwitchBackDelay) + .to(timeoutProperty(autoClusterFailoverBuilder::switchBackDelay)); + map.from(failoverProperties::getCheckInterval).to(timeoutProperty(autoClusterFailoverBuilder::checkInterval)); + map.from(secondaryAuths).to(autoClusterFailoverBuilder::secondaryAuthentication); + serviceUrlProviderConsumer.accept(autoClusterFailoverBuilder.build()); + } + + private Map getSecondaryAuths(PulsarProperties.Failover properties) { + Map secondaryAuths = new LinkedHashMap<>(); + properties.getBackupClusters().forEach((backupCluster) -> { + PulsarProperties.Authentication authenticationProperties = backupCluster.getAuthentication(); + if (authenticationProperties.getPluginClassName() == null) { + secondaryAuths.put(backupCluster.getServiceUrl(), null); + } + else { + customizeAuthentication(authenticationProperties, (authPluginClassName, authParams) -> { + Authentication authentication = AuthenticationFactory.create(authPluginClassName, authParams); + secondaryAuths.put(backupCluster.getServiceUrl(), authentication); + }); + } + }); + return secondaryAuths; + } + + void customizeAdminBuilder(PulsarAdminBuilder adminBuilder, PulsarConnectionDetails connectionDetails) { + PulsarProperties.Admin properties = this.properties.getAdmin(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(connectionDetails::getAdminUrl).to(adminBuilder::serviceHttpUrl); + map.from(properties::getConnectionTimeout).to(timeoutProperty(adminBuilder::connectionTimeout)); + map.from(properties::getReadTimeout).to(timeoutProperty(adminBuilder::readTimeout)); + map.from(properties::getRequestTimeout).to(timeoutProperty(adminBuilder::requestTimeout)); + customizeAuthentication(properties.getAuthentication(), adminBuilder::authentication); + } + + private void customizeAuthentication(PulsarProperties.Authentication properties, AuthenticationConsumer action) { + String pluginClassName = properties.getPluginClassName(); + if (StringUtils.hasText(pluginClassName)) { + try { + action.accept(pluginClassName, jsonWriter.writeToString(properties.getParam())); + } + catch (UnsupportedAuthenticationException ex) { + throw new IllegalStateException("Unable to configure Pulsar authentication", ex); + } + } + } + + void customizeProducerBuilder(ProducerBuilder producerBuilder) { + PulsarProperties.Producer properties = this.properties.getProducer(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getName).to(producerBuilder::producerName); + map.from(properties::getTopicName).to(producerBuilder::topic); + map.from(properties::getSendTimeout).to(timeoutProperty(producerBuilder::sendTimeout)); + map.from(properties::getMessageRoutingMode).to(producerBuilder::messageRoutingMode); + map.from(properties::getHashingScheme).to(producerBuilder::hashingScheme); + map.from(properties::isBatchingEnabled).to(producerBuilder::enableBatching); + map.from(properties::isChunkingEnabled).to(producerBuilder::enableChunking); + map.from(properties::getCompressionType).to(producerBuilder::compressionType); + map.from(properties::getAccessMode).to(producerBuilder::accessMode); + } + + void customizeTemplate(PulsarTemplate template) { + template.transactions().setEnabled(this.properties.getTransaction().isEnabled()); + } + + void customizeConsumerBuilder(ConsumerBuilder consumerBuilder) { + PulsarProperties.Consumer properties = this.properties.getConsumer(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getName).to(consumerBuilder::consumerName); + map.from(properties::getTopics).as(ArrayList::new).to(consumerBuilder::topics); + map.from(properties::getTopicsPattern).to(consumerBuilder::topicsPattern); + map.from(properties::getPriorityLevel).to(consumerBuilder::priorityLevel); + map.from(properties::isReadCompacted).to(consumerBuilder::readCompacted); + map.from(properties::getDeadLetterPolicy).as(DeadLetterPolicyMapper::map).to(consumerBuilder::deadLetterPolicy); + map.from(properties::isRetryEnable).to(consumerBuilder::enableRetry); + customizeConsumerBuilderSubscription(consumerBuilder); + } + + private void customizeConsumerBuilderSubscription(ConsumerBuilder consumerBuilder) { + PulsarProperties.Consumer.Subscription properties = this.properties.getConsumer().getSubscription(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getName).to(consumerBuilder::subscriptionName); + map.from(properties::getInitialPosition).to(consumerBuilder::subscriptionInitialPosition); + map.from(properties::getMode).to(consumerBuilder::subscriptionMode); + map.from(properties::getTopicsMode).to(consumerBuilder::subscriptionTopicsMode); + map.from(properties::getType).to(consumerBuilder::subscriptionType); + } + + void customizeContainerProperties(PulsarContainerProperties containerProperties) { + customizePulsarContainerConsumerSubscriptionProperties(containerProperties); + customizePulsarContainerListenerProperties(containerProperties); + containerProperties.transactions().setEnabled(this.properties.getTransaction().isEnabled()); + } + + private void customizePulsarContainerConsumerSubscriptionProperties(PulsarContainerProperties containerProperties) { + PulsarProperties.Consumer.Subscription properties = this.properties.getConsumer().getSubscription(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getType).to(containerProperties::setSubscriptionType); + } + + private void customizePulsarContainerListenerProperties(PulsarContainerProperties containerProperties) { + PulsarProperties.Listener properties = this.properties.getListener(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getSchemaType).to(containerProperties::setSchemaType); + map.from(properties::isObservationEnabled).to(containerProperties::setObservationEnabled); + } + + void customizeReaderBuilder(ReaderBuilder readerBuilder) { + PulsarProperties.Reader properties = this.properties.getReader(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getName).to(readerBuilder::readerName); + map.from(properties::getTopics).to(readerBuilder::topics); + map.from(properties::getSubscriptionName).to(readerBuilder::subscriptionName); + map.from(properties::getSubscriptionRolePrefix).to(readerBuilder::subscriptionRolePrefix); + map.from(properties::isReadCompacted).to(readerBuilder::readCompacted); + } + + void customizeReaderContainerProperties(PulsarReaderContainerProperties readerContainerProperties) { + PulsarProperties.Reader properties = this.properties.getReader(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getTopics).to(readerContainerProperties::setTopics); + } + + private Consumer timeoutProperty(BiConsumer setter) { + return (duration) -> setter.accept((int) duration.toMillis(), TimeUnit.MILLISECONDS); + } + + private interface AuthenticationConsumer { + + void accept(String authPluginClassName, String authParamString) throws UnsupportedAuthenticationException; + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarReactiveAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarReactiveAutoConfiguration.java new file mode 100644 index 000000000000..4c2aeb172d52 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarReactiveAutoConfiguration.java @@ -0,0 +1,201 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import org.apache.pulsar.client.api.PulsarClient; +import org.apache.pulsar.reactive.client.adapter.AdaptedReactivePulsarClientFactory; +import org.apache.pulsar.reactive.client.adapter.ProducerCacheProvider; +import org.apache.pulsar.reactive.client.api.ReactiveMessageConsumerBuilder; +import org.apache.pulsar.reactive.client.api.ReactiveMessageReaderBuilder; +import org.apache.pulsar.reactive.client.api.ReactiveMessageSenderBuilder; +import org.apache.pulsar.reactive.client.api.ReactiveMessageSenderCache; +import org.apache.pulsar.reactive.client.api.ReactivePulsarClient; +import org.apache.pulsar.reactive.client.producercache.CaffeineShadedProducerCacheProvider; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.util.LambdaSafe; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.pulsar.config.PulsarAnnotationSupportBeanNames; +import org.springframework.pulsar.core.SchemaResolver; +import org.springframework.pulsar.core.TopicResolver; +import org.springframework.pulsar.reactive.config.DefaultReactivePulsarListenerContainerFactory; +import org.springframework.pulsar.reactive.config.annotation.EnableReactivePulsar; +import org.springframework.pulsar.reactive.core.DefaultReactivePulsarConsumerFactory; +import org.springframework.pulsar.reactive.core.DefaultReactivePulsarReaderFactory; +import org.springframework.pulsar.reactive.core.DefaultReactivePulsarSenderFactory; +import org.springframework.pulsar.reactive.core.ReactiveMessageConsumerBuilderCustomizer; +import org.springframework.pulsar.reactive.core.ReactiveMessageReaderBuilderCustomizer; +import org.springframework.pulsar.reactive.core.ReactiveMessageSenderBuilderCustomizer; +import org.springframework.pulsar.reactive.core.ReactivePulsarConsumerFactory; +import org.springframework.pulsar.reactive.core.ReactivePulsarReaderFactory; +import org.springframework.pulsar.reactive.core.ReactivePulsarSenderFactory; +import org.springframework.pulsar.reactive.core.ReactivePulsarTemplate; +import org.springframework.pulsar.reactive.listener.ReactivePulsarContainerProperties; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Spring for Apache Pulsar + * Reactive. + * + * @author Chris Bono + * @author Christophe Bornet + * @since 3.2.0 + */ +@AutoConfiguration(after = PulsarAutoConfiguration.class) +@ConditionalOnClass({ PulsarClient.class, ReactivePulsarClient.class, ReactivePulsarTemplate.class }) +@Import(PulsarConfiguration.class) +public class PulsarReactiveAutoConfiguration { + + private final PulsarProperties properties; + + private final PulsarReactivePropertiesMapper propertiesMapper; + + PulsarReactiveAutoConfiguration(PulsarProperties properties) { + this.properties = properties; + this.propertiesMapper = new PulsarReactivePropertiesMapper(properties); + } + + @Bean + @ConditionalOnMissingBean + ReactivePulsarClient reactivePulsarClient(PulsarClient pulsarClient) { + return AdaptedReactivePulsarClientFactory.create(pulsarClient); + } + + @Bean + @ConditionalOnMissingBean(ProducerCacheProvider.class) + @ConditionalOnClass(CaffeineShadedProducerCacheProvider.class) + @ConditionalOnProperty(name = "spring.pulsar.producer.cache.enabled", havingValue = "true", matchIfMissing = true) + CaffeineShadedProducerCacheProvider reactivePulsarProducerCacheProvider() { + PulsarProperties.Producer.Cache properties = this.properties.getProducer().getCache(); + return new CaffeineShadedProducerCacheProvider(properties.getExpireAfterAccess(), Duration.ofMinutes(10), + properties.getMaximumSize(), properties.getInitialCapacity()); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(name = "spring.pulsar.producer.cache.enabled", havingValue = "true", matchIfMissing = true) + ReactiveMessageSenderCache reactivePulsarMessageSenderCache( + ObjectProvider producerCacheProvider) { + return reactivePulsarMessageSenderCache(producerCacheProvider.getIfAvailable()); + } + + private ReactiveMessageSenderCache reactivePulsarMessageSenderCache(ProducerCacheProvider producerCacheProvider) { + return (producerCacheProvider != null) ? AdaptedReactivePulsarClientFactory.createCache(producerCacheProvider) + : AdaptedReactivePulsarClientFactory.createCache(); + } + + @Bean + @ConditionalOnMissingBean(ReactivePulsarSenderFactory.class) + DefaultReactivePulsarSenderFactory reactivePulsarSenderFactory(ReactivePulsarClient reactivePulsarClient, + ObjectProvider reactiveMessageSenderCache, TopicResolver topicResolver, + ObjectProvider> customizersProvider) { + List> customizers = new ArrayList<>(); + customizers.add(this.propertiesMapper::customizeMessageSenderBuilder); + customizers.addAll(customizersProvider.orderedStream().toList()); + List> lambdaSafeCustomizers = List + .of((builder) -> applyMessageSenderBuilderCustomizers(customizers, builder)); + return DefaultReactivePulsarSenderFactory.builderFor(reactivePulsarClient) + .withDefaultConfigCustomizers(lambdaSafeCustomizers) + .withMessageSenderCache(reactiveMessageSenderCache.getIfAvailable()) + .withTopicResolver(topicResolver) + .build(); + } + + @SuppressWarnings("unchecked") + private void applyMessageSenderBuilderCustomizers(List> customizers, + ReactiveMessageSenderBuilder builder) { + LambdaSafe.callbacks(ReactiveMessageSenderBuilderCustomizer.class, customizers, builder) + .invoke((customizer) -> customizer.customize(builder)); + } + + @Bean + @ConditionalOnMissingBean(ReactivePulsarConsumerFactory.class) + DefaultReactivePulsarConsumerFactory reactivePulsarConsumerFactory( + ReactivePulsarClient pulsarReactivePulsarClient, + ObjectProvider> customizersProvider) { + List> customizers = new ArrayList<>(); + customizers.add(this.propertiesMapper::customizeMessageConsumerBuilder); + customizers.addAll(customizersProvider.orderedStream().toList()); + List> lambdaSafeCustomizers = List + .of((builder) -> applyMessageConsumerBuilderCustomizers(customizers, builder)); + return new DefaultReactivePulsarConsumerFactory<>(pulsarReactivePulsarClient, lambdaSafeCustomizers); + } + + @SuppressWarnings("unchecked") + private void applyMessageConsumerBuilderCustomizers(List> customizers, + ReactiveMessageConsumerBuilder builder) { + LambdaSafe.callbacks(ReactiveMessageConsumerBuilderCustomizer.class, customizers, builder) + .invoke((customizer) -> customizer.customize(builder)); + } + + @Bean + @ConditionalOnMissingBean(name = "reactivePulsarListenerContainerFactory") + DefaultReactivePulsarListenerContainerFactory reactivePulsarListenerContainerFactory( + ReactivePulsarConsumerFactory reactivePulsarConsumerFactory, SchemaResolver schemaResolver, + TopicResolver topicResolver) { + ReactivePulsarContainerProperties containerProperties = new ReactivePulsarContainerProperties<>(); + containerProperties.setSchemaResolver(schemaResolver); + containerProperties.setTopicResolver(topicResolver); + this.propertiesMapper.customizeContainerProperties(containerProperties); + return new DefaultReactivePulsarListenerContainerFactory<>(reactivePulsarConsumerFactory, containerProperties); + } + + @Bean + @ConditionalOnMissingBean(ReactivePulsarReaderFactory.class) + DefaultReactivePulsarReaderFactory reactivePulsarReaderFactory(ReactivePulsarClient reactivePulsarClient, + ObjectProvider> customizersProvider) { + List> customizers = new ArrayList<>(); + customizers.add(this.propertiesMapper::customizeMessageReaderBuilder); + customizers.addAll(customizersProvider.orderedStream().toList()); + List> lambdaSafeCustomizers = List + .of((builder) -> applyMessageReaderBuilderCustomizers(customizers, builder)); + return new DefaultReactivePulsarReaderFactory<>(reactivePulsarClient, lambdaSafeCustomizers); + } + + @SuppressWarnings("unchecked") + private void applyMessageReaderBuilderCustomizers(List> customizers, + ReactiveMessageReaderBuilder builder) { + LambdaSafe.callbacks(ReactiveMessageReaderBuilderCustomizer.class, customizers, builder) + .invoke((customizer) -> customizer.customize(builder)); + } + + @Bean + @ConditionalOnMissingBean + ReactivePulsarTemplate pulsarReactiveTemplate(ReactivePulsarSenderFactory reactivePulsarSenderFactory, + SchemaResolver schemaResolver, TopicResolver topicResolver) { + return new ReactivePulsarTemplate<>(reactivePulsarSenderFactory, schemaResolver, topicResolver); + } + + @Configuration(proxyBeanMethods = false) + @EnableReactivePulsar + @ConditionalOnMissingBean( + name = PulsarAnnotationSupportBeanNames.REACTIVE_PULSAR_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME) + static class EnableReactivePulsarConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarReactivePropertiesMapper.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarReactivePropertiesMapper.java new file mode 100644 index 000000000000..2f79bbae615f --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarReactivePropertiesMapper.java @@ -0,0 +1,108 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import java.util.ArrayList; + +import org.apache.pulsar.reactive.client.api.ReactiveMessageConsumerBuilder; +import org.apache.pulsar.reactive.client.api.ReactiveMessageReaderBuilder; +import org.apache.pulsar.reactive.client.api.ReactiveMessageSenderBuilder; + +import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.pulsar.reactive.listener.ReactivePulsarContainerProperties; + +/** + * Helper class used to map reactive {@link PulsarProperties} to various builder + * customizers. + * + * @author Chris Bono + * @author Phillip Webb + */ +final class PulsarReactivePropertiesMapper { + + private final PulsarProperties properties; + + PulsarReactivePropertiesMapper(PulsarProperties properties) { + this.properties = properties; + } + + void customizeMessageSenderBuilder(ReactiveMessageSenderBuilder builder) { + PulsarProperties.Producer properties = this.properties.getProducer(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getName).to(builder::producerName); + map.from(properties::getTopicName).to(builder::topic); + map.from(properties::getSendTimeout).to(builder::sendTimeout); + map.from(properties::getMessageRoutingMode).to(builder::messageRoutingMode); + map.from(properties::getHashingScheme).to(builder::hashingScheme); + map.from(properties::isBatchingEnabled).to(builder::batchingEnabled); + map.from(properties::isChunkingEnabled).to(builder::chunkingEnabled); + map.from(properties::getCompressionType).to(builder::compressionType); + map.from(properties::getAccessMode).to(builder::accessMode); + } + + void customizeMessageConsumerBuilder(ReactiveMessageConsumerBuilder builder) { + PulsarProperties.Consumer properties = this.properties.getConsumer(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getName).to(builder::consumerName); + map.from(properties::getTopics).as(ArrayList::new).to(builder::topics); + map.from(properties::getTopicsPattern).to(builder::topicsPattern); + map.from(properties::getPriorityLevel).to(builder::priorityLevel); + map.from(properties::isReadCompacted).to(builder::readCompacted); + map.from(properties::getDeadLetterPolicy).as(DeadLetterPolicyMapper::map).to(builder::deadLetterPolicy); + map.from(properties::isRetryEnable).to(builder::retryLetterTopicEnable); + customizerMessageConsumerBuilderSubscription(builder); + } + + private void customizerMessageConsumerBuilderSubscription(ReactiveMessageConsumerBuilder builder) { + PulsarProperties.Consumer.Subscription properties = this.properties.getConsumer().getSubscription(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getName).to(builder::subscriptionName); + map.from(properties::getInitialPosition).to(builder::subscriptionInitialPosition); + map.from(properties::getMode).to(builder::subscriptionMode); + map.from(properties::getTopicsMode).to(builder::topicsPatternSubscriptionMode); + map.from(properties::getType).to(builder::subscriptionType); + } + + void customizeContainerProperties(ReactivePulsarContainerProperties containerProperties) { + customizePulsarContainerConsumerSubscriptionProperties(containerProperties); + customizePulsarContainerListenerProperties(containerProperties); + } + + private void customizePulsarContainerConsumerSubscriptionProperties( + ReactivePulsarContainerProperties containerProperties) { + PulsarProperties.Consumer.Subscription properties = this.properties.getConsumer().getSubscription(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getType).to(containerProperties::setSubscriptionType); + } + + private void customizePulsarContainerListenerProperties(ReactivePulsarContainerProperties containerProperties) { + PulsarProperties.Listener properties = this.properties.getListener(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getSchemaType).to(containerProperties::setSchemaType); + } + + void customizeMessageReaderBuilder(ReactiveMessageReaderBuilder builder) { + PulsarProperties.Reader properties = this.properties.getReader(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getName).to(builder::readerName); + map.from(properties::getTopics).to(builder::topics); + map.from(properties::getSubscriptionName).to(builder::subscriptionName); + map.from(properties::getSubscriptionRolePrefix).to(builder::generatedSubscriptionNamePrefix); + map.from(properties::isReadCompacted).to(builder::readCompacted); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/package-info.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/package-info.java new file mode 100644 index 000000000000..d6ce8ee1d218 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for Spring for Apache Pulsar. + */ +package org.springframework.boot.autoconfigure.pulsar; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryConfigurations.java index 987b80fee7fc..6807a349f77b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryConfigurations.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryConfigurations.java @@ -33,6 +33,7 @@ import org.springframework.boot.context.properties.bind.BindResult; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.r2dbc.ConnectionFactoryDecorator; import org.springframework.boot.r2dbc.EmbeddedDatabaseConnection; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Condition; @@ -54,12 +55,14 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Moritz Halbritter */ abstract class ConnectionFactoryConfigurations { protected static ConnectionFactory createConnectionFactory(R2dbcProperties properties, R2dbcConnectionDetails connectionDetails, ClassLoader classLoader, - List optionsCustomizers) { + List optionsCustomizers, + List decorators) { try { return org.springframework.boot.r2dbc.ConnectionFactoryBuilder .withOptions(new ConnectionFactoryOptionsInitializer().initialize(properties, connectionDetails, @@ -69,6 +72,7 @@ protected static ConnectionFactory createConnectionFactory(R2dbcProperties prope optionsCustomizer.customize(options); } }) + .decorators(decorators) .build(); } catch (IllegalStateException ex) { @@ -93,10 +97,11 @@ static class PooledConnectionFactoryConfiguration { @Bean(destroyMethod = "dispose") ConnectionPool connectionFactory(R2dbcProperties properties, ObjectProvider connectionDetails, ResourceLoader resourceLoader, - ObjectProvider customizers) { + ObjectProvider customizers, + ObjectProvider decorators) { ConnectionFactory connectionFactory = createConnectionFactory(properties, connectionDetails.getIfAvailable(), resourceLoader.getClassLoader(), - customizers.orderedStream().toList()); + customizers.orderedStream().toList(), decorators.orderedStream().toList()); R2dbcProperties.Pool pool = properties.getPool(); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); ConnectionPoolConfiguration.Builder builder = ConnectionPoolConfiguration.builder(connectionFactory); @@ -126,9 +131,11 @@ static class GenericConfiguration { @Bean ConnectionFactory connectionFactory(R2dbcProperties properties, ObjectProvider connectionDetails, ResourceLoader resourceLoader, - ObjectProvider customizers) { + ObjectProvider customizers, + ObjectProvider decorators) { return createConnectionFactory(properties, connectionDetails.getIfAvailable(), - resourceLoader.getClassLoader(), customizers.orderedStream().toList()); + resourceLoader.getClassLoader(), customizers.orderedStream().toList(), + decorators.orderedStream().toList()); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ProxyConnectionFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ProxyConnectionFactoryCustomizer.java new file mode 100644 index 000000000000..d969ae5aa7b1 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ProxyConnectionFactoryCustomizer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.r2dbc; + +import io.r2dbc.proxy.ProxyConnectionFactory; + +/** + * Callback interface that can be used to customize a + * {@link ProxyConnectionFactory.Builder}. + * + * @author Tadaya Tsuyukubo + * @since 3.4.0 + */ +public interface ProxyConnectionFactoryCustomizer { + + /** + * Callback to customize a {@link ProxyConnectionFactory.Builder} instance. + * @param builder the builder to customize + */ + void customize(ProxyConnectionFactory.Builder builder); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProxyAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProxyAutoConfiguration.java new file mode 100644 index 000000000000..e929be0d95a3 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProxyAutoConfiguration.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.r2dbc; + +import io.r2dbc.proxy.ProxyConnectionFactory; +import io.r2dbc.spi.ConnectionFactory; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.r2dbc.ConnectionFactoryDecorator; +import org.springframework.context.annotation.Bean; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for {@link ProxyConnectionFactory}. + * + * @author Tadaya Tsuyukubo + * @author Moritz Halbritter + * @since 3.4.0 + */ +@AutoConfiguration +@ConditionalOnClass({ ConnectionFactory.class, ProxyConnectionFactory.class }) +public class R2dbcProxyAutoConfiguration { + + @Bean + ConnectionFactoryDecorator connectionFactoryDecorator( + ObjectProvider customizers) { + return (connectionFactory) -> { + ProxyConnectionFactory.Builder builder = ProxyConnectionFactory.builder(connectionFactory); + customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); + return builder.build(); + }; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/ReactorAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/ReactorAutoConfiguration.java new file mode 100644 index 000000000000..9323e6eca46a --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/ReactorAutoConfiguration.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.reactor; + +import reactor.core.publisher.Hooks; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Reactor. + * + * @author Brian Clozel + * @since 3.2.0 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(Hooks.class) +@EnableConfigurationProperties(ReactorProperties.class) +public class ReactorAutoConfiguration { + + ReactorAutoConfiguration(ReactorProperties properties) { + if (properties.getContextPropagation() == ReactorProperties.ContextPropagationMode.AUTO) { + Hooks.enableAutomaticContextPropagation(); + } + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/ReactorProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/ReactorProperties.java new file mode 100644 index 000000000000..c82da8b52389 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/ReactorProperties.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.reactor; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for Reactor. + * + * @author Brian Clozel + * @since 3.2.0 + */ +@ConfigurationProperties(prefix = "spring.reactor") +public class ReactorProperties { + + /** + * Context Propagation support mode for Reactor operators. + */ + private ContextPropagationMode contextPropagation = ContextPropagationMode.LIMITED; + + public ContextPropagationMode getContextPropagation() { + return this.contextPropagation; + } + + public void setContextPropagation(ContextPropagationMode contextPropagation) { + this.contextPropagation = contextPropagation; + } + + public enum ContextPropagationMode { + + /** + * Context Propagation is applied to all Reactor operators. + */ + AUTO, + + /** + * Context Propagation is only applied to "tap" and "handle" Reactor operators. + */ + LIMITED + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/netty/ReactorNettyConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/netty/ReactorNettyConfigurations.java index b6e3ba354805..35867272331f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/netty/ReactorNettyConfigurations.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/netty/ReactorNettyConfigurations.java @@ -20,7 +20,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.reactive.ReactorResourceFactory; +import org.springframework.http.client.ReactorResourceFactory; /** * Configurations for Reactor Netty. Those should be {@code @Import} in a regular diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/package-info.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/package-info.java new file mode 100644 index 000000000000..4b55cfe4d534 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for Reactor. + */ +package org.springframework.boot.autoconfigure.reactor; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketProperties.java index b5af9bc50305..bba90f2f7ef1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketProperties.java @@ -73,6 +73,8 @@ public static class Server { @NestedConfigurationProperty private Ssl ssl; + private final Spec spec = new Spec(); + public Integer getPort() { return this.port; } @@ -121,6 +123,66 @@ public void setSsl(Ssl ssl) { this.ssl = ssl; } + public Spec getSpec() { + return this.spec; + } + + public static class Spec { + + /** + * Sub-protocols to use in websocket handshake signature. + */ + private String protocols; + + /** + * Maximum allowable frame payload length. + */ + private DataSize maxFramePayloadLength = DataSize.ofBytes(65536); + + /** + * Whether to proxy websocket ping frames or respond to them. + */ + private boolean handlePing; + + /** + * Whether the websocket compression extension is enabled. + */ + private boolean compress; + + public String getProtocols() { + return this.protocols; + } + + public void setProtocols(String protocols) { + this.protocols = protocols; + } + + public DataSize getMaxFramePayloadLength() { + return this.maxFramePayloadLength; + } + + public void setMaxFramePayloadLength(DataSize maxFramePayloadLength) { + this.maxFramePayloadLength = maxFramePayloadLength; + } + + public boolean isHandlePing() { + return this.handlePing; + } + + public void setHandlePing(boolean handlePing) { + this.handlePing = handlePing; + } + + public boolean isCompress() { + return this.compress; + } + + public void setCompress(boolean compress) { + this.compress = compress; + } + + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfiguration.java index 96bbbc454ba2..c9ae1d033847 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfiguration.java @@ -16,10 +16,13 @@ package org.springframework.boot.autoconfigure.rsocket; +import java.util.function.Consumer; + import io.rsocket.core.RSocketServer; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.transport.netty.server.TcpServerTransport; import reactor.netty.http.server.HttpServer; +import reactor.netty.http.server.WebsocketServerSpec.Builder; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -31,6 +34,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.reactor.netty.ReactorNettyConfigurations; +import org.springframework.boot.autoconfigure.rsocket.RSocketProperties.Server.Spec; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.rsocket.context.RSocketServerBootstrap; @@ -43,9 +47,10 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.io.buffer.NettyDataBufferFactory; -import org.springframework.http.client.reactive.ReactorResourceFactory; +import org.springframework.http.client.ReactorResourceFactory; import org.springframework.messaging.rsocket.RSocketStrategies; import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; +import org.springframework.util.unit.DataSize; /** * {@link EnableAutoConfiguration Auto-configuration} for RSocket servers. In the case of @@ -73,7 +78,18 @@ static class WebFluxServerConfiguration { RSocketWebSocketNettyRouteProvider rSocketWebsocketRouteProvider(RSocketProperties properties, RSocketMessageHandler messageHandler, ObjectProvider customizers) { return new RSocketWebSocketNettyRouteProvider(properties.getServer().getMappingPath(), - messageHandler.responder(), customizers.orderedStream()); + messageHandler.responder(), customizeWebsocketServerSpec(properties.getServer().getSpec()), + customizers.orderedStream()); + } + + private Consumer customizeWebsocketServerSpec(Spec spec) { + return (builder) -> { + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(spec.getProtocols()).to(builder::protocols); + map.from(spec.getMaxFramePayloadLength()).asInt(DataSize::toBytes).to(builder::maxFramePayloadLength); + map.from(spec.isHandlePing()).to(builder::handlePing); + map.from(spec.isCompress()).to(builder::compress); + }; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketWebSocketNettyRouteProvider.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketWebSocketNettyRouteProvider.java index 5cb8d7f374f5..f70f9d41d546 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketWebSocketNettyRouteProvider.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketWebSocketNettyRouteProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.rsocket; import java.util.List; +import java.util.function.Consumer; import java.util.stream.Stream; import io.rsocket.SocketAcceptor; @@ -24,6 +25,8 @@ import io.rsocket.transport.ServerTransport; import io.rsocket.transport.netty.server.WebsocketRouteTransport; import reactor.netty.http.server.HttpServerRoutes; +import reactor.netty.http.server.WebsocketServerSpec; +import reactor.netty.http.server.WebsocketServerSpec.Builder; import org.springframework.boot.rsocket.server.RSocketServerCustomizer; import org.springframework.boot.web.embedded.netty.NettyRouteProvider; @@ -32,6 +35,7 @@ * {@link NettyRouteProvider} that configures an RSocket Websocket endpoint. * * @author Brian Clozel + * @author Leo Li */ class RSocketWebSocketNettyRouteProvider implements NettyRouteProvider { @@ -41,10 +45,13 @@ class RSocketWebSocketNettyRouteProvider implements NettyRouteProvider { private final List customizers; + private final Consumer serverSpecCustomizer; + RSocketWebSocketNettyRouteProvider(String mappingPath, SocketAcceptor socketAcceptor, - Stream customizers) { + Consumer serverSpecCustomizer, Stream customizers) { this.mappingPath = mappingPath; this.socketAcceptor = socketAcceptor; + this.serverSpecCustomizer = serverSpecCustomizer; this.customizers = customizers.toList(); } @@ -53,7 +60,14 @@ public HttpServerRoutes apply(HttpServerRoutes httpServerRoutes) { RSocketServer server = RSocketServer.create(this.socketAcceptor); this.customizers.forEach((customizer) -> customizer.customize(server)); ServerTransport.ConnectionAcceptor connectionAcceptor = server.asConnectionAcceptor(); - return httpServerRoutes.ws(this.mappingPath, WebsocketRouteTransport.newHandler(connectionAcceptor)); + return httpServerRoutes.ws(this.mappingPath, WebsocketRouteTransport.newHandler(connectionAcceptor), + createWebsocketServerSpec()); + } + + private WebsocketServerSpec createWebsocketServerSpec() { + WebsocketServerSpec.Builder builder = WebsocketServerSpec.builder(); + this.serverSpecCustomizer.accept(builder); + return builder.build(); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapter.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapter.java deleted file mode 100644 index 343f511caf87..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapter.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.security.oauth2.client; - -import java.util.Map; - -import org.springframework.security.oauth2.client.registration.ClientRegistration; - -/** - * Adapter class to convert {@link OAuth2ClientProperties} to a - * {@link ClientRegistration}. - * - * @author Phillip Webb - * @author Thiago Hirata - * @author Madhura Bhave - * @author MyeongHyeon Lee - * @since 2.1.0 - * @deprecated since 3.1.0 for removal in 3.3.0 in favor of - * {@link OAuth2ClientPropertiesMapper} - */ -@Deprecated(since = "3.1.0", forRemoval = true) -public final class OAuth2ClientPropertiesRegistrationAdapter { - - private OAuth2ClientPropertiesRegistrationAdapter() { - } - - public static Map getClientRegistrations(OAuth2ClientProperties properties) { - return new OAuth2ClientPropertiesMapper(properties).asClientRegistrations(); - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerProperties.java index 61c478793220..f974fbbacfe2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ * @author Madhura Bhave * @author Artsiom Yudovin * @author Mushtaq Ahmed + * @author Yan Kardziyaka * @since 2.1.0 */ @ConfigurationProperties(prefix = "spring.security.oauth2.resourceserver") @@ -80,6 +81,26 @@ public static class Jwt { */ private List audiences = new ArrayList<>(); + /** + * Prefix to use for authorities mapped from JWT. + */ + private String authorityPrefix; + + /** + * Regex to use for splitting the value of the authorities claim into authorities. + */ + private String authoritiesClaimDelimiter; + + /** + * Name of token claim to use for mapping authorities from JWT. + */ + private String authoritiesClaimName; + + /** + * JWT principal claim name. + */ + private String principalClaimName; + public String getJwkSetUri() { return this.jwkSetUri; } @@ -120,6 +141,38 @@ public void setAudiences(List audiences) { this.audiences = audiences; } + public String getAuthorityPrefix() { + return this.authorityPrefix; + } + + public void setAuthorityPrefix(String authorityPrefix) { + this.authorityPrefix = authorityPrefix; + } + + public String getAuthoritiesClaimDelimiter() { + return this.authoritiesClaimDelimiter; + } + + public void setAuthoritiesClaimDelimiter(String authoritiesClaimDelimiter) { + this.authoritiesClaimDelimiter = authoritiesClaimDelimiter; + } + + public String getAuthoritiesClaimName() { + return this.authoritiesClaimName; + } + + public void setAuthoritiesClaimName(String authoritiesClaimName) { + this.authoritiesClaimName = authoritiesClaimName; + } + + public String getPrincipalClaimName() { + return this.principalClaimName; + } + + public void setPrincipalClaimName(String principalClaimName) { + this.principalClaimName = principalClaimName; + } + public String readPublicKey() throws IOException { String key = "spring.security.oauth2.resourceserver.public-key-location"; Assert.notNull(this.publicKeyLocation, "PublicKeyLocation must not be null"); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerConfiguration.java index d4f5388f041d..7429a8bc405c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ class ReactiveOAuth2ResourceServerConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ BearerTokenAuthenticationToken.class, ReactiveJwtDecoder.class }) @Import({ ReactiveOAuth2ResourceServerJwkConfiguration.JwtConfiguration.class, + ReactiveOAuth2ResourceServerJwkConfiguration.JwtConverterConfiguration.class, ReactiveOAuth2ResourceServerJwkConfiguration.WebSecurityConfiguration.class }) static class JwtConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java index 3b2546865b71..5cd528044e47 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,15 +24,16 @@ import java.util.Collections; import java.util.List; import java.util.Set; -import java.util.function.Supplier; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.security.oauth2.resource.IssuerUriCondition; import org.springframework.boot.autoconfigure.security.oauth2.resource.KeyValueCondition; import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; +import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @@ -49,6 +50,9 @@ import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.SupplierReactiveJwtDecoder; +import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; +import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter; +import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtGrantedAuthoritiesConverterAdapter; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.util.CollectionUtils; @@ -62,6 +66,8 @@ * @author HaiTao Zhang * @author Anastasiia Losieva * @author Mushtaq Ahmed + * @author Roman Golovin + * @author Yan Kardziyaka */ @Configuration(proxyBeanMethods = false) class ReactiveOAuth2ResourceServerJwkConfiguration { @@ -72,8 +78,12 @@ static class JwtConfiguration { private final OAuth2ResourceServerProperties.Jwt properties; - JwtConfiguration(OAuth2ResourceServerProperties properties) { + private final List> additionalValidators; + + JwtConfiguration(OAuth2ResourceServerProperties properties, + ObjectProvider> additionalValidators) { this.properties = properties.getJwt(); + this.additionalValidators = additionalValidators.orderedStream().toList(); } @Bean @@ -85,8 +95,8 @@ ReactiveJwtDecoder jwtDecoder(ObjectProvider customizer.customize(builder)); NimbusReactiveJwtDecoder nimbusReactiveJwtDecoder = builder.build(); String issuerUri = this.properties.getIssuerUri(); - Supplier> defaultValidator = (issuerUri != null) - ? () -> JwtValidators.createDefaultWithIssuer(issuerUri) : JwtValidators::createDefault; + OAuth2TokenValidator defaultValidator = (issuerUri != null) + ? JwtValidators.createDefaultWithIssuer(issuerUri) : JwtValidators.createDefault(); nimbusReactiveJwtDecoder.setJwtValidator(getValidators(defaultValidator)); return nimbusReactiveJwtDecoder; } @@ -97,16 +107,18 @@ private void jwsAlgorithms(Set signatureAlgorithms) { } } - private OAuth2TokenValidator getValidators(Supplier> defaultValidator) { - OAuth2TokenValidator defaultValidators = defaultValidator.get(); + private OAuth2TokenValidator getValidators(OAuth2TokenValidator defaultValidator) { List audiences = this.properties.getAudiences(); - if (CollectionUtils.isEmpty(audiences)) { - return defaultValidators; + if (CollectionUtils.isEmpty(audiences) && this.additionalValidators.isEmpty()) { + return defaultValidator; } List> validators = new ArrayList<>(); - validators.add(defaultValidators); - validators.add(new JwtClaimValidator>(JwtClaimNames.AUD, - (aud) -> aud != null && !Collections.disjoint(aud, audiences))); + validators.add(defaultValidator); + if (!CollectionUtils.isEmpty(audiences)) { + validators.add(new JwtClaimValidator>(JwtClaimNames.AUD, + (aud) -> aud != null && !Collections.disjoint(aud, audiences))); + } + validators.addAll(this.additionalValidators); return new DelegatingOAuth2TokenValidator<>(validators); } @@ -118,7 +130,7 @@ NimbusReactiveJwtDecoder jwtDecoderByPublicKeyValue() throws Exception { NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withPublicKey(publicKey) .signatureAlgorithm(SignatureAlgorithm.from(exactlyOneAlgorithm())) .build(); - jwtDecoder.setJwtValidator(getValidators(JwtValidators::createDefault)); + jwtDecoder.setJwtValidator(getValidators(JwtValidators.createDefault())); return jwtDecoder; } @@ -148,13 +160,42 @@ SupplierReactiveJwtDecoder jwtDecoderByIssuerUri( customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); NimbusReactiveJwtDecoder jwtDecoder = builder.build(); jwtDecoder.setJwtValidator( - getValidators(() -> JwtValidators.createDefaultWithIssuer(this.properties.getIssuerUri()))); + getValidators(JwtValidators.createDefaultWithIssuer(this.properties.getIssuerUri()))); return jwtDecoder; }); } } + @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingBean(ReactiveJwtAuthenticationConverter.class) + @Conditional(JwtConverterPropertiesCondition.class) + static class JwtConverterConfiguration { + + private final OAuth2ResourceServerProperties.Jwt properties; + + JwtConverterConfiguration(OAuth2ResourceServerProperties properties) { + this.properties = properties.getJwt(); + } + + @Bean + ReactiveJwtAuthenticationConverter reactiveJwtAuthenticationConverter() { + JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(this.properties.getAuthorityPrefix()).to(grantedAuthoritiesConverter::setAuthorityPrefix); + map.from(this.properties.getAuthoritiesClaimDelimiter()) + .to(grantedAuthoritiesConverter::setAuthoritiesClaimDelimiter); + map.from(this.properties.getAuthoritiesClaimName()) + .to(grantedAuthoritiesConverter::setAuthoritiesClaimName); + ReactiveJwtAuthenticationConverter jwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter(); + map.from(this.properties.getPrincipalClaimName()).to(jwtAuthenticationConverter::setPrincipalClaimName); + jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter( + new ReactiveJwtGrantedAuthoritiesConverterAdapter(grantedAuthoritiesConverter)); + return jwtAuthenticationConverter; + } + + } + @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(SecurityWebFilterChain.class) static class WebSecurityConfiguration { @@ -173,4 +214,27 @@ private void customDecoder(OAuth2ResourceServerSpec server, ReactiveJwtDecoder d } + private static class JwtConverterPropertiesCondition extends AnyNestedCondition { + + JwtConverterPropertiesCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnProperty(prefix = "spring.security.oauth2.resourceserver.jwt", name = "authority-prefix") + static class OnAuthorityPrefix { + + } + + @ConditionalOnProperty(prefix = "spring.security.oauth2.resourceserver.jwt", name = "principal-claim-name") + static class OnPrincipalClaimName { + + } + + @ConditionalOnProperty(prefix = "spring.security.oauth2.resourceserver.jwt", name = "authorities-claim-name") + static class OnAuthoritiesClaimName { + + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java index 5146570a28d0..c3e20f9841a6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,9 +24,9 @@ import java.util.Collections; import java.util.List; import java.util.Set; -import java.util.function.Supplier; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -34,6 +34,7 @@ import org.springframework.boot.autoconfigure.security.oauth2.resource.IssuerUriCondition; import org.springframework.boot.autoconfigure.security.oauth2.resource.KeyValueCondition; import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; +import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @@ -49,6 +50,8 @@ import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder; import org.springframework.security.oauth2.jwt.SupplierJwtDecoder; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import org.springframework.security.web.SecurityFilterChain; import org.springframework.util.CollectionUtils; @@ -63,6 +66,8 @@ * @author Artsiom Yudovin * @author HaiTao Zhang * @author Mushtaq Ahmed + * @author Roman Golovin + * @author Yan Kardziyaka */ @Configuration(proxyBeanMethods = false) class OAuth2ResourceServerJwtConfiguration { @@ -73,8 +78,12 @@ static class JwtDecoderConfiguration { private final OAuth2ResourceServerProperties.Jwt properties; - JwtDecoderConfiguration(OAuth2ResourceServerProperties properties) { + private final List> additionalValidators; + + JwtDecoderConfiguration(OAuth2ResourceServerProperties properties, + ObjectProvider> additionalValidators) { this.properties = properties.getJwt(); + this.additionalValidators = additionalValidators.orderedStream().toList(); } @Bean @@ -85,8 +94,8 @@ JwtDecoder jwtDecoderByJwkKeySetUri(ObjectProvider customizer.customize(builder)); NimbusJwtDecoder nimbusJwtDecoder = builder.build(); String issuerUri = this.properties.getIssuerUri(); - Supplier> defaultValidator = (issuerUri != null) - ? () -> JwtValidators.createDefaultWithIssuer(issuerUri) : JwtValidators::createDefault; + OAuth2TokenValidator defaultValidator = (issuerUri != null) + ? JwtValidators.createDefaultWithIssuer(issuerUri) : JwtValidators.createDefault(); nimbusJwtDecoder.setJwtValidator(getValidators(defaultValidator)); return nimbusJwtDecoder; } @@ -97,16 +106,18 @@ private void jwsAlgorithms(Set signatureAlgorithms) { } } - private OAuth2TokenValidator getValidators(Supplier> defaultValidator) { - OAuth2TokenValidator defaultValidators = defaultValidator.get(); + private OAuth2TokenValidator getValidators(OAuth2TokenValidator defaultValidator) { List audiences = this.properties.getAudiences(); - if (CollectionUtils.isEmpty(audiences)) { - return defaultValidators; + if (CollectionUtils.isEmpty(audiences) && this.additionalValidators.isEmpty()) { + return defaultValidator; } List> validators = new ArrayList<>(); - validators.add(defaultValidators); - validators.add(new JwtClaimValidator>(JwtClaimNames.AUD, - (aud) -> aud != null && !Collections.disjoint(aud, audiences))); + validators.add(defaultValidator); + if (!CollectionUtils.isEmpty(audiences)) { + validators.add(new JwtClaimValidator>(JwtClaimNames.AUD, + (aud) -> aud != null && !Collections.disjoint(aud, audiences))); + } + validators.addAll(this.additionalValidators); return new DelegatingOAuth2TokenValidator<>(validators); } @@ -118,7 +129,7 @@ JwtDecoder jwtDecoderByPublicKeyValue() throws Exception { NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(publicKey) .signatureAlgorithm(SignatureAlgorithm.from(exactlyOneAlgorithm())) .build(); - jwtDecoder.setJwtValidator(getValidators(JwtValidators::createDefault)); + jwtDecoder.setJwtValidator(getValidators(JwtValidators.createDefault())); return jwtDecoder; } @@ -146,7 +157,7 @@ SupplierJwtDecoder jwtDecoderByIssuerUri(ObjectProvider customizer.customize(builder)); NimbusJwtDecoder jwtDecoder = builder.build(); - jwtDecoder.setJwtValidator(getValidators(() -> JwtValidators.createDefaultWithIssuer(issuerUri))); + jwtDecoder.setJwtValidator(getValidators(JwtValidators.createDefaultWithIssuer(issuerUri))); return jwtDecoder; }); } @@ -167,4 +178,55 @@ SecurityFilterChain jwtSecurityFilterChain(HttpSecurity http) throws Exception { } + @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingBean(JwtAuthenticationConverter.class) + @Conditional(JwtConverterPropertiesCondition.class) + static class JwtConverterConfiguration { + + private final OAuth2ResourceServerProperties.Jwt properties; + + JwtConverterConfiguration(OAuth2ResourceServerProperties properties) { + this.properties = properties.getJwt(); + } + + @Bean + JwtAuthenticationConverter getJwtAuthenticationConverter() { + JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(this.properties.getAuthorityPrefix()).to(grantedAuthoritiesConverter::setAuthorityPrefix); + map.from(this.properties.getAuthoritiesClaimDelimiter()) + .to(grantedAuthoritiesConverter::setAuthoritiesClaimDelimiter); + map.from(this.properties.getAuthoritiesClaimName()) + .to(grantedAuthoritiesConverter::setAuthoritiesClaimName); + JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); + map.from(this.properties.getPrincipalClaimName()).to(jwtAuthenticationConverter::setPrincipalClaimName); + jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); + return jwtAuthenticationConverter; + } + + } + + private static class JwtConverterPropertiesCondition extends AnyNestedCondition { + + JwtConverterPropertiesCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnProperty(prefix = "spring.security.oauth2.resourceserver.jwt", name = "authority-prefix") + static class OnAuthorityPrefix { + + } + + @ConditionalOnProperty(prefix = "spring.security.oauth2.resourceserver.jwt", name = "principal-claim-name") + static class OnPrincipalClaimName { + + } + + @ConditionalOnProperty(prefix = "spring.security.oauth2.resourceserver.jwt", name = "authorities-claim-name") + static class OnAuthoritiesClaimName { + + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/Oauth2ResourceServerConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/Oauth2ResourceServerConfiguration.java index 36c522e39a7b..6a323bdd6e27 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/Oauth2ResourceServerConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/Oauth2ResourceServerConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,8 @@ class Oauth2ResourceServerConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnClass(JwtDecoder.class) - @Import({ OAuth2ResourceServerJwtConfiguration.JwtDecoderConfiguration.class, + @Import({ OAuth2ResourceServerJwtConfiguration.JwtConverterConfiguration.class, + OAuth2ResourceServerJwtConfiguration.JwtDecoderConfiguration.class, OAuth2ResourceServerJwtConfiguration.OAuth2SecurityFilterChainConfiguration.class }) static class JwtConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerProperties.java index 196afbdc75fe..fd7ceba3c338 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,13 @@ public class OAuth2AuthorizationServerProperties implements InitializingBean { */ private String issuer; + /** + * Whether multiple issuers are allowed per host. Using path components in the URL of + * the issuer identifier enables supporting multiple issuers per host in a + * multi-tenant hosting configuration. + */ + private boolean multipleIssuersAllowed = false; + /** * Registered clients of the Authorization Server. */ @@ -52,6 +59,14 @@ public class OAuth2AuthorizationServerProperties implements InitializingBean { */ private final Endpoint endpoint = new Endpoint(); + public boolean isMultipleIssuersAllowed() { + return this.multipleIssuersAllowed; + } + + public void setMultipleIssuersAllowed(boolean multipleIssuersAllowed) { + this.multipleIssuersAllowed = multipleIssuersAllowed; + } + public String getIssuer() { return this.issuer; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapper.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapper.java index e53d587e192c..58083756c537 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapper.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,6 +52,7 @@ AuthorizationServerSettings asAuthorizationServerSettings() { OAuth2AuthorizationServerProperties.OidcEndpoint oidc = endpoint.getOidc(); AuthorizationServerSettings.Builder builder = AuthorizationServerSettings.builder(); map.from(this.properties::getIssuer).to(builder::issuer); + map.from(this.properties::isMultipleIssuersAllowed).to(builder::multipleIssuersAllowed); map.from(endpoint::getAuthorizationUri).to(builder::authorizationEndpoint); map.from(endpoint::getDeviceAuthorizationUri).to(builder::deviceAuthorizationEndpoint); map.from(endpoint::getDeviceVerificationUri).to(builder::deviceVerificationEndpoint); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveSecurityAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveSecurityAutoConfiguration.java index f995f66cdd69..9e07d8f9b98a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveSecurityAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveSecurityAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.security.reactive; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -25,8 +26,13 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.core.userdetails.ReactiveUserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.web.reactive.config.WebFluxConfigurer; @@ -46,11 +52,23 @@ @ConditionalOnClass({ Flux.class, EnableWebFluxSecurity.class, WebFilterChainProxy.class, WebFluxConfigurer.class }) public class ReactiveSecurityAutoConfiguration { - @Configuration(proxyBeanMethods = false) - @ConditionalOnMissingBean(WebFilterChainProxy.class) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) - @EnableWebFluxSecurity - static class EnableWebFluxSecurityConfiguration { + @Configuration(proxyBeanMethods = false) + class SpringBootWebFluxSecurityConfiguration { + + @Bean + @ConditionalOnMissingBean({ ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class, + SecurityWebFilterChain.class }) + ReactiveAuthenticationManager denyAllAuthenticationManager() { + return (authentication) -> Mono.error(new UsernameNotFoundException(authentication.getName())); + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingBean(WebFilterChainProxy.class) + @EnableWebFluxSecurity + static class EnableWebFluxSecurityConfiguration { + + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfiguration.java index c2f4ce2a7323..596f0d9c0b9d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration; import org.springframework.boot.autoconfigure.security.SecurityProperties; @@ -55,14 +57,14 @@ * @author Madhura Bhave * @since 2.0.0 */ -@AutoConfiguration(after = RSocketMessagingAutoConfiguration.class) +@AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class, after = RSocketMessagingAutoConfiguration.class) @ConditionalOnClass({ ReactiveAuthenticationManager.class }) @ConditionalOnMissingBean( value = { ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class, ReactiveAuthenticationManagerResolver.class }, - type = { "org.springframework.security.oauth2.jwt.ReactiveJwtDecoder", - "org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector" }) -@Conditional(ReactiveUserDetailsServiceAutoConfiguration.ReactiveUserDetailsServiceCondition.class) + type = { "org.springframework.security.oauth2.jwt.ReactiveJwtDecoder" }) +@Conditional({ ReactiveUserDetailsServiceAutoConfiguration.RSocketEnabledOrReactiveWebApplication.class, + ReactiveUserDetailsServiceAutoConfiguration.MissingAlternativeOrUserPropertiesConfigured.class }) @EnableConfigurationProperties(SecurityProperties.class) public class ReactiveUserDetailsServiceAutoConfiguration { @@ -96,9 +98,9 @@ private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder return NOOP_PASSWORD_PREFIX + password; } - static class ReactiveUserDetailsServiceCondition extends AnyNestedCondition { + static class RSocketEnabledOrReactiveWebApplication extends AnyNestedCondition { - ReactiveUserDetailsServiceCondition() { + RSocketEnabledOrReactiveWebApplication() { super(ConfigurationPhase.REGISTER_BEAN); } @@ -114,4 +116,29 @@ static class ReactiveWebApplicationCondition { } + static final class MissingAlternativeOrUserPropertiesConfigured extends AnyNestedCondition { + + MissingAlternativeOrUserPropertiesConfigured() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @ConditionalOnMissingClass({ + "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository", + "org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector" }) + static final class MissingAlternative { + + } + + @ConditionalOnProperty(prefix = "spring.security.user", name = "name") + static final class NameConfigured { + + } + + @ConditionalOnProperty(prefix = "spring.security.user", name = "password") + static final class PasswordConfigured { + + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyProperties.java index 8898587a46b1..5b31dd9961d8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ * @author Madhura Bhave * @author Phillip Webb * @author Moritz Halbritter + * @author Lasse Wulff * @since 2.2.0 */ @ConfigurationProperties("spring.security.saml2.relyingparty") @@ -72,6 +73,11 @@ public static class Registration { */ private final AssertingParty assertingparty = new AssertingParty(); + /** + * Name ID format for a relying party registration. + */ + private String nameIdFormat; + public String getEntityId() { return this.entityId; } @@ -92,12 +98,20 @@ public Decryption getDecryption() { return this.decryption; } + public Singlelogout getSinglelogout() { + return this.singlelogout; + } + public AssertingParty getAssertingparty() { return this.assertingparty; } - public Singlelogout getSinglelogout() { - return this.singlelogout; + public String getNameIdFormat() { + return this.nameIdFormat; + } + + public void setNameIdFormat(String nameIdFormat) { + this.nameIdFormat = nameIdFormat; } public static class Acs { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyRegistrationConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyRegistrationConfiguration.java index 830077fae5b9..37896de47413 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyRegistrationConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyRegistrationConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -56,6 +56,7 @@ * @author Phillip Webb * @author Moritz Halbritter * @author Lasse Lindqvist + * @author Lasse Wulff */ @Configuration(proxyBeanMethods = false) @Conditional(RegistrationConfiguredCondition.class) @@ -104,6 +105,7 @@ private RelyingPartyRegistration asRegistration(String id, Registration properti builder.singleLogoutServiceResponseLocation(properties.getSinglelogout().getResponseUrl()); builder.singleLogoutServiceBinding(properties.getSinglelogout().getBinding()); builder.entityId(properties.getEntityId()); + builder.nameIdFormat(properties.getNameIdFormat()); RelyingPartyRegistration registration = builder.build(); boolean signRequest = registration.getAssertingPartyDetails().getWantAuthnRequestsSigned(); validateSigningCredentials(properties, signRequest); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfiguration.java index 55c3dec9a6a7..9c4fef69ea0a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,11 +25,16 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.security.SecurityProperties; +import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration.MissingAlternativeOrUserPropertiesConfigured; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManagerResolver; import org.springframework.security.authentication.AuthenticationProvider; @@ -43,9 +48,7 @@ /** * {@link EnableAutoConfiguration Auto-configuration} for a Spring Security in-memory * {@link AuthenticationManager}. Adds an {@link InMemoryUserDetailsManager} with a - * default user and generated password. This can be disabled by providing a bean of type - * {@link AuthenticationManager}, {@link AuthenticationProvider} or - * {@link UserDetailsService}. + * default user and generated password. * * @author Dave Syer * @author Rob Winch @@ -54,14 +57,10 @@ */ @AutoConfiguration @ConditionalOnClass(AuthenticationManager.class) +@Conditional(MissingAlternativeOrUserPropertiesConfigured.class) @ConditionalOnBean(ObjectPostProcessor.class) -@ConditionalOnMissingBean( - value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class, - AuthenticationManagerResolver.class }, - type = { "org.springframework.security.oauth2.jwt.JwtDecoder", - "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector", - "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository", - "org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository" }) +@ConditionalOnMissingBean(value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class, + AuthenticationManagerResolver.class }, type = "org.springframework.security.oauth2.jwt.JwtDecoder") public class UserDetailsServiceAutoConfiguration { private static final String NOOP_PASSWORD_PREFIX = "{noop}"; @@ -96,4 +95,30 @@ private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder return NOOP_PASSWORD_PREFIX + password; } + static final class MissingAlternativeOrUserPropertiesConfigured extends AnyNestedCondition { + + MissingAlternativeOrUserPropertiesConfigured() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @ConditionalOnMissingClass({ + "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository", + "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector", + "org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository" }) + static final class MissingAlternative { + + } + + @ConditionalOnProperty(prefix = "spring.security.user", name = "name") + static final class NameConfigured { + + } + + @ConditionalOnProperty(prefix = "spring.security.user", name = "password") + static final class PasswordConfigured { + + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java index 4d6329462831..b5e36068a8a3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java @@ -41,8 +41,8 @@ import org.springframework.boot.autoconfigure.web.reactive.WebSessionIdResolverAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.web.server.Cookie; import org.springframework.boot.web.server.Cookie.SameSite; -import org.springframework.boot.web.servlet.server.Session.Cookie; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/BundleContentNotWatchableException.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/BundleContentNotWatchableException.java new file mode 100644 index 000000000000..71b1787cefcb --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/BundleContentNotWatchableException.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.ssl; + +/** + * Thrown when a bundle content location is not watchable. + * + * @author Moritz Halbritter + */ +class BundleContentNotWatchableException extends RuntimeException { + + private final BundleContentProperty property; + + BundleContentNotWatchableException(BundleContentProperty property) { + super("The content of '%s' is not watchable. Only 'file:' resources are watchable, but '%s' has been set" + .formatted(property.name(), property.value())); + this.property = property; + } + + private BundleContentNotWatchableException(String bundleName, BundleContentProperty property, Throwable cause) { + super("The content of '%s' from bundle '%s' is not watchable'. Only 'file:' resources are watchable, but '%s' has been set" + .formatted(property.name(), bundleName, property.value()), cause); + this.property = property; + } + + BundleContentNotWatchableException withBundleName(String bundleName) { + return new BundleContentNotWatchableException(bundleName, this.property, this); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/BundleContentNotWatchableFailureAnalyzer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/BundleContentNotWatchableFailureAnalyzer.java new file mode 100644 index 000000000000..a41d9eabb3a3 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/BundleContentNotWatchableFailureAnalyzer.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.ssl; + +import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; +import org.springframework.boot.diagnostics.FailureAnalysis; + +/** + * An {@link AbstractFailureAnalyzer} that performs analysis of non-watchable bundle + * content failures caused by {@link BundleContentNotWatchableException}. + * + * @author Moritz Halbritter + */ +class BundleContentNotWatchableFailureAnalyzer extends AbstractFailureAnalyzer { + + @Override + protected FailureAnalysis analyze(Throwable rootFailure, BundleContentNotWatchableException cause) { + return new FailureAnalysis(cause.getMessage(), "Update your application to correct the invalid configuration:\n" + + "Either use a watchable resource, or disable bundle reloading by setting reload-on-update = false on the bundle.", + cause); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/BundleContentProperty.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/BundleContentProperty.java new file mode 100644 index 000000000000..1a15a67ad3fc --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/BundleContentProperty.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.ssl; + +import java.nio.file.Path; + +import org.springframework.boot.io.ApplicationResourceLoader; +import org.springframework.boot.ssl.pem.PemContent; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Helper utility to manage a single bundle content configuration property. May possibly + * contain PEM content, a location or a directory search pattern. + * + * @param name the configuration property name (excluding any prefix) + * @param value the configuration property value + * @author Phillip Webb + * @author Moritz Halbritter + */ +record BundleContentProperty(String name, String value) { + + /** + * Return if the property value is PEM content. + * @return if the value is PEM content + */ + boolean isPemContent() { + return PemContent.isPresentInText(this.value); + } + + /** + * Return if there is any property value present. + * @return if the value is present + */ + boolean hasValue() { + return StringUtils.hasText(this.value); + } + + Path toWatchPath() { + try { + Resource resource = getResource(); + if (!resource.isFile()) { + throw new BundleContentNotWatchableException(this); + } + return Path.of(resource.getFile().getAbsolutePath()); + } + catch (Exception ex) { + if (ex instanceof BundleContentNotWatchableException bundleContentNotWatchableException) { + throw bundleContentNotWatchableException; + } + throw new IllegalStateException("Unable to convert value of property '%s' to a path".formatted(this.name), + ex); + } + } + + private Resource getResource() { + Assert.state(!isPemContent(), "Value contains PEM content"); + return new ApplicationResourceLoader().getResource(this.value); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/CertificateMatcher.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/CertificateMatcher.java new file mode 100644 index 000000000000..3f25ecc2c0c4 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/CertificateMatcher.java @@ -0,0 +1,117 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.ssl; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.cert.Certificate; +import java.util.List; +import java.util.Objects; + +import org.springframework.util.Assert; + +/** + * Helper used to match certificates against a {@link PrivateKey}. + * + * @author Moritz Halbritter + * @author Phillip Webb + */ +class CertificateMatcher { + + private static final byte[] DATA = new byte[256]; + static { + for (int i = 0; i < DATA.length; i++) { + DATA[i] = (byte) i; + } + } + + private final PrivateKey privateKey; + + private final Signature signature; + + private final byte[] generatedSignature; + + CertificateMatcher(PrivateKey privateKey) { + Assert.notNull(privateKey, "Private key must not be null"); + this.privateKey = privateKey; + this.signature = createSignature(privateKey); + Assert.notNull(this.signature, "Failed to create signature"); + this.generatedSignature = sign(this.signature, privateKey); + } + + private Signature createSignature(PrivateKey privateKey) { + try { + String algorithm = getSignatureAlgorithm(privateKey); + return (algorithm != null) ? Signature.getInstance(algorithm) : null; + } + catch (NoSuchAlgorithmException ex) { + return null; + } + } + + private static String getSignatureAlgorithm(PrivateKey privateKey) { + // https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#signature-algorithms + // https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#keypairgenerator-algorithms + return switch (privateKey.getAlgorithm()) { + case "RSA" -> "SHA256withRSA"; + case "DSA" -> "SHA256withDSA"; + case "EC" -> "SHA256withECDSA"; + case "EdDSA" -> "EdDSA"; + default -> null; + }; + } + + boolean matchesAny(List certificates) { + return (this.generatedSignature != null) && certificates.stream().anyMatch(this::matches); + } + + boolean matches(Certificate certificate) { + return matches(certificate.getPublicKey()); + } + + private boolean matches(PublicKey publicKey) { + return (this.generatedSignature != null) + && Objects.equals(this.privateKey.getAlgorithm(), publicKey.getAlgorithm()) && verify(publicKey); + } + + private boolean verify(PublicKey publicKey) { + try { + this.signature.initVerify(publicKey); + this.signature.update(DATA); + return this.signature.verify(this.generatedSignature); + } + catch (InvalidKeyException | SignatureException ex) { + return false; + } + } + + private static byte[] sign(Signature signature, PrivateKey privateKey) { + try { + signature.initSign(privateKey); + signature.update(DATA); + return signature.sign(); + } + catch (InvalidKeyException | SignatureException ex) { + return null; + } + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/FileWatcher.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/FileWatcher.java new file mode 100644 index 000000000000..eecad97b3b4e --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/FileWatcher.java @@ -0,0 +1,229 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.ssl; + +import java.io.Closeable; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.ClosedWatchServiceException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.time.Duration; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.core.log.LogMessage; +import org.springframework.util.Assert; + +/** + * Watches files and directories and triggers a callback on change. + * + * @author Moritz Halbritter + * @author Phillip Webb + */ +class FileWatcher implements Closeable { + + private static final Log logger = LogFactory.getLog(FileWatcher.class); + + private final Duration quietPeriod; + + private final Object lock = new Object(); + + private WatcherThread thread; + + /** + * Create a new {@link FileWatcher} instance. + * @param quietPeriod the duration that no file changes should occur before triggering + * actions + */ + FileWatcher(Duration quietPeriod) { + Assert.notNull(quietPeriod, "QuietPeriod must not be null"); + this.quietPeriod = quietPeriod; + } + + /** + * Watch the given files or directories for changes. + * @param paths the files or directories to watch + * @param action the action to take when changes are detected + */ + void watch(Set paths, Runnable action) { + Assert.notNull(paths, "Paths must not be null"); + Assert.notNull(action, "Action must not be null"); + if (paths.isEmpty()) { + return; + } + synchronized (this.lock) { + try { + if (this.thread == null) { + this.thread = new WatcherThread(); + this.thread.start(); + } + this.thread.register(new Registration(paths, action)); + } + catch (IOException ex) { + throw new UncheckedIOException("Failed to register paths for watching: " + paths, ex); + } + } + } + + @Override + public void close() throws IOException { + synchronized (this.lock) { + if (this.thread != null) { + this.thread.close(); + this.thread.interrupt(); + try { + this.thread.join(); + } + catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + this.thread = null; + } + } + } + + /** + * The watcher thread used to check for changes. + */ + private class WatcherThread extends Thread implements Closeable { + + private final WatchService watchService = FileSystems.getDefault().newWatchService(); + + private final Map> registrations = new ConcurrentHashMap<>(); + + private volatile boolean running = true; + + WatcherThread() throws IOException { + setName("ssl-bundle-watcher"); + setDaemon(true); + setUncaughtExceptionHandler(this::onThreadException); + } + + private void onThreadException(Thread thread, Throwable throwable) { + logger.error("Uncaught exception in file watcher thread", throwable); + } + + void register(Registration registration) throws IOException { + for (Path path : registration.paths()) { + if (!Files.isRegularFile(path) && !Files.isDirectory(path)) { + throw new IOException("'%s' is neither a file nor a directory".formatted(path)); + } + Path directory = Files.isDirectory(path) ? path : path.getParent(); + WatchKey watchKey = register(directory); + this.registrations.computeIfAbsent(watchKey, (key) -> new CopyOnWriteArrayList<>()).add(registration); + } + } + + private WatchKey register(Path directory) throws IOException { + logger.debug(LogMessage.format("Registering '%s'", directory)); + return directory.register(this.watchService, StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE); + } + + @Override + public void run() { + logger.debug("Watch thread started"); + Set actions = new HashSet<>(); + while (this.running) { + try { + long timeout = FileWatcher.this.quietPeriod.toMillis(); + WatchKey key = this.watchService.poll(timeout, TimeUnit.MILLISECONDS); + if (key == null) { + actions.forEach(this::runSafely); + actions.clear(); + } + else { + accumulate(key, actions); + key.reset(); + } + } + catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + catch (ClosedWatchServiceException ex) { + logger.debug("File watcher has been closed"); + this.running = false; + } + } + logger.debug("Watch thread stopped"); + } + + private void runSafely(Runnable action) { + try { + action.run(); + } + catch (Throwable ex) { + logger.error("Unexpected SSL reload error", ex); + } + } + + private void accumulate(WatchKey key, Set actions) { + List registrations = this.registrations.get(key); + Path directory = (Path) key.watchable(); + for (WatchEvent event : key.pollEvents()) { + Path file = directory.resolve((Path) event.context()); + for (Registration registration : registrations) { + if (registration.manages(file)) { + actions.add(registration.action()); + } + } + } + } + + @Override + public void close() throws IOException { + this.running = false; + this.watchService.close(); + } + + } + + /** + * An individual watch registration. + */ + private record Registration(Set paths, Runnable action) { + + Registration { + paths = paths.stream().map(Path::toAbsolutePath).collect(Collectors.toSet()); + } + + boolean manages(Path file) { + Path absolutePath = file.toAbsolutePath(); + return this.paths.contains(absolutePath) || isInDirectories(absolutePath); + } + + private boolean isInDirectories(Path file) { + return this.paths.stream().filter(Files::isDirectory).anyMatch(file::startsWith); + } + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PemSslBundleProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PemSslBundleProperties.java index 16798cb2cb05..beb58d87dc91 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PemSslBundleProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PemSslBundleProperties.java @@ -23,6 +23,7 @@ * * @author Scott Frederick * @author Phillip Webb + * @author Moritz Halbritter * @since 3.1.0 * @see PemSslStoreBundle */ @@ -57,7 +58,7 @@ public static class Store { private String type; /** - * Location or content of the certificate in PEM format. + * Location or content of the certificate or certificate chain in PEM format. */ private String certificate; @@ -71,6 +72,11 @@ public static class Store { */ private String privateKeyPassword; + /** + * Whether to verify that the private key matches the public key. + */ + private boolean verifyKeys; + public String getType() { return this.type; } @@ -103,6 +109,14 @@ public void setPrivateKeyPassword(String privateKeyPassword) { this.privateKeyPassword = privateKeyPassword; } + public boolean isVerifyKeys() { + return this.verifyKeys; + } + + public void setVerifyKeys(boolean verifyKeys) { + this.verifyKeys = verifyKeys; + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java index 4222a21609fe..29baa16e69ee 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java @@ -24,9 +24,11 @@ import org.springframework.boot.ssl.SslStoreBundle; import org.springframework.boot.ssl.jks.JksSslStoreBundle; import org.springframework.boot.ssl.jks.JksSslStoreDetails; +import org.springframework.boot.ssl.pem.PemSslStore; import org.springframework.boot.ssl.pem.PemSslStoreBundle; import org.springframework.boot.ssl.pem.PemSslStoreDetails; import org.springframework.core.style.ToStringCreator; +import org.springframework.util.Assert; /** * {@link SslBundle} backed by {@link JksSslBundleProperties} or @@ -95,7 +97,29 @@ public SslManagerBundle getManagers() { * @return an {@link SslBundle} instance */ public static SslBundle get(PemSslBundleProperties properties) { - return new PropertiesSslBundle(asSslStoreBundle(properties), properties); + PemSslStore keyStore = getPemSslStore("keystore", properties.getKeystore()); + if (keyStore != null) { + keyStore = keyStore.withAlias(properties.getKey().getAlias()) + .withPassword(properties.getKey().getPassword()); + } + PemSslStore trustStore = getPemSslStore("truststore", properties.getTruststore()); + SslStoreBundle storeBundle = new PemSslStoreBundle(keyStore, trustStore); + return new PropertiesSslBundle(storeBundle, properties); + } + + private static PemSslStore getPemSslStore(String propertyName, PemSslBundleProperties.Store properties) { + PemSslStore pemSslStore = PemSslStore.load(asPemSslStoreDetails(properties)); + if (properties.isVerifyKeys()) { + CertificateMatcher certificateMatcher = new CertificateMatcher(pemSslStore.privateKey()); + Assert.state(certificateMatcher.matchesAny(pemSslStore.certificates()), + "Private key in %s matches none of the certificates in the chain".formatted(propertyName)); + } + return pemSslStore; + } + + private static PemSslStoreDetails asPemSslStoreDetails(PemSslBundleProperties.Store properties) { + return new PemSslStoreDetails(properties.getType(), properties.getCertificate(), properties.getPrivateKey(), + properties.getPrivateKeyPassword()); } /** @@ -104,18 +128,8 @@ public static SslBundle get(PemSslBundleProperties properties) { * @return an {@link SslBundle} instance */ public static SslBundle get(JksSslBundleProperties properties) { - return new PropertiesSslBundle(asSslStoreBundle(properties), properties); - } - - private static SslStoreBundle asSslStoreBundle(PemSslBundleProperties properties) { - PemSslStoreDetails keyStoreDetails = asStoreDetails(properties.getKeystore()); - PemSslStoreDetails trustStoreDetails = asStoreDetails(properties.getTruststore()); - return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, properties.getKey().getAlias()); - } - - private static PemSslStoreDetails asStoreDetails(PemSslBundleProperties.Store properties) { - return new PemSslStoreDetails(properties.getType(), properties.getCertificate(), properties.getPrivateKey(), - properties.getPrivateKeyPassword()); + SslStoreBundle storeBundle = asSslStoreBundle(properties); + return new PropertiesSslBundle(storeBundle, properties); } private static SslStoreBundle asSslStoreBundle(JksSslBundleProperties properties) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslAutoConfiguration.java index 12b856c8a01d..1348f16b37b8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslAutoConfiguration.java @@ -16,8 +16,7 @@ package org.springframework.boot.autoconfigure.ssl; -import java.util.List; - +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -37,19 +36,27 @@ @EnableConfigurationProperties(SslProperties.class) public class SslAutoConfiguration { - SslAutoConfiguration() { + private final SslProperties sslProperties; + + SslAutoConfiguration(SslProperties sslProperties) { + this.sslProperties = sslProperties; + } + + @Bean + FileWatcher fileWatcher() { + return new FileWatcher(this.sslProperties.getBundle().getWatch().getFile().getQuietPeriod()); } @Bean - public SslPropertiesBundleRegistrar sslPropertiesSslBundleRegistrar(SslProperties sslProperties) { - return new SslPropertiesBundleRegistrar(sslProperties); + SslPropertiesBundleRegistrar sslPropertiesSslBundleRegistrar(FileWatcher fileWatcher) { + return new SslPropertiesBundleRegistrar(this.sslProperties, fileWatcher); } @Bean @ConditionalOnMissingBean({ SslBundleRegistry.class, SslBundles.class }) - public DefaultSslBundleRegistry sslBundleRegistry(List sslBundleRegistrars) { + DefaultSslBundleRegistry sslBundleRegistry(ObjectProvider sslBundleRegistrars) { DefaultSslBundleRegistry registry = new DefaultSslBundleRegistry(); - sslBundleRegistrars.forEach((registrar) -> registrar.registerBundles(registry)); + sslBundleRegistrars.orderedStream().forEach((registrar) -> registrar.registerBundles(registry)); return registry; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslBundleProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslBundleProperties.java index e8b9fd1a4cba..b01201dba07e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslBundleProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslBundleProperties.java @@ -36,7 +36,7 @@ public abstract class SslBundleProperties { private final Key key = new Key(); /** - * Options for the SLL connection. + * Options for the SSL connection. */ private final Options options = new Options(); @@ -45,6 +45,11 @@ public abstract class SslBundleProperties { */ private String protocol = SslBundle.DEFAULT_PROTOCOL; + /** + * Whether to reload the SSL bundle. + */ + private boolean reloadOnUpdate; + public Key getKey() { return this.key; } @@ -61,6 +66,14 @@ public void setProtocol(String protocol) { this.protocol = protocol; } + public boolean isReloadOnUpdate() { + return this.reloadOnUpdate; + } + + public void setReloadOnUpdate(boolean reloadOnUpdate) { + this.reloadOnUpdate = reloadOnUpdate; + } + public static class Options { /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslProperties.java index 49aced749021..a755a871b8d7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslProperties.java @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.ssl; +import java.time.Duration; import java.util.LinkedHashMap; import java.util.Map; @@ -25,6 +26,7 @@ * Properties for centralized SSL trust material configuration. * * @author Scott Frederick + * @author Moritz Halbritter * @since 3.1.0 */ @ConfigurationProperties(prefix = "spring.ssl") @@ -54,6 +56,11 @@ public static class Bundles { */ private final Map jks = new LinkedHashMap<>(); + /** + * Trust material watching. + */ + private final Watch watch = new Watch(); + public Map getPem() { return this.pem; } @@ -62,6 +69,40 @@ public Map getJks() { return this.jks; } + public Watch getWatch() { + return this.watch; + } + + public static class Watch { + + /** + * File watching. + */ + private final File file = new File(); + + public File getFile() { + return this.file; + } + + public static class File { + + /** + * Quiet period, after which changes are detected. + */ + private Duration quietPeriod = Duration.ofSeconds(10); + + public Duration getQuietPeriod() { + return this.quietPeriod; + } + + public void setQuietPeriod(Duration quietPeriod) { + this.quietPeriod = quietPeriod; + } + + } + + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrar.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrar.java index 89a3e7c1265c..c8f595b0d254 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrar.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,14 @@ package org.springframework.boot.autoconfigure.ssl; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslBundleRegistry; @@ -28,25 +34,87 @@ * * @author Scott Frederick * @author Phillip Webb + * @author Moritz Halbritter */ class SslPropertiesBundleRegistrar implements SslBundleRegistrar { private final SslProperties.Bundles properties; - SslPropertiesBundleRegistrar(SslProperties properties) { + private final FileWatcher fileWatcher; + + SslPropertiesBundleRegistrar(SslProperties properties, FileWatcher fileWatcher) { this.properties = properties.getBundle(); + this.fileWatcher = fileWatcher; } @Override public void registerBundles(SslBundleRegistry registry) { - registerBundles(registry, this.properties.getPem(), PropertiesSslBundle::get); - registerBundles(registry, this.properties.getJks(), PropertiesSslBundle::get); + registerBundles(registry, this.properties.getPem(), PropertiesSslBundle::get, this::watchedPemPaths); + registerBundles(registry, this.properties.getJks(), PropertiesSslBundle::get, this::watchedJksPaths); } private

void registerBundles(SslBundleRegistry registry, Map properties, - Function bundleFactory) { - properties.forEach((bundleName, bundleProperties) -> registry.registerBundle(bundleName, - bundleFactory.apply(bundleProperties))); + Function bundleFactory, Function, Set> watchedPaths) { + properties.forEach((bundleName, bundleProperties) -> { + Supplier bundleSupplier = () -> bundleFactory.apply(bundleProperties); + try { + registry.registerBundle(bundleName, bundleSupplier.get()); + if (bundleProperties.isReloadOnUpdate()) { + Supplier> pathsSupplier = () -> watchedPaths + .apply(new Bundle<>(bundleName, bundleProperties)); + watchForUpdates(registry, bundleName, pathsSupplier, bundleSupplier); + } + } + catch (IllegalStateException ex) { + throw new IllegalStateException("Unable to register SSL bundle '%s'".formatted(bundleName), ex); + } + }); + } + + private void watchForUpdates(SslBundleRegistry registry, String bundleName, Supplier> pathsSupplier, + Supplier bundleSupplier) { + try { + this.fileWatcher.watch(pathsSupplier.get(), () -> registry.updateBundle(bundleName, bundleSupplier.get())); + } + catch (RuntimeException ex) { + throw new IllegalStateException("Unable to watch for reload on update", ex); + } + } + + private Set watchedJksPaths(Bundle bundle) { + List watched = new ArrayList<>(); + watched.add(new BundleContentProperty("keystore.location", bundle.properties().getKeystore().getLocation())); + watched + .add(new BundleContentProperty("truststore.location", bundle.properties().getTruststore().getLocation())); + return watchedPaths(bundle.name(), watched); + } + + private Set watchedPemPaths(Bundle bundle) { + List watched = new ArrayList<>(); + watched + .add(new BundleContentProperty("keystore.private-key", bundle.properties().getKeystore().getPrivateKey())); + watched + .add(new BundleContentProperty("keystore.certificate", bundle.properties().getKeystore().getCertificate())); + watched.add(new BundleContentProperty("truststore.private-key", + bundle.properties().getTruststore().getPrivateKey())); + watched.add(new BundleContentProperty("truststore.certificate", + bundle.properties().getTruststore().getCertificate())); + return watchedPaths(bundle.name(), watched); + } + + private Set watchedPaths(String bundleName, List properties) { + try { + return properties.stream() + .filter(BundleContentProperty::hasValue) + .map(BundleContentProperty::toWatchPath) + .collect(Collectors.toSet()); + } + catch (BundleContentNotWatchableException ex) { + throw ex.withBundleName(bundleName); + } + } + + private record Bundle

(String name, P properties) { } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java index 1ebb19871931..b1faadcd8c13 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,22 +16,12 @@ package org.springframework.boot.autoconfigure.task; -import java.util.concurrent.Executor; - -import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.task.TaskExecutionProperties.Shutdown; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.task.TaskExecutorBuilder; -import org.springframework.boot.task.TaskExecutorCustomizer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Lazy; -import org.springframework.core.task.TaskDecorator; +import org.springframework.context.annotation.Import; import org.springframework.core.task.TaskExecutor; -import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; /** @@ -39,11 +29,15 @@ * * @author Stephane Nicoll * @author Camille Vienot + * @author Moritz Halbritter * @since 2.1.0 */ @ConditionalOnClass(ThreadPoolTaskExecutor.class) @AutoConfiguration @EnableConfigurationProperties(TaskExecutionProperties.class) +@Import({ TaskExecutorConfigurations.ThreadPoolTaskExecutorBuilderConfiguration.class, + TaskExecutorConfigurations.SimpleAsyncTaskExecutorBuilderConfiguration.class, + TaskExecutorConfigurations.TaskExecutorConfiguration.class }) public class TaskExecutionAutoConfiguration { /** @@ -51,33 +45,4 @@ public class TaskExecutionAutoConfiguration { */ public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor"; - @Bean - @ConditionalOnMissingBean - public TaskExecutorBuilder taskExecutorBuilder(TaskExecutionProperties properties, - ObjectProvider taskExecutorCustomizers, - ObjectProvider taskDecorator) { - TaskExecutionProperties.Pool pool = properties.getPool(); - TaskExecutorBuilder builder = new TaskExecutorBuilder(); - builder = builder.queueCapacity(pool.getQueueCapacity()); - builder = builder.corePoolSize(pool.getCoreSize()); - builder = builder.maxPoolSize(pool.getMaxSize()); - builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout()); - builder = builder.keepAlive(pool.getKeepAlive()); - Shutdown shutdown = properties.getShutdown(); - builder = builder.awaitTermination(shutdown.isAwaitTermination()); - builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod()); - builder = builder.threadNamePrefix(properties.getThreadNamePrefix()); - builder = builder.customizers(taskExecutorCustomizers.orderedStream()::iterator); - builder = builder.taskDecorator(taskDecorator.getIfUnique()); - return builder; - } - - @Lazy - @Bean(name = { APPLICATION_TASK_EXECUTOR_BEAN_NAME, - AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME }) - @ConditionalOnMissingBean(Executor.class) - public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) { - return builder.build(); - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionProperties.java index c8bcc17ce999..2781e99a0c6f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ * * @author Stephane Nicoll * @author Filip Hrisafov + * @author Yanming Zhou * @since 2.1.0 */ @ConfigurationProperties("spring.task.execution") @@ -32,6 +33,8 @@ public class TaskExecutionProperties { private final Pool pool = new Pool(); + private final Simple simple = new Simple(); + private final Shutdown shutdown = new Shutdown(); /** @@ -39,6 +42,10 @@ public class TaskExecutionProperties { */ private String threadNamePrefix = "task-"; + public Simple getSimple() { + return this.simple; + } + public Pool getPool() { return this.pool; } @@ -55,6 +62,24 @@ public void setThreadNamePrefix(String threadNamePrefix) { this.threadNamePrefix = threadNamePrefix; } + public static class Simple { + + /** + * Set the maximum number of parallel accesses allowed. -1 indicates no + * concurrency limit at all. + */ + private Integer concurrencyLimit; + + public Integer getConcurrencyLimit() { + return this.concurrencyLimit; + } + + public void setConcurrencyLimit(Integer concurrencyLimit) { + this.concurrencyLimit = concurrencyLimit; + } + + } + public static class Pool { /** @@ -86,6 +111,8 @@ public static class Pool { */ private Duration keepAlive = Duration.ofSeconds(60); + private final Shutdown shutdown = new Shutdown(); + public int getQueueCapacity() { return this.queueCapacity; } @@ -126,6 +153,28 @@ public void setKeepAlive(Duration keepAlive) { this.keepAlive = keepAlive; } + public Shutdown getShutdown() { + return this.shutdown; + } + + public static class Shutdown { + + /** + * Whether to accept further tasks after the application context close phase + * has begun. + */ + private boolean acceptTasksAfterContextClose; + + public boolean isAcceptTasksAfterContextClose() { + return this.acceptTasksAfterContextClose; + } + + public void setAcceptTasksAfterContextClose(boolean acceptTasksAfterContextClose) { + this.acceptTasksAfterContextClose = acceptTasksAfterContextClose; + } + + } + } public static class Shutdown { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java new file mode 100644 index 000000000000..b8c2758ca1d8 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java @@ -0,0 +1,145 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.task; + +import java.util.concurrent.Executor; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading; +import org.springframework.boot.autoconfigure.thread.Threading; +import org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder; +import org.springframework.boot.task.SimpleAsyncTaskExecutorCustomizer; +import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder; +import org.springframework.boot.task.ThreadPoolTaskExecutorCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.core.task.TaskDecorator; +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +/** + * {@link TaskExecutor} configurations to be imported by + * {@link TaskExecutionAutoConfiguration} in a specific order. + * + * @author Andy Wilkinson + * @author Moritz Halbritter + * @author Yanming Zhou + */ +class TaskExecutorConfigurations { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingBean(Executor.class) + static class TaskExecutorConfiguration { + + @Bean(name = { TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME, + AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME }) + @ConditionalOnThreading(Threading.VIRTUAL) + SimpleAsyncTaskExecutor applicationTaskExecutorVirtualThreads(SimpleAsyncTaskExecutorBuilder builder) { + return builder.build(); + } + + @Lazy + @Bean(name = { TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME, + AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME }) + @ConditionalOnThreading(Threading.PLATFORM) + ThreadPoolTaskExecutor applicationTaskExecutor(ThreadPoolTaskExecutorBuilder threadPoolTaskExecutorBuilder) { + return threadPoolTaskExecutorBuilder.build(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ThreadPoolTaskExecutorBuilderConfiguration { + + @Bean + @ConditionalOnMissingBean(ThreadPoolTaskExecutorBuilder.class) + ThreadPoolTaskExecutorBuilder threadPoolTaskExecutorBuilder(TaskExecutionProperties properties, + ObjectProvider threadPoolTaskExecutorCustomizers, + ObjectProvider taskDecorator) { + TaskExecutionProperties.Pool pool = properties.getPool(); + ThreadPoolTaskExecutorBuilder builder = new ThreadPoolTaskExecutorBuilder(); + builder = builder.queueCapacity(pool.getQueueCapacity()); + builder = builder.corePoolSize(pool.getCoreSize()); + builder = builder.maxPoolSize(pool.getMaxSize()); + builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout()); + builder = builder.keepAlive(pool.getKeepAlive()); + builder = builder.acceptTasksAfterContextClose(pool.getShutdown().isAcceptTasksAfterContextClose()); + TaskExecutionProperties.Shutdown shutdown = properties.getShutdown(); + builder = builder.awaitTermination(shutdown.isAwaitTermination()); + builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod()); + builder = builder.threadNamePrefix(properties.getThreadNamePrefix()); + builder = builder.customizers(threadPoolTaskExecutorCustomizers.orderedStream()::iterator); + builder = builder.taskDecorator(taskDecorator.getIfUnique()); + return builder; + } + + } + + @Configuration(proxyBeanMethods = false) + static class SimpleAsyncTaskExecutorBuilderConfiguration { + + private final TaskExecutionProperties properties; + + private final ObjectProvider taskExecutorCustomizers; + + private final ObjectProvider taskDecorator; + + SimpleAsyncTaskExecutorBuilderConfiguration(TaskExecutionProperties properties, + ObjectProvider taskExecutorCustomizers, + ObjectProvider taskDecorator) { + this.properties = properties; + this.taskExecutorCustomizers = taskExecutorCustomizers; + this.taskDecorator = taskDecorator; + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnThreading(Threading.PLATFORM) + SimpleAsyncTaskExecutorBuilder simpleAsyncTaskExecutorBuilder() { + return builder(); + } + + @Bean(name = "simpleAsyncTaskExecutorBuilder") + @ConditionalOnMissingBean + @ConditionalOnThreading(Threading.VIRTUAL) + SimpleAsyncTaskExecutorBuilder simpleAsyncTaskExecutorBuilderVirtualThreads() { + SimpleAsyncTaskExecutorBuilder builder = builder(); + builder = builder.virtualThreads(true); + return builder; + } + + private SimpleAsyncTaskExecutorBuilder builder() { + SimpleAsyncTaskExecutorBuilder builder = new SimpleAsyncTaskExecutorBuilder(); + builder = builder.threadNamePrefix(this.properties.getThreadNamePrefix()); + builder = builder.customizers(this.taskExecutorCustomizers.orderedStream()::iterator); + builder = builder.taskDecorator(this.taskDecorator.getIfUnique()); + TaskExecutionProperties.Simple simple = this.properties.getSimple(); + builder = builder.concurrencyLimit(simple.getConcurrencyLimit()); + TaskExecutionProperties.Shutdown shutdown = this.properties.getShutdown(); + if (shutdown.isAwaitTermination()) { + builder = builder.taskTerminationTimeout(shutdown.getAwaitTerminationPeriod()); + } + return builder; + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.java index a5dd93bf4f44..9b1cbee55fc4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,22 +16,15 @@ package org.springframework.boot.autoconfigure.task; -import java.util.concurrent.ScheduledExecutorService; - -import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.LazyInitializationExcludeFilter; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.task.TaskSchedulingProperties.Shutdown; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.task.TaskSchedulerBuilder; -import org.springframework.boot.task.TaskSchedulerCustomizer; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; import org.springframework.scheduling.TaskScheduler; -import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.config.TaskManagementConfigUtils; @@ -39,38 +32,21 @@ * {@link EnableAutoConfiguration Auto-configuration} for {@link TaskScheduler}. * * @author Stephane Nicoll + * @author Moritz Halbritter * @since 2.1.0 */ @ConditionalOnClass(ThreadPoolTaskScheduler.class) @AutoConfiguration(after = TaskExecutionAutoConfiguration.class) @EnableConfigurationProperties(TaskSchedulingProperties.class) +@Import({ TaskSchedulingConfigurations.ThreadPoolTaskSchedulerBuilderConfiguration.class, + TaskSchedulingConfigurations.SimpleAsyncTaskSchedulerBuilderConfiguration.class, + TaskSchedulingConfigurations.TaskSchedulerConfiguration.class }) public class TaskSchedulingAutoConfiguration { - @Bean - @ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME) - @ConditionalOnMissingBean({ SchedulingConfigurer.class, TaskScheduler.class, ScheduledExecutorService.class }) - public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) { - return builder.build(); - } - @Bean @ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME) public static LazyInitializationExcludeFilter scheduledBeanLazyInitializationExcludeFilter() { return new ScheduledBeanLazyInitializationExcludeFilter(); } - @Bean - @ConditionalOnMissingBean - public TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties, - ObjectProvider taskSchedulerCustomizers) { - TaskSchedulerBuilder builder = new TaskSchedulerBuilder(); - builder = builder.poolSize(properties.getPool().getSize()); - Shutdown shutdown = properties.getShutdown(); - builder = builder.awaitTermination(shutdown.isAwaitTermination()); - builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod()); - builder = builder.threadNamePrefix(properties.getThreadNamePrefix()); - builder = builder.customizers(taskSchedulerCustomizers); - return builder; - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingConfigurations.java new file mode 100644 index 000000000000..53ae5c870218 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingConfigurations.java @@ -0,0 +1,127 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.task; + +import java.util.concurrent.ScheduledExecutorService; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading; +import org.springframework.boot.autoconfigure.thread.Threading; +import org.springframework.boot.task.SimpleAsyncTaskSchedulerBuilder; +import org.springframework.boot.task.SimpleAsyncTaskSchedulerCustomizer; +import org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder; +import org.springframework.boot.task.ThreadPoolTaskSchedulerCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.config.TaskManagementConfigUtils; + +/** + * {@link TaskScheduler} configurations to be imported by + * {@link TaskSchedulingAutoConfiguration} in a specific order. + * + * @author Moritz Halbritter + */ +class TaskSchedulingConfigurations { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME) + @ConditionalOnMissingBean({ TaskScheduler.class, ScheduledExecutorService.class }) + static class TaskSchedulerConfiguration { + + @Bean(name = "taskScheduler") + @ConditionalOnThreading(Threading.VIRTUAL) + SimpleAsyncTaskScheduler taskSchedulerVirtualThreads(SimpleAsyncTaskSchedulerBuilder builder) { + return builder.build(); + } + + @Bean + @ConditionalOnThreading(Threading.PLATFORM) + ThreadPoolTaskScheduler taskScheduler(ThreadPoolTaskSchedulerBuilder threadPoolTaskSchedulerBuilder) { + return threadPoolTaskSchedulerBuilder.build(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ThreadPoolTaskSchedulerBuilderConfiguration { + + @Bean + @ConditionalOnMissingBean(ThreadPoolTaskSchedulerBuilder.class) + ThreadPoolTaskSchedulerBuilder threadPoolTaskSchedulerBuilder(TaskSchedulingProperties properties, + ObjectProvider threadPoolTaskSchedulerCustomizers) { + TaskSchedulingProperties.Shutdown shutdown = properties.getShutdown(); + ThreadPoolTaskSchedulerBuilder builder = new ThreadPoolTaskSchedulerBuilder(); + builder = builder.poolSize(properties.getPool().getSize()); + builder = builder.awaitTermination(shutdown.isAwaitTermination()); + builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod()); + builder = builder.threadNamePrefix(properties.getThreadNamePrefix()); + builder = builder.customizers(threadPoolTaskSchedulerCustomizers); + return builder; + } + + } + + @Configuration(proxyBeanMethods = false) + static class SimpleAsyncTaskSchedulerBuilderConfiguration { + + private final TaskSchedulingProperties properties; + + private final ObjectProvider taskSchedulerCustomizers; + + SimpleAsyncTaskSchedulerBuilderConfiguration(TaskSchedulingProperties properties, + ObjectProvider taskSchedulerCustomizers) { + this.properties = properties; + this.taskSchedulerCustomizers = taskSchedulerCustomizers; + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnThreading(Threading.PLATFORM) + SimpleAsyncTaskSchedulerBuilder simpleAsyncTaskSchedulerBuilder() { + return builder(); + } + + @Bean(name = "simpleAsyncTaskSchedulerBuilder") + @ConditionalOnMissingBean + @ConditionalOnThreading(Threading.VIRTUAL) + SimpleAsyncTaskSchedulerBuilder simpleAsyncTaskSchedulerBuilderVirtualThreads() { + SimpleAsyncTaskSchedulerBuilder builder = builder(); + builder = builder.virtualThreads(true); + return builder; + } + + private SimpleAsyncTaskSchedulerBuilder builder() { + SimpleAsyncTaskSchedulerBuilder builder = new SimpleAsyncTaskSchedulerBuilder(); + builder = builder.threadNamePrefix(this.properties.getThreadNamePrefix()); + builder = builder.customizers(this.taskSchedulerCustomizers.orderedStream()::iterator); + TaskSchedulingProperties.Simple simple = this.properties.getSimple(); + builder = builder.concurrencyLimit(simple.getConcurrencyLimit()); + TaskSchedulingProperties.Shutdown shutdown = this.properties.getShutdown(); + if (shutdown.isAwaitTermination()) { + builder = builder.taskTerminationTimeout(shutdown.getAwaitTerminationPeriod()); + } + return builder; + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingProperties.java index f9bc7beac2c1..ea26f3261039 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,8 @@ public class TaskSchedulingProperties { private final Pool pool = new Pool(); + private final Simple simple = new Simple(); + private final Shutdown shutdown = new Shutdown(); /** @@ -42,6 +44,10 @@ public Pool getPool() { return this.pool; } + public Simple getSimple() { + return this.simple; + } + public Shutdown getShutdown() { return this.shutdown; } @@ -71,6 +77,24 @@ public void setSize(int size) { } + public static class Simple { + + /** + * Set the maximum number of parallel accesses allowed. -1 indicates no + * concurrency limit at all. + */ + private Integer concurrencyLimit; + + public Integer getConcurrencyLimit() { + return this.concurrencyLimit; + } + + public void setConcurrencyLimit(Integer concurrencyLimit) { + this.concurrencyLimit = concurrencyLimit; + } + + } + public static class Shutdown { /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thread/Threading.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thread/Threading.java new file mode 100644 index 000000000000..b82e29953fce --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thread/Threading.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.thread; + +import org.springframework.boot.system.JavaVersion; +import org.springframework.core.env.Environment; + +/** + * Threading of the application. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +public enum Threading { + + /** + * Platform threads. Active if virtual threads are not active. + */ + PLATFORM { + + @Override + public boolean isActive(Environment environment) { + return !VIRTUAL.isActive(environment); + } + + }, + /** + * Virtual threads. Active if {@code spring.threads.virtual.enabled} is {@code true} + * and running on Java 21 or later. + */ + VIRTUAL { + + @Override + public boolean isActive(Environment environment) { + return environment.getProperty("spring.threads.virtual.enabled", boolean.class, false) + && JavaVersion.getJavaVersion().isEqualOrNewerThan(JavaVersion.TWENTY_ONE); + } + + }; + + /** + * Determines whether the threading is active. + * @param environment the environment + * @return whether the threading is active + */ + public abstract boolean isActive(Environment environment); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thread/package-info.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thread/package-info.java new file mode 100644 index 000000000000..61c141a651aa --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thread/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Classes related to threads. + */ +package org.springframework.boot.autoconfigure.thread; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/ExecutionListenersTransactionManagerCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/ExecutionListenersTransactionManagerCustomizer.java new file mode 100644 index 000000000000..18a072b0f837 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/ExecutionListenersTransactionManagerCustomizer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.transaction; + +import java.util.List; + +import org.springframework.transaction.ConfigurableTransactionManager; +import org.springframework.transaction.TransactionExecutionListener; + +/** + * {@link TransactionManagerCustomizer} that adds {@link TransactionExecutionListener + * execution listeners} to any transaction manager that is + * {@link ConfigurableTransactionManager configurable}. + * + * @author Andy Wilkinson + */ +class ExecutionListenersTransactionManagerCustomizer + implements TransactionManagerCustomizer { + + private final List listeners; + + ExecutionListenersTransactionManagerCustomizer(List listeners) { + this.listeners = listeners; + } + + @Override + public void customize(ConfigurableTransactionManager transactionManager) { + this.listeners.forEach(transactionManager::addListener); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/PlatformTransactionManagerCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/PlatformTransactionManagerCustomizer.java deleted file mode 100644 index 64c7fd927bdd..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/PlatformTransactionManagerCustomizer.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.transaction; - -import org.springframework.transaction.PlatformTransactionManager; - -/** - * Callback interface that can be implemented by beans wishing to customize - * {@link PlatformTransactionManager PlatformTransactionManagers} whilst retaining default - * auto-configuration. - * - * @param the transaction manager type - * @author Phillip Webb - * @since 1.5.0 - */ -@FunctionalInterface -public interface PlatformTransactionManagerCustomizer { - - /** - * Customize the given transaction manager. - * @param transactionManager the transaction manager to customize - */ - void customize(T transactionManager); - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionAutoConfiguration.java index 7f3efffddde4..b1268020c454 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionAutoConfiguration.java @@ -16,7 +16,6 @@ package org.springframework.boot.autoconfigure.transaction; -import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.LazyInitializationExcludeFilter; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -24,7 +23,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.PlatformTransactionManager; @@ -46,16 +44,8 @@ */ @AutoConfiguration @ConditionalOnClass(PlatformTransactionManager.class) -@EnableConfigurationProperties(TransactionProperties.class) public class TransactionAutoConfiguration { - @Bean - @ConditionalOnMissingBean - public TransactionManagerCustomizers platformTransactionManagerCustomizers( - ObjectProvider> customizers) { - return new TransactionManagerCustomizers(customizers.orderedStream().toList()); - } - @Bean @ConditionalOnMissingBean @ConditionalOnSingleCandidate(ReactiveTransactionManager.class) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionManagerCustomizationAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionManagerCustomizationAutoConfiguration.java new file mode 100644 index 000000000000..aba33e3226c3 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionManagerCustomizationAutoConfiguration.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.transaction; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionExecutionListener; +import org.springframework.transaction.TransactionManager; + +/** + * Auto-configuration for the customization of a {@link TransactionManager}. + * + * @author Andy Wilkinson + * @since 3.2.0 + */ +@ConditionalOnClass(PlatformTransactionManager.class) +@AutoConfiguration(before = TransactionAutoConfiguration.class) +@EnableConfigurationProperties(TransactionProperties.class) +public class TransactionManagerCustomizationAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + TransactionManagerCustomizers platformTransactionManagerCustomizers( + ObjectProvider> customizers) { + return TransactionManagerCustomizers.of(customizers.orderedStream().toList()); + } + + @Bean + ExecutionListenersTransactionManagerCustomizer transactionExecutionListeners( + ObjectProvider listeners) { + return new ExecutionListenersTransactionManagerCustomizer(listeners.orderedStream().toList()); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionManagerCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionManagerCustomizer.java new file mode 100644 index 000000000000..e268fe87a49f --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionManagerCustomizer.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.transaction; + +import org.springframework.transaction.TransactionManager; + +/** + * Callback interface that can be implemented by beans wishing to customize + * {@link TransactionManager TransactionManagers} while retaining default + * auto-configuration. + * + * @param the transaction manager type + * @author Andy Wilkinson + * @since 3.2.0 + */ +public interface TransactionManagerCustomizer { + + /** + * Customize the given transaction manager. + * @param transactionManager the transaction manager to customize + */ + void customize(T transactionManager); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionManagerCustomizers.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionManagerCustomizers.java index 2bb603c4bc60..e4c0b5465c81 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionManagerCustomizers.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionManagerCustomizers.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,27 +22,45 @@ import java.util.List; import org.springframework.boot.util.LambdaSafe; -import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionManager; /** - * A collection of {@link PlatformTransactionManagerCustomizer}. + * A collection of {@link TransactionManagerCustomizer TransactionManagerCustomizers}. * * @author Phillip Webb + * @author Andy Wilkinson * @since 1.5.0 */ -public class TransactionManagerCustomizers { +public final class TransactionManagerCustomizers { - private final List> customizers; + private final List> customizers; - public TransactionManagerCustomizers(Collection> customizers) { - this.customizers = (customizers != null) ? new ArrayList<>(customizers) : Collections.emptyList(); + private TransactionManagerCustomizers(List> customizers) { + this.customizers = customizers; } + /** + * Customize the given {@code transactionManager}. + * @param transactionManager the transaction manager to customize + * @since 3.2.0 + */ @SuppressWarnings("unchecked") - public void customize(PlatformTransactionManager transactionManager) { - LambdaSafe.callbacks(PlatformTransactionManagerCustomizer.class, this.customizers, transactionManager) + public void customize(TransactionManager transactionManager) { + LambdaSafe.callbacks(TransactionManagerCustomizer.class, this.customizers, transactionManager) .withLogger(TransactionManagerCustomizers.class) .invoke((customizer) -> customizer.customize(transactionManager)); } + /** + * Returns a new {@code TransactionManagerCustomizers} instance containing the given + * {@code customizers}. + * @param customizers the customizers + * @return the new instance + * @since 3.2.0 + */ + public static TransactionManagerCustomizers of(Collection> customizers) { + return new TransactionManagerCustomizers((customizers != null) ? new ArrayList<>(customizers) + : Collections.>emptyList()); + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionProperties.java index d48200a06f39..a9170ee0cd78 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ * @since 1.5.0 */ @ConfigurationProperties(prefix = "spring.transaction") -public class TransactionProperties implements PlatformTransactionManagerCustomizer { +public class TransactionProperties implements TransactionManagerCustomizer { /** * Default transaction timeout. If a duration suffix is not specified, seconds will be diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JndiJtaConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JndiJtaConfiguration.java index 91b59dd02af0..9e51eb2ecaa9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JndiJtaConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JndiJtaConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfiguration.java index a9040ac4ba1f..480e32b81dfa 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; +import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizationAutoConfiguration; import org.springframework.context.annotation.Import; /** @@ -36,7 +37,8 @@ * @since 1.2.0 */ @AutoConfiguration(before = { XADataSourceAutoConfiguration.class, ActiveMQAutoConfiguration.class, - ArtemisAutoConfiguration.class, HibernateJpaAutoConfiguration.class, TransactionAutoConfiguration.class }) + ArtemisAutoConfiguration.class, HibernateJpaAutoConfiguration.class, TransactionAutoConfiguration.class, + TransactionManagerCustomizationAutoConfiguration.class }) @ConditionalOnClass(jakarta.transaction.Transaction.class) @ConditionalOnProperty(prefix = "spring.jta", value = "enabled", matchIfMissing = true) @Import(JndiJtaConfiguration.class) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapter.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapter.java index 700a68a91610..e6c792838d86 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapter.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapter.java @@ -39,6 +39,7 @@ * * @author Stephane Nicoll * @author Phillip Webb + * @author Zisis Pavloudis * @since 2.0.0 */ public class ValidatorAdapter implements SmartValidator, ApplicationContextAware, InitializingBean, DisposableBean { @@ -153,4 +154,13 @@ private static Validator wrap(Validator validator, boolean existingBean) { return validator; } + @Override + @SuppressWarnings("unchecked") + public T unwrap(Class type) { + if (type.isInstance(this.target)) { + return (T) this.target; + } + return this.target.unwrap(type); + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ConditionalOnEnabledResourceChain.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ConditionalOnEnabledResourceChain.java index 3a3467b5e6d4..62b8760ec8bc 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ConditionalOnEnabledResourceChain.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ConditionalOnEnabledResourceChain.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,10 @@ /** * {@link Conditional @Conditional} that checks whether the Spring resource handling chain * is enabled. Matches if {@link WebProperties.Resources.Chain#getEnabled()} is - * {@code true} or if {@code webjars-locator-core} is on the classpath. + * {@code true} or if one of {@code "org.webjars:webjars-locator-core"}, + * {@code "org.webjars:webjars-locator-lite"} is on the classpath. + *

+ * Note that support for {@code "org.webjars:webjars-locator-core"} is deprecated. * * @author Stephane Nicoll * @since 1.3.0 diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorProperties.java index 9900a86157b9..e8bbce97d980 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,6 +55,11 @@ public class ErrorProperties { */ private IncludeAttribute includeBindingErrors = IncludeAttribute.NEVER; + /** + * When to include "path" attribute. + */ + private IncludeAttribute includePath = IncludeAttribute.ALWAYS; + private final Whitelabel whitelabel = new Whitelabel(); public String getPath() { @@ -97,6 +102,14 @@ public void setIncludeBindingErrors(IncludeAttribute includeBindingErrors) { this.includeBindingErrors = includeBindingErrors; } + public IncludeAttribute getIncludePath() { + return this.includePath; + } + + public void setIncludePath(IncludeAttribute includePath) { + this.includePath = includePath; + } + public Whitelabel getWhitelabel() { return this.whitelabel; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/OnEnabledResourceChainCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/OnEnabledResourceChainCondition.java index c4ce3b4ea992..e457f8a31bb8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/OnEnabledResourceChainCondition.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/OnEnabledResourceChainCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,12 +32,15 @@ * @author Stephane Nicoll * @author Phillip Webb * @author Madhura Bhave + * @author Brian Clozel * @see ConditionalOnEnabledResourceChain */ class OnEnabledResourceChainCondition extends SpringBootCondition { private static final String WEBJAR_ASSET_LOCATOR = "org.webjars.WebJarAssetLocator"; + private static final String WEBJAR_VERSION_LOCATOR = "org.webjars.WebJarVersionLocator"; + @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ConfigurableEnvironment environment = (ConfigurableEnvironment) context.getEnvironment(); @@ -47,10 +50,13 @@ public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeM Boolean match = Chain.getEnabled(fixed, content, chain); ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnEnabledResourceChain.class); if (match == null) { + if (ClassUtils.isPresent(WEBJAR_VERSION_LOCATOR, getClass().getClassLoader())) { + return ConditionOutcome.match(message.found("class").items(WEBJAR_VERSION_LOCATOR)); + } if (ClassUtils.isPresent(WEBJAR_ASSET_LOCATOR, getClass().getClassLoader())) { return ConditionOutcome.match(message.found("class").items(WEBJAR_ASSET_LOCATOR)); } - return ConditionOutcome.noMatch(message.didNotFind("class").items(WEBJAR_ASSET_LOCATOR)); + return ConditionOutcome.noMatch(message.didNotFind("class").items(WEBJAR_VERSION_LOCATOR)); } if (match) { return ConditionOutcome.match(message.because("enabled")); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index a4e5bcf2d1a8..0c2e2b04d409 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,7 @@ import org.springframework.boot.web.server.Compression; import org.springframework.boot.web.server.Cookie; import org.springframework.boot.web.server.Http2; +import org.springframework.boot.web.server.MimeMappings; import org.springframework.boot.web.server.Shutdown; import org.springframework.boot.web.server.Ssl; import org.springframework.boot.web.servlet.server.Encoding; @@ -71,6 +72,7 @@ * @author Parviz Rozikov * @author Florian Storz * @author Michael Weidmann + * @author Lasse Wulff * @since 1.0.0 */ @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) @@ -115,6 +117,11 @@ public class ServerProperties { @NestedConfigurationProperty private final Compression compression = new Compression(); + /** + * Custom MIME mappings in addition to the default MIME mappings. + */ + private final MimeMappings mimeMappings = new MimeMappings(); + @NestedConfigurationProperty private final Http2 http2 = new Http2(); @@ -154,17 +161,6 @@ public void setServerHeader(String serverHeader) { this.serverHeader = serverHeader; } - @Deprecated(since = "3.0.0", forRemoval = true) - @DeprecatedConfigurationProperty - public DataSize getMaxHttpHeaderSize() { - return getMaxHttpRequestHeaderSize(); - } - - @Deprecated(since = "3.0.0", forRemoval = true) - public void setMaxHttpHeaderSize(DataSize maxHttpHeaderSize) { - setMaxHttpRequestHeaderSize(maxHttpHeaderSize); - } - public DataSize getMaxHttpRequestHeaderSize() { return this.maxHttpRequestHeaderSize; } @@ -197,6 +193,14 @@ public Compression getCompression() { return this.compression; } + public MimeMappings getMimeMappings() { + return this.mimeMappings; + } + + public void setMimeMappings(Map customMappings) { + customMappings.forEach(this.mimeMappings::add); + } + public Http2 getHttp2() { return this.http2; } @@ -340,6 +344,11 @@ public static class Session { @DurationUnit(ChronoUnit.SECONDS) private Duration timeout = Duration.ofMinutes(30); + /** + * Maximum number of sessions that can be stored. + */ + private int maxSessions = 10000; + @NestedConfigurationProperty private final Cookie cookie = new Cookie(); @@ -351,6 +360,14 @@ public void setTimeout(Duration timeout) { this.timeout = timeout; } + public int getMaxSessions() { + return this.maxSessions; + } + + public void setMaxSessions(int maxSessions) { + this.maxSessions = maxSessions; + } + public Cookie getCookie() { return this.cookie; } @@ -475,7 +492,7 @@ public static class Tomcat { /** * Whether to reject requests with illegal header names or values. */ - @Deprecated(since = "2.7.12", forRemoval = true) + @Deprecated(since = "2.7.12", forRemoval = true) // Remove in 3.3 private boolean rejectIllegalHeader = true; /** @@ -634,11 +651,13 @@ public void setConnectionTimeout(Duration connectionTimeout) { this.connectionTimeout = connectionTimeout; } - @DeprecatedConfigurationProperty(reason = "The setting has been deprecated in Tomcat") + @Deprecated(since = "3.2.0", forRemoval = true) + @DeprecatedConfigurationProperty(reason = "The setting has been deprecated in Tomcat", since = "3.2.0") public boolean isRejectIllegalHeader() { return this.rejectIllegalHeader; } + @Deprecated(since = "3.2.0", forRemoval = true) public void setRejectIllegalHeader(boolean rejectIllegalHeader) { this.rejectIllegalHeader = rejectIllegalHeader; } @@ -914,6 +933,11 @@ public static class Threads { */ private int minSpare = 10; + /** + * Maximum capacity of the thread pool's backing queue. + */ + private int maxQueueCapacity = 2147483647; + public int getMax() { return this.max; } @@ -930,6 +954,14 @@ public void setMinSpare(int minSpare) { this.minSpare = minSpare; } + public int getMaxQueueCapacity() { + return this.maxQueueCapacity; + } + + public void setMaxQueueCapacity(int maxQueueCapacity) { + this.maxQueueCapacity = maxQueueCapacity; + } + } /** @@ -1123,6 +1155,12 @@ public static class Jetty { */ private DataSize maxHttpResponseHeaderSize = DataSize.ofKilobytes(8); + /** + * Maximum number of connections that the server accepts and processes at any + * given time. + */ + private int maxConnections = -1; + public Accesslog getAccesslog() { return this.accesslog; } @@ -1155,6 +1193,14 @@ public void setMaxHttpResponseHeaderSize(DataSize maxHttpResponseHeaderSize) { this.maxHttpResponseHeaderSize = maxHttpResponseHeaderSize; } + public int getMaxConnections() { + return this.maxConnections; + } + + public void setMaxConnections(int maxConnections) { + this.maxConnections = maxConnections; + } + /** * Jetty access log properties. */ @@ -1395,11 +1441,6 @@ public static class Netty { */ private DataSize initialBufferSize = DataSize.ofBytes(128); - /** - * Maximum chunk size that can be decoded for an HTTP request. - */ - private DataSize maxChunkSize = DataSize.ofKilobytes(8); - /** * Maximum length that can be decoded for an HTTP request's initial line. */ @@ -1446,17 +1487,6 @@ public void setInitialBufferSize(DataSize initialBufferSize) { this.initialBufferSize = initialBufferSize; } - @Deprecated(since = "3.0.0", forRemoval = true) - @DeprecatedConfigurationProperty(reason = "Deprecated for removal in Reactor Netty") - public DataSize getMaxChunkSize() { - return this.maxChunkSize; - } - - @Deprecated(since = "3.0.0", forRemoval = true) - public void setMaxChunkSize(DataSize maxChunkSize) { - this.maxChunkSize = maxChunkSize; - } - public DataSize getMaxInitialLineLength() { return this.maxInitialLineLength; } @@ -1647,7 +1677,7 @@ public void setMaxCookies(Integer maxCookies) { this.maxCookies = maxCookies; } - @DeprecatedConfigurationProperty(replacement = "server.undertow.decode-slash") + @DeprecatedConfigurationProperty(replacement = "server.undertow.decode-slash", since = "3.0.3") @Deprecated(forRemoval = true, since = "3.0.3") public boolean isAllowEncodedSlash() { return this.allowEncodedSlash; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/AutoConfiguredRestClientSsl.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/AutoConfiguredRestClientSsl.java new file mode 100644 index 000000000000..8fc9dd9663b2 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/AutoConfiguredRestClientSsl.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.client; + +import java.util.function.Consumer; + +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.boot.web.client.ClientHttpRequestFactories; +import org.springframework.boot.web.client.ClientHttpRequestFactorySettings; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.web.client.RestClient; + +/** + * An auto-configured {@link RestClientSsl} implementation. + * + * @author Phillip Webb + */ +class AutoConfiguredRestClientSsl implements RestClientSsl { + + private final SslBundles sslBundles; + + AutoConfiguredRestClientSsl(SslBundles sslBundles) { + this.sslBundles = sslBundles; + } + + @Override + public Consumer fromBundle(String bundleName) { + return fromBundle(this.sslBundles.getBundle(bundleName)); + } + + @Override + public Consumer fromBundle(SslBundle bundle) { + return (builder) -> { + ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS.withSslBundle(bundle); + ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(settings); + builder.requestFactory(requestFactory); + }; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/HttpMessageConvertersRestClientCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/HttpMessageConvertersRestClientCustomizer.java new file mode 100644 index 000000000000..c5372d5d7f84 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/HttpMessageConvertersRestClientCustomizer.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.client; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.boot.web.client.RestClientCustomizer; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.util.Assert; +import org.springframework.web.client.RestClient; + +/** + * {@link RestClientCustomizer} to apply {@link HttpMessageConverter + * HttpMessageConverters}. + * + * @author Phillip Webb + * @since 3.2.0 + */ +public class HttpMessageConvertersRestClientCustomizer implements RestClientCustomizer { + + private final Iterable> messageConverters; + + public HttpMessageConvertersRestClientCustomizer(HttpMessageConverter... messageConverters) { + Assert.notNull(messageConverters, "MessageConverters must not be null"); + this.messageConverters = Arrays.asList(messageConverters); + } + + HttpMessageConvertersRestClientCustomizer(HttpMessageConverters messageConverters) { + this.messageConverters = messageConverters; + } + + @Override + public void customize(RestClient.Builder restClientBuilder) { + restClientBuilder.messageConverters(this::configureMessageConverters); + } + + private void configureMessageConverters(List> messageConverters) { + if (this.messageConverters != null) { + messageConverters.clear(); + this.messageConverters.forEach(messageConverters::add); + } + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/NotReactiveWebApplicationCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/NotReactiveWebApplicationCondition.java new file mode 100644 index 000000000000..b45fbcc58f1f --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/NotReactiveWebApplicationCondition.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.client; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.NoneNestedConditions; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; + +/** + * {@link SpringBootCondition} that applies only when running in a non-reactive web + * application. + * + * @author Phillip Webb + */ +class NotReactiveWebApplicationCondition extends NoneNestedConditions { + + NotReactiveWebApplicationCondition() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) + private static final class ReactiveWebApplication { + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java new file mode 100644 index 000000000000..6198b70bfaee --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.client; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.boot.web.client.ClientHttpRequestFactories; +import org.springframework.boot.web.client.ClientHttpRequestFactorySettings; +import org.springframework.boot.web.client.RestClientCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Scope; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.web.client.RestClient; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for {@link RestClient}. + *

+ * This will produce a {@link RestClient.Builder RestClient.Builder} bean with the + * {@code prototype} scope, meaning each injection point will receive a newly cloned + * instance of the builder. + * + * @author Arjen Poutsma + * @author Moritz Halbritter + * @since 3.2.0 + */ +@AutoConfiguration(after = { HttpMessageConvertersAutoConfiguration.class, SslAutoConfiguration.class }) +@ConditionalOnClass(RestClient.class) +@Conditional(NotReactiveWebApplicationCondition.class) +public class RestClientAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + @Order(Ordered.LOWEST_PRECEDENCE) + HttpMessageConvertersRestClientCustomizer httpMessageConvertersRestClientCustomizer( + ObjectProvider messageConverters) { + return new HttpMessageConvertersRestClientCustomizer(messageConverters.getIfUnique()); + } + + @Bean + @ConditionalOnMissingBean(RestClientSsl.class) + @ConditionalOnBean(SslBundles.class) + AutoConfiguredRestClientSsl restClientSsl(SslBundles sslBundles) { + return new AutoConfiguredRestClientSsl(sslBundles); + } + + @Bean + @ConditionalOnMissingBean + RestClientBuilderConfigurer restClientBuilderConfigurer(ObjectProvider customizerProvider) { + RestClientBuilderConfigurer configurer = new RestClientBuilderConfigurer(); + configurer.setRestClientCustomizers(customizerProvider.orderedStream().toList()); + return configurer; + } + + @Bean + @Scope("prototype") + @ConditionalOnMissingBean + RestClient.Builder restClientBuilder(RestClientBuilderConfigurer restClientBuilderConfigurer) { + RestClient.Builder builder = RestClient.builder() + .requestFactory(ClientHttpRequestFactories.get(ClientHttpRequestFactorySettings.DEFAULTS)); + return restClientBuilderConfigurer.configure(builder); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientBuilderConfigurer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientBuilderConfigurer.java new file mode 100644 index 000000000000..8d6f57bd461f --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientBuilderConfigurer.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.client; + +import java.util.List; + +import org.springframework.boot.web.client.RestClientCustomizer; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClient.Builder; + +/** + * Configure {@link RestClient.Builder} with sensible defaults. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +public class RestClientBuilderConfigurer { + + private List customizers; + + void setRestClientCustomizers(List customizers) { + this.customizers = customizers; + } + + /** + * Configure the specified {@link RestClient.Builder}. The builder can be further + * tuned and default settings can be overridden. + * @param builder the {@link RestClient.Builder} instance to configure + * @return the configured builder + */ + public RestClient.Builder configure(RestClient.Builder builder) { + applyCustomizers(builder); + return builder; + } + + private void applyCustomizers(Builder builder) { + if (this.customizers != null) { + for (RestClientCustomizer customizer : this.customizers) { + customizer.customize(builder); + } + } + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientSsl.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientSsl.java new file mode 100644 index 000000000000..fd892efb4326 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientSsl.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.client; + +import java.util.function.Consumer; + +import org.springframework.boot.ssl.NoSuchSslBundleException; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.web.client.ClientHttpRequestFactories; +import org.springframework.boot.web.client.ClientHttpRequestFactorySettings; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.web.client.RestClient; + +/** + * Interface that can be used to {@link RestClient.Builder#apply apply} SSL configuration + * to a {@link org.springframework.web.client.RestClient.Builder RestClient.Builder}. + *

+ * Typically used as follows:

+ * @Bean
+ * public MyBean myBean(RestClient.Builder restClientBuilder, RestClientSsl ssl) {
+ *     RestClient restClientrestClient= restClientBuilder.apply(ssl.fromBundle("mybundle")).build();
+ *     return new MyBean(webClient);
+ * }
+ * 
NOTE: Apply SSL configuration will replace any previously + * {@link RestClient.Builder#requestFactory configured} {@link ClientHttpRequestFactory}. + * If you need to configure {@link ClientHttpRequestFactory} with more than just SSL + * consider using a {@link ClientHttpRequestFactorySettings} with + * {@link ClientHttpRequestFactories}. + * + * @author Phillip Webb + * @since 3.2.0 + */ +public interface RestClientSsl { + + /** + * Return a {@link Consumer} that will apply SSL configuration for the named + * {@link SslBundle} to a {@link org.springframework.web.client.RestClient.Builder + * RestClient.Builder}. + * @param bundleName the name of the SSL bundle to apply + * @return a {@link Consumer} to apply the configuration + * @throws NoSuchSslBundleException if a bundle with the provided name does not exist + */ + Consumer fromBundle(String bundleName) throws NoSuchSslBundleException; + + /** + * Return a {@link Consumer} that will apply SSL configuration for the + * {@link SslBundle} to a {@link org.springframework.web.client.RestClient.Builder + * RestClient.Builder}. + * @param bundle the SSL bundle to apply + * @return a {@link Consumer} to apply the configuration + */ + Consumer fromBundle(SslBundle bundle); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.java index 0986c60bd9b8..f6fc73629ae5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.java @@ -21,12 +21,8 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; -import org.springframework.boot.autoconfigure.condition.NoneNestedConditions; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration.NotReactiveWebApplicationCondition; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.client.RestTemplateCustomizer; import org.springframework.boot.web.client.RestTemplateRequestCustomizer; @@ -49,7 +45,6 @@ public class RestTemplateAutoConfiguration { @Bean @Lazy - @ConditionalOnMissingBean public RestTemplateBuilderConfigurer restTemplateBuilderConfigurer( ObjectProvider messageConverters, ObjectProvider restTemplateCustomizers, @@ -69,17 +64,4 @@ public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer res return restTemplateBuilderConfigurer.configure(builder); } - static class NotReactiveWebApplicationCondition extends NoneNestedConditions { - - NotReactiveWebApplicationCondition() { - super(ConfigurationPhase.PARSE_CONFIGURATION); - } - - @ConditionalOnWebApplication(type = Type.REACTIVE) - static class ReactiveWebApplication { - - } - - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration.java index eca465353db6..5c69a63b4bb9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,9 +19,9 @@ import io.undertow.Undertow; import org.apache.catalina.startup.Tomcat; import org.apache.coyote.UpgradeProtocol; +import org.eclipse.jetty.ee10.webapp.WebAppContext; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.Loader; -import org.eclipse.jetty.webapp.WebAppContext; import org.xnio.SslClientAuthMode; import reactor.netty.http.server.HttpServer; @@ -29,7 +29,9 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWarDeployment; +import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.thread.Threading; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -62,6 +64,12 @@ public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environ return new TomcatWebServerFactoryCustomizer(environment, serverProperties); } + @Bean + @ConditionalOnThreading(Threading.VIRTUAL) + TomcatVirtualThreadsWebServerFactoryCustomizer tomcatVirtualThreadsProtocolHandlerCustomizer() { + return new TomcatVirtualThreadsWebServerFactoryCustomizer(); + } + } /** @@ -77,6 +85,13 @@ public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environme return new JettyWebServerFactoryCustomizer(environment, serverProperties); } + @Bean + @ConditionalOnThreading(Threading.VIRTUAL) + JettyVirtualThreadsWebServerFactoryCustomizer jettyVirtualThreadsWebServerFactoryCustomizer( + ServerProperties serverProperties) { + return new JettyVirtualThreadsWebServerFactoryCustomizer(serverProperties); + } + } /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyThreadPool.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyThreadPool.java new file mode 100644 index 000000000000..7c8dadadb908 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyThreadPool.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.embedded; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.SynchronousQueue; + +import org.eclipse.jetty.util.BlockingArrayQueue; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.eclipse.jetty.util.thread.ThreadPool; + +import org.springframework.boot.autoconfigure.web.ServerProperties; + +/** + * Creates a {@link ThreadPool} for Jetty, applying + * {@link org.springframework.boot.autoconfigure.web.ServerProperties.Jetty.Threads + * ServerProperties.Jetty.Threads Jetty thread properties}. + * + * @author Moritz Halbritter + */ +final class JettyThreadPool { + + private JettyThreadPool() { + } + + static QueuedThreadPool create(ServerProperties.Jetty.Threads properties) { + BlockingQueue queue = determineBlockingQueue(properties.getMaxQueueCapacity()); + int maxThreadCount = (properties.getMax() > 0) ? properties.getMax() : 200; + int minThreadCount = (properties.getMin() > 0) ? properties.getMin() : 8; + int threadIdleTimeout = (properties.getIdleTimeout() != null) ? (int) properties.getIdleTimeout().toMillis() + : 60000; + return new QueuedThreadPool(maxThreadCount, minThreadCount, threadIdleTimeout, queue); + } + + private static BlockingQueue determineBlockingQueue(Integer maxQueueCapacity) { + if (maxQueueCapacity == null) { + return null; + } + if (maxQueueCapacity == 0) { + return new SynchronousQueue<>(); + } + return new BlockingArrayQueue<>(maxQueueCapacity); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyVirtualThreadsWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyVirtualThreadsWebServerFactoryCustomizer.java new file mode 100644 index 000000000000..c54e308cd973 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyVirtualThreadsWebServerFactoryCustomizer.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.embedded; + +import org.eclipse.jetty.util.VirtualThreads; +import org.eclipse.jetty.util.thread.QueuedThreadPool; + +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.web.embedded.jetty.ConfigurableJettyWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.core.Ordered; +import org.springframework.util.Assert; + +/** + * Activates virtual threads on the {@link ConfigurableJettyWebServerFactory}. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +public class JettyVirtualThreadsWebServerFactoryCustomizer + implements WebServerFactoryCustomizer, Ordered { + + private final ServerProperties serverProperties; + + public JettyVirtualThreadsWebServerFactoryCustomizer(ServerProperties serverProperties) { + this.serverProperties = serverProperties; + } + + @Override + public void customize(ConfigurableJettyWebServerFactory factory) { + Assert.state(VirtualThreads.areSupported(), "Virtual threads are not supported"); + QueuedThreadPool threadPool = JettyThreadPool.create(this.serverProperties.getJetty().getThreads()); + threadPool.setVirtualThreadsExecutor(VirtualThreads.getNamedVirtualThreadsExecutor("jetty-")); + factory.setThreadPool(threadPool); + } + + @Override + public int getOrder() { + return JettyWebServerFactoryCustomizer.ORDER + 1; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java index 2248f1a72039..c12333dc9cfc 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java @@ -18,9 +18,9 @@ import java.time.Duration; import java.util.Arrays; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.SynchronousQueue; +import java.util.List; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.CustomRequestLog; @@ -28,12 +28,6 @@ import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.RequestLogWriter; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.server.handler.HandlerCollection; -import org.eclipse.jetty.server.handler.HandlerWrapper; -import org.eclipse.jetty.util.BlockingArrayQueue; -import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.eclipse.jetty.util.thread.ThreadPool; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.cloud.CloudPlatform; @@ -60,6 +54,8 @@ public class JettyWebServerFactoryCustomizer implements WebServerFactoryCustomizer, Ordered { + static final int ORDER = 0; + private final Environment environment; private final ServerProperties serverProperties; @@ -71,7 +67,7 @@ public JettyWebServerFactoryCustomizer(Environment environment, ServerProperties @Override public int getOrder() { - return 0; + return ORDER; } @Override @@ -79,8 +75,9 @@ public void customize(ConfigurableJettyWebServerFactory factory) { ServerProperties.Jetty properties = this.serverProperties.getJetty(); factory.setUseForwardHeaders(getOrDeduceUseForwardHeaders()); ServerProperties.Jetty.Threads threadProperties = properties.getThreads(); - factory.setThreadPool(determineThreadPool(properties.getThreads())); + factory.setThreadPool(JettyThreadPool.create(properties.getThreads())); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getMaxConnections).to(factory::setMaxConnections); map.from(threadProperties::getAcceptors).to(factory::setAcceptors); map.from(threadProperties::getSelectors).to(factory::setSelectors); map.from(this.serverProperties::getMaxHttpRequestHeaderSize) @@ -133,42 +130,25 @@ public void customize(Server server) { setHandlerMaxHttpFormPostSize(server.getHandlers()); } - private void setHandlerMaxHttpFormPostSize(Handler... handlers) { + private void setHandlerMaxHttpFormPostSize(List handlers) { for (Handler handler : handlers) { - if (handler instanceof ContextHandler contextHandler) { - contextHandler.setMaxFormContentSize(maxHttpFormPostSize); - } - else if (handler instanceof HandlerWrapper wrapper) { - setHandlerMaxHttpFormPostSize(wrapper.getHandler()); - } - else if (handler instanceof HandlerCollection collection) { - setHandlerMaxHttpFormPostSize(collection.getHandlers()); - } + setHandlerMaxHttpFormPostSize(handler); } } - }); - } - - private ThreadPool determineThreadPool(ServerProperties.Jetty.Threads properties) { - BlockingQueue queue = determineBlockingQueue(properties.getMaxQueueCapacity()); - int maxThreadCount = (properties.getMax() > 0) ? properties.getMax() : 200; - int minThreadCount = (properties.getMin() > 0) ? properties.getMin() : 8; - int threadIdleTimeout = (properties.getIdleTimeout() != null) ? (int) properties.getIdleTimeout().toMillis() - : 60000; - return new QueuedThreadPool(maxThreadCount, minThreadCount, threadIdleTimeout, queue); - } + private void setHandlerMaxHttpFormPostSize(Handler handler) { + if (handler instanceof ServletContextHandler contextHandler) { + contextHandler.setMaxFormContentSize(maxHttpFormPostSize); + } + else if (handler instanceof Handler.Wrapper wrapper) { + setHandlerMaxHttpFormPostSize(wrapper.getHandler()); + } + else if (handler instanceof Handler.Collection collection) { + setHandlerMaxHttpFormPostSize(collection.getHandlers()); + } + } - private BlockingQueue determineBlockingQueue(Integer maxQueueCapacity) { - if (maxQueueCapacity == null) { - return null; - } - if (maxQueueCapacity == 0) { - return new SynchronousQueue<>(); - } - else { - return new BlockingArrayQueue<>(maxQueueCapacity); - } + }); } private void customizeAccessLog(ConfigurableJettyWebServerFactory factory, diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizer.java index 0748e79898d1..6dc657b3d7cb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizer.java @@ -19,7 +19,6 @@ import java.time.Duration; import io.netty.channel.ChannelOption; -import reactor.netty.http.server.HttpRequestDecoderSpec; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.cloud.CloudPlatform; @@ -66,7 +65,6 @@ public void customize(NettyReactiveWebServerFactory factory) { .to((maxKeepAliveRequests) -> customizeMaxKeepAliveRequests(factory, maxKeepAliveRequests)); if (this.serverProperties.getHttp2() != null && this.serverProperties.getHttp2().isEnabled()) { map.from(this.serverProperties.getMaxHttpRequestHeaderSize()) - .whenNonNull() .to((size) -> customizeHttp2MaxHeaderSize(factory, size.toBytes())); } customizeRequestDecoder(factory, map); @@ -88,37 +86,22 @@ private void customizeConnectionTimeout(NettyReactiveWebServerFactory factory, D private void customizeRequestDecoder(NettyReactiveWebServerFactory factory, PropertyMapper propertyMapper) { factory.addServerCustomizers((httpServer) -> httpServer.httpRequestDecoder((httpRequestDecoderSpec) -> { propertyMapper.from(this.serverProperties.getMaxHttpRequestHeaderSize()) - .whenNonNull() .to((maxHttpRequestHeader) -> httpRequestDecoderSpec .maxHeaderSize((int) maxHttpRequestHeader.toBytes())); ServerProperties.Netty nettyProperties = this.serverProperties.getNetty(); - maxChunkSize(propertyMapper, httpRequestDecoderSpec, nettyProperties); propertyMapper.from(nettyProperties.getMaxInitialLineLength()) - .whenNonNull() .to((maxInitialLineLength) -> httpRequestDecoderSpec .maxInitialLineLength((int) maxInitialLineLength.toBytes())); propertyMapper.from(nettyProperties.getH2cMaxContentLength()) - .whenNonNull() .to((h2cMaxContentLength) -> httpRequestDecoderSpec .h2cMaxContentLength((int) h2cMaxContentLength.toBytes())); propertyMapper.from(nettyProperties.getInitialBufferSize()) - .whenNonNull() .to((initialBufferSize) -> httpRequestDecoderSpec.initialBufferSize((int) initialBufferSize.toBytes())); - propertyMapper.from(nettyProperties.isValidateHeaders()) - .whenNonNull() - .to(httpRequestDecoderSpec::validateHeaders); + propertyMapper.from(nettyProperties.isValidateHeaders()).to(httpRequestDecoderSpec::validateHeaders); return httpRequestDecoderSpec; })); } - @SuppressWarnings({ "deprecation", "removal" }) - private void maxChunkSize(PropertyMapper propertyMapper, HttpRequestDecoderSpec httpRequestDecoderSpec, - ServerProperties.Netty nettyProperties) { - propertyMapper.from(nettyProperties.getMaxChunkSize()) - .whenNonNull() - .to((maxChunkSize) -> httpRequestDecoderSpec.maxChunkSize((int) maxChunkSize.toBytes())); - } - private void customizeIdleTimeout(NettyReactiveWebServerFactory factory, Duration idleTimeout) { factory.addServerCustomizers((httpServer) -> httpServer.idleTimeout(idleTimeout)); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizer.java new file mode 100644 index 000000000000..54ba36c7e67f --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizer.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.embedded; + +import org.apache.coyote.ProtocolHandler; +import org.apache.tomcat.util.threads.VirtualThreadExecutor; + +import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.core.Ordered; + +/** + * Activates {@link VirtualThreadExecutor} on {@link ProtocolHandler Tomcat's protocol + * handler}. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +public class TomcatVirtualThreadsWebServerFactoryCustomizer + implements WebServerFactoryCustomizer, Ordered { + + @Override + public void customize(ConfigurableTomcatWebServerFactory factory) { + factory.addProtocolHandlerCustomizers( + (protocolHandler) -> protocolHandler.setExecutor(new VirtualThreadExecutor("tomcat-handler-"))); + } + + @Override + public int getOrder() { + return TomcatWebServerFactoryCustomizer.ORDER + 1; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java index e47b7cccfe59..8699299ee8b5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,6 +67,8 @@ public class TomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer, Ordered { + static final int ORDER = 0; + private final Environment environment; private final ServerProperties serverProperties; @@ -78,10 +80,11 @@ public TomcatWebServerFactoryCustomizer(Environment environment, ServerPropertie @Override public int getOrder() { - return 0; + return ORDER; } @Override + @SuppressWarnings("removal") public void customize(ConfigurableTomcatWebServerFactory factory) { ServerProperties.Tomcat properties = this.serverProperties.getTomcat(); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); @@ -94,10 +97,13 @@ public void customize(ConfigurableTomcatWebServerFactory factory) { ServerProperties.Tomcat.Threads threadProperties = properties.getThreads(); map.from(threadProperties::getMax) .when(this::isPositive) - .to((maxThreads) -> customizeMaxThreads(factory, threadProperties.getMax())); + .to((maxThreads) -> customizeMaxThreads(factory, maxThreads)); map.from(threadProperties::getMinSpare) .when(this::isPositive) .to((minSpareThreads) -> customizeMinThreads(factory, minSpareThreads)); + map.from(threadProperties::getMaxQueueCapacity) + .when(this::isPositive) + .to((maxQueueCapacity) -> customizeMaxQueueCapacity(factory, maxQueueCapacity)); map.from(this.serverProperties.getMaxHttpRequestHeaderSize()) .asInt(DataSize::toBytes) .when(this::isPositive) @@ -149,6 +155,21 @@ private boolean isPositive(int value) { return value > 0; } + @SuppressWarnings("rawtypes") + private void customizeMaxThreads(ConfigurableTomcatWebServerFactory factory, int maxThreads) { + customizeHandler(factory, maxThreads, AbstractProtocol.class, AbstractProtocol::setMaxThreads); + } + + @SuppressWarnings("rawtypes") + private void customizeMinThreads(ConfigurableTomcatWebServerFactory factory, int minSpareThreads) { + customizeHandler(factory, minSpareThreads, AbstractProtocol.class, AbstractProtocol::setMinSpareThreads); + } + + @SuppressWarnings("rawtypes") + private void customizeMaxQueueCapacity(ConfigurableTomcatWebServerFactory factory, int maxQueueCapacity) { + customizeHandler(factory, maxQueueCapacity, AbstractProtocol.class, AbstractProtocol::setMaxQueueSize); + } + @SuppressWarnings("rawtypes") private void customizeAcceptCount(ConfigurableTomcatWebServerFactory factory, int acceptCount) { customizeHandler(factory, acceptCount, AbstractProtocol.class, AbstractProtocol::setAcceptCount); @@ -249,16 +270,6 @@ private boolean getOrDeduceUseForwardHeaders() { return this.serverProperties.getForwardHeadersStrategy() == ServerProperties.ForwardHeadersStrategy.NATIVE; } - @SuppressWarnings("rawtypes") - private void customizeMaxThreads(ConfigurableTomcatWebServerFactory factory, int maxThreads) { - customizeHandler(factory, maxThreads, AbstractProtocol.class, AbstractProtocol::setMaxThreads); - } - - @SuppressWarnings("rawtypes") - private void customizeMinThreads(ConfigurableTomcatWebServerFactory factory, int minSpareThreads) { - customizeHandler(factory, minSpareThreads, AbstractProtocol.class, AbstractProtocol::setMinSpareThreads); - } - @SuppressWarnings("rawtypes") private void customizeMaxHttpRequestHeaderSize(ConfigurableTomcatWebServerFactory factory, int maxHttpRequestHeaderSize) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfiguration.java index 7a6c32c0933f..b59914875f79 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ * * @author Brian Clozel * @author Stephane Nicoll + * @author Lasse Wulff * @since 2.0.0 */ @AutoConfiguration(after = { WebFluxAutoConfiguration.class }) @@ -60,8 +61,11 @@ public AnnotationConfig(ApplicationContext applicationContext) { } @Bean - public HttpHandler httpHandler(ObjectProvider propsProvider) { - HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(this.applicationContext).build(); + public HttpHandler httpHandler(ObjectProvider propsProvider, + ObjectProvider handlerBuilderCustomizers) { + WebHttpHandlerBuilder handlerBuilder = WebHttpHandlerBuilder.applicationContext(this.applicationContext); + handlerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(handlerBuilder)); + HttpHandler httpHandler = handlerBuilder.build(); WebFluxProperties properties = propsProvider.getIfAvailable(); if (properties != null && StringUtils.hasText(properties.getBasePath())) { Map handlersMap = Collections.singletonMap(properties.getBasePath(), httpHandler); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveMultipartAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveMultipartAutoConfiguration.java index 722c5706f31a..45ba63007162 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveMultipartAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveMultipartAutoConfiguration.java @@ -78,6 +78,10 @@ else if (codec instanceof PartEventHttpMessageReader partEventHttpMessageReader) map.from(multipartProperties::getMaxHeadersSize) .asInt(DataSize::toBytes) .to(partEventHttpMessageReader::setMaxHeadersSize); + map.from(multipartProperties::getMaxDiskUsagePerPart) + .as(DataSize::toBytes) + .to(partEventHttpMessageReader::setMaxPartSize); + map.from(multipartProperties::getMaxParts).to(partEventHttpMessageReader::setMaxParts); map.from(multipartProperties::getHeadersCharset).to(partEventHttpMessageReader::setHeadersCharset); } }); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveMultipartProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveMultipartProperties.java index b1bd6d7854d9..01b4e005a2eb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveMultipartProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveMultipartProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ public class ReactiveMultipartProperties { /** * Maximum amount of memory allowed per part before it's written to disk. Set to -1 to - * store all contents in memory. Ignored when streaming is enabled. + * store all contents in memory. */ private DataSize maxInMemorySize = DataSize.ofKilobytes(256); @@ -49,7 +49,7 @@ public class ReactiveMultipartProperties { /** * Maximum amount of disk space allowed per part. Default is -1 which enforces no - * limits. Ignored when streaming is enabled. + * limits. */ private DataSize maxDiskUsagePerPart = DataSize.ofBytes(-1); @@ -62,7 +62,7 @@ public class ReactiveMultipartProperties { /** * Directory used to store file parts larger than 'maxInMemorySize'. Default is a * directory named 'spring-multipart' created under the system temporary directory. - * Ignored when streaming is enabled. + * Ignored when using the PartEvent streaming support. */ private String fileStorageDirectory; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryConfiguration.java index 2d92ced3d6e8..74880e24d3c3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryConfiguration.java @@ -17,7 +17,7 @@ package org.springframework.boot.autoconfigure.web.reactive; import io.undertow.Undertow; -import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.ee10.servlet.ServletHolder; import reactor.netty.http.server.HttpServer; import org.springframework.beans.factory.ObjectProvider; @@ -39,8 +39,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.http.client.reactive.JettyResourceFactory; -import org.springframework.http.client.reactive.ReactorResourceFactory; +import org.springframework.http.client.ReactorResourceFactory; /** * Configuration classes for reactive web servers @@ -97,17 +96,10 @@ TomcatReactiveWebServerFactory tomcatReactiveWebServerFactory( static class EmbeddedJetty { @Bean - @ConditionalOnMissingBean - JettyResourceFactory jettyServerResourceFactory() { - return new JettyResourceFactory(); - } - - @Bean - JettyReactiveWebServerFactory jettyReactiveWebServerFactory(JettyResourceFactory resourceFactory, + JettyReactiveWebServerFactory jettyReactiveWebServerFactory( ObjectProvider serverCustomizers) { JettyReactiveWebServerFactory serverFactory = new JettyReactiveWebServerFactory(); serverFactory.getServerCustomizers().addAll(serverCustomizers.orderedStream().toList()); - serverFactory.setResourceFactory(resourceFactory); return serverFactory; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java index 54b1effcb9cb..be7e3e3dda4e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration; +import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; +import org.springframework.boot.autoconfigure.thread.Threading; import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.autoconfigure.validation.ValidatorAdapter; import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain; @@ -54,12 +56,15 @@ import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.core.env.Environment; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.format.FormatterRegistry; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.util.ClassUtils; import org.springframework.validation.Validator; import org.springframework.web.filter.reactive.HiddenHttpMethodFilter; +import org.springframework.web.reactive.config.BlockingExecutionConfigurer; import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.config.ResourceHandlerRegistration; @@ -146,6 +151,8 @@ public static class WebFluxConfig implements WebFluxConfigurer { private static final Log logger = LogFactory.getLog(WebFluxConfig.class); + private final Environment environment; + private final Resources resourceProperties; private final WebFluxProperties webFluxProperties; @@ -160,11 +167,12 @@ public static class WebFluxConfig implements WebFluxConfigurer { private final ObjectProvider viewResolvers; - public WebFluxConfig(WebProperties webProperties, WebFluxProperties webFluxProperties, + public WebFluxConfig(Environment environment, WebProperties webProperties, WebFluxProperties webFluxProperties, ListableBeanFactory beanFactory, ObjectProvider resolvers, ObjectProvider codecCustomizers, ObjectProvider resourceHandlerRegistrationCustomizer, ObjectProvider viewResolvers) { + this.environment = environment; this.resourceProperties = webProperties.getResources(); this.webFluxProperties = webFluxProperties; this.beanFactory = beanFactory; @@ -184,6 +192,18 @@ public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { this.codecCustomizers.orderedStream().forEach((customizer) -> customizer.customize(configurer)); } + @Override + public void configureBlockingExecution(BlockingExecutionConfigurer configurer) { + if (Threading.VIRTUAL.isActive(this.environment) && this.beanFactory + .containsBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)) { + Object taskExecutor = this.beanFactory + .getBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME); + if (taskExecutor instanceof AsyncTaskExecutor asyncTaskExecutor) { + configurer.setExecutor(asyncTaskExecutor); + } + } + } + @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { @@ -318,7 +338,10 @@ public LocaleContextResolver localeContextResolver() { public WebSessionManager webSessionManager(ObjectProvider webSessionIdResolver) { DefaultWebSessionManager webSessionManager = new DefaultWebSessionManager(); Duration timeout = this.serverProperties.getReactive().getSession().getTimeout(); - webSessionManager.setSessionStore(new MaxIdleTimeInMemoryWebSessionStore(timeout)); + int maxSessions = this.serverProperties.getReactive().getSession().getMaxSessions(); + MaxIdleTimeInMemoryWebSessionStore sessionStore = new MaxIdleTimeInMemoryWebSessionStore(timeout); + sessionStore.setMaxSessions(maxSessions); + webSessionManager.setSessionStore(sessionStore); webSessionIdResolver.ifAvailable(webSessionManager::setSessionIdResolver); return webSessionManager; } @@ -343,6 +366,7 @@ static class ProblemDetailsErrorHandlingConfiguration { @Bean @ConditionalOnMissingBean(ResponseEntityExceptionHandler.class) + @Order(0) ProblemDetailsExceptionHandler problemDetailsExceptionHandler() { return new ProblemDetailsExceptionHandler(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java index 0d0f2359c2fc..670a81f3f5f4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -99,17 +99,20 @@ public void setWebjarsPathPattern(String webjarsPathPattern) { public static class Format { /** - * Date format to use, for example 'dd/MM/yyyy'. + * Date format to use, for example 'dd/MM/yyyy'. Used for formatting of + * java.util.Date and java.time.LocalDate. */ private String date; /** - * Time format to use, for example 'HH:mm:ss'. + * Time format to use, for example 'HH:mm:ss'. Used for formatting of java.time's + * LocalTime and OffsetTime. */ private String time; /** - * Date-time format to use, for example 'yyyy-MM-dd HH:mm:ss'. + * Date-time format to use, for example 'yyyy-MM-dd HH:mm:ss'. Used for formatting + * of java.time's LocalDateTime, OffsetDateTime, and ZonedDateTime. */ private String dateTime; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebHttpHandlerBuilderCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebHttpHandlerBuilderCustomizer.java new file mode 100644 index 000000000000..b416e066c7c8 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebHttpHandlerBuilderCustomizer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.reactive; + +import org.springframework.web.server.adapter.WebHttpHandlerBuilder; + +/** + * Callback interface used to customize a {@link WebHttpHandlerBuilder}. + * + * @author Lasse Wulff + * @since 3.3.0 + */ +@FunctionalInterface +public interface WebHttpHandlerBuilderCustomizer { + + /** + * Callback to customize a {@link WebHttpHandlerBuilder} instance. + * @param webHttpHandlerBuilder the {@link WebHttpHandlerBuilder} to customize + */ + void customize(WebHttpHandlerBuilder webHttpHandlerBuilder); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java index 1e8ddc1fd1f0..c601654d0e42 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java @@ -18,10 +18,8 @@ import java.util.Collections; import java.util.Date; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import org.apache.commons.logging.Log; import reactor.core.publisher.Mono; @@ -33,7 +31,6 @@ import org.springframework.boot.web.reactive.error.ErrorAttributes; import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; import org.springframework.context.ApplicationContext; -import org.springframework.core.NestedExceptionUtils; import org.springframework.core.io.Resource; import org.springframework.core.log.LogMessage; import org.springframework.http.HttpLogging; @@ -49,6 +46,7 @@ import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.util.DisconnectedClientHelper; import org.springframework.web.util.HtmlUtils; /** @@ -56,25 +54,12 @@ * * @author Brian Clozel * @author Scott Frederick + * @author Moritz Halbritter * @since 2.0.0 * @see ErrorAttributes */ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExceptionHandler, InitializingBean { - /** - * Currently duplicated from Spring WebFlux HttpWebHandlerAdapter. - */ - private static final Set DISCONNECTED_CLIENT_EXCEPTIONS; - - static { - Set exceptions = new HashSet<>(); - exceptions.add("AbortedException"); - exceptions.add("ClientAbortException"); - exceptions.add("EOFException"); - exceptions.add("EofException"); - DISCONNECTED_CLIENT_EXCEPTIONS = Collections.unmodifiableSet(exceptions); - } - private static final Log logger = HttpLogging.forLogName(AbstractErrorWebExceptionHandler.class); private final ApplicationContext applicationContext; @@ -184,6 +169,17 @@ protected boolean isBindingErrorsEnabled(ServerRequest request) { return getBooleanParameter(request, "errors"); } + /** + * Check whether the path attribute has been set on the given request. + * @param request the source request + * @return {@code true} if the path attribute has been requested, {@code false} + * otherwise + * @since 3.3.0 + */ + protected boolean isPathEnabled(ServerRequest request) { + return getBooleanParameter(request, "path"); + } + private boolean getBooleanParameter(ServerRequest request, String parameterName) { String parameter = request.queryParam(parameterName).orElse("false"); return !"false".equalsIgnoreCase(parameter); @@ -306,13 +302,7 @@ public Mono handle(ServerWebExchange exchange, Throwable throwable) { } private boolean isDisconnectedClientError(Throwable ex) { - return DISCONNECTED_CLIENT_EXCEPTIONS.contains(ex.getClass().getSimpleName()) - || isDisconnectedClientErrorMessage(NestedExceptionUtils.getMostSpecificCause(ex).getMessage()); - } - - private boolean isDisconnectedClientErrorMessage(String message) { - message = (message != null) ? message.toLowerCase() : ""; - return (message.contains("broken pipe") || message.contains("connection reset by peer")); + return DisconnectedClientHelper.isClientDisconnectedException(ex); } /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java index 5bc52de7c76f..5ce76162ef58 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.MediaType; +import org.springframework.util.Assert; import org.springframework.util.MimeTypeUtils; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.RequestPredicate; @@ -75,6 +76,7 @@ * * @author Brian Clozel * @author Scott Frederick + * @author Moritz Halbritter * @since 2.0.0 */ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler { @@ -90,6 +92,8 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa SERIES_VIEWS = Collections.unmodifiableMap(views); } + private static final ErrorAttributeOptions ONLY_STATUS = ErrorAttributeOptions.of(Include.STATUS); + private final ErrorProperties errorProperties; /** @@ -117,13 +121,13 @@ protected RouterFunction getRoutingFunction(ErrorAttributes erro * @return a {@code Publisher} of the HTTP response */ protected Mono renderErrorView(ServerRequest request) { - Map error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)); - int errorStatus = getHttpStatus(error); - ServerResponse.BodyBuilder responseBody = ServerResponse.status(errorStatus).contentType(TEXT_HTML_UTF8); - return Flux.just(getData(errorStatus).toArray(new String[] {})) - .flatMap((viewName) -> renderErrorView(viewName, responseBody, error)) + int status = getHttpStatus(getErrorAttributes(request, ONLY_STATUS)); + Map errorAttributes = getErrorAttributes(request, MediaType.TEXT_HTML); + ServerResponse.BodyBuilder responseBody = ServerResponse.status(status).contentType(TEXT_HTML_UTF8); + return Flux.just(getData(status).toArray(new String[] {})) + .flatMap((viewName) -> renderErrorView(viewName, responseBody, errorAttributes)) .switchIfEmpty(this.errorProperties.getWhitelabel().isEnabled() - ? renderDefaultErrorView(responseBody, error) : Mono.error(getError(request))) + ? renderDefaultErrorView(responseBody, errorAttributes) : Mono.error(getError(request))) .next(); } @@ -144,10 +148,15 @@ private List getData(int errorStatus) { * @return a {@code Publisher} of the HTTP response */ protected Mono renderErrorResponse(ServerRequest request) { - Map error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL)); - return ServerResponse.status(getHttpStatus(error)) + int status = getHttpStatus(getErrorAttributes(request, ONLY_STATUS)); + Map errorAttributes = getErrorAttributes(request, MediaType.ALL); + return ServerResponse.status(status) .contentType(MediaType.APPLICATION_JSON) - .body(BodyInserters.fromValue(error)); + .body(BodyInserters.fromValue(errorAttributes)); + } + + private Map getErrorAttributes(ServerRequest request, MediaType mediaType) { + return getErrorAttributes(request, getErrorAttributeOptions(request, mediaType)); } protected ErrorAttributeOptions getErrorAttributeOptions(ServerRequest request, MediaType mediaType) { @@ -164,6 +173,7 @@ protected ErrorAttributeOptions getErrorAttributeOptions(ServerRequest request, if (isIncludeBindingErrors(request, mediaType)) { options = options.including(Include.BINDING_ERRORS); } + options = isIncludePath(request, mediaType) ? options.including(Include.PATH) : options.excluding(Include.PATH); return options; } @@ -177,7 +187,7 @@ protected boolean isIncludeStackTrace(ServerRequest request, MediaType produces) return switch (this.errorProperties.getIncludeStacktrace()) { case ALWAYS -> true; case ON_PARAM -> isTraceEnabled(request); - default -> false; + case NEVER -> false; }; } @@ -191,7 +201,7 @@ protected boolean isIncludeMessage(ServerRequest request, MediaType produces) { return switch (this.errorProperties.getIncludeMessage()) { case ALWAYS -> true; case ON_PARAM -> isMessageEnabled(request); - default -> false; + case NEVER -> false; }; } @@ -205,7 +215,22 @@ protected boolean isIncludeBindingErrors(ServerRequest request, MediaType produc return switch (this.errorProperties.getIncludeBindingErrors()) { case ALWAYS -> true; case ON_PARAM -> isBindingErrorsEnabled(request); - default -> false; + case NEVER -> false; + }; + } + + /** + * Determine if the path attribute should be included. + * @param request the source request + * @param produces the media type produced (or {@code MediaType.ALL}) + * @return if the path attribute should be included + * @since 3.3.0 + */ + protected boolean isIncludePath(ServerRequest request, MediaType produces) { + return switch (this.errorProperties.getIncludePath()) { + case ALWAYS -> true; + case ON_PARAM -> isPathEnabled(request); + case NEVER -> false; }; } @@ -215,7 +240,9 @@ protected boolean isIncludeBindingErrors(ServerRequest request, MediaType produc * @return the error HTTP status */ protected int getHttpStatus(Map errorAttributes) { - return (int) errorAttributes.get("status"); + Object status = errorAttributes.get("status"); + Assert.state(status instanceof Integer, "ErrorAttributes must contain a status integer"); + return (int) status; } /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfiguration.java index 34bf67e605a8..308ac80f5031 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfiguration.java @@ -46,7 +46,6 @@ @ConditionalOnClass(WebClient.class) @AutoConfigureAfter(SslAutoConfiguration.class) @Import({ ClientHttpConnectorFactoryConfiguration.ReactorNetty.class, - ClientHttpConnectorFactoryConfiguration.JettyClient.class, ClientHttpConnectorFactoryConfiguration.HttpClient5.class, ClientHttpConnectorFactoryConfiguration.JdkClient.class }) public class ClientHttpConnectorAutoConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorFactoryConfiguration.java index cf0769bc0cda..d3f9aa1b0d8b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorFactoryConfiguration.java @@ -27,8 +27,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.http.client.reactive.JettyResourceFactory; -import org.springframework.http.client.reactive.ReactorResourceFactory; +import org.springframework.http.client.ReactorResourceFactory; /** * Configuration classes for WebClient client connectors. @@ -55,24 +54,6 @@ ReactorClientHttpConnectorFactory reactorClientHttpConnectorFactory( } - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(org.eclipse.jetty.reactive.client.ReactiveRequest.class) - @ConditionalOnMissingBean(ClientHttpConnectorFactory.class) - static class JettyClient { - - @Bean - @ConditionalOnMissingBean - JettyResourceFactory jettyClientResourceFactory() { - return new JettyResourceFactory(); - } - - @Bean - JettyClientHttpConnectorFactory jettyClientHttpConnectorFactory(JettyResourceFactory jettyResourceFactory) { - return new JettyClientHttpConnectorFactory(jettyResourceFactory); - } - - } - @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ HttpAsyncClients.class, AsyncRequestProducer.class, ReactiveResponseConsumer.class }) @ConditionalOnMissingBean(ClientHttpConnectorFactory.class) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/JettyClientHttpConnectorFactory.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/JettyClientHttpConnectorFactory.java deleted file mode 100644 index 5824abf1ca92..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/JettyClientHttpConnectorFactory.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.web.reactive.function.client; - -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; -import org.eclipse.jetty.io.ClientConnector; -import org.eclipse.jetty.util.ssl.SslContextFactory; - -import org.springframework.boot.ssl.SslBundle; -import org.springframework.boot.ssl.SslOptions; -import org.springframework.http.client.reactive.JettyClientHttpConnector; -import org.springframework.http.client.reactive.JettyResourceFactory; - -/** - * {@link ClientHttpConnectorFactory} for {@link JettyClientHttpConnector}. - * - * @author Phillip Webb - */ -class JettyClientHttpConnectorFactory implements ClientHttpConnectorFactory { - - private final JettyResourceFactory jettyResourceFactory; - - JettyClientHttpConnectorFactory(JettyResourceFactory jettyResourceFactory) { - this.jettyResourceFactory = jettyResourceFactory; - } - - @Override - public JettyClientHttpConnector createClientHttpConnector(SslBundle sslBundle) { - SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); - if (sslBundle != null) { - SslOptions options = sslBundle.getOptions(); - if (options.getCiphers() != null) { - sslContextFactory.setIncludeCipherSuites(options.getCiphers()); - sslContextFactory.setExcludeCipherSuites(); - } - if (options.getEnabledProtocols() != null) { - sslContextFactory.setIncludeProtocols(options.getEnabledProtocols()); - sslContextFactory.setExcludeProtocols(); - } - sslContextFactory.setSslContext(sslBundle.createSslContext()); - } - ClientConnector connector = new ClientConnector(); - connector.setSslContextFactory(sslContextFactory); - HttpClientTransportOverHTTP transport = new HttpClientTransportOverHTTP(connector); - HttpClient httpClient = new HttpClient(transport); - return new JettyClientHttpConnector(httpClient, this.jettyResourceFactory); - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ReactorClientHttpConnectorFactory.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ReactorClientHttpConnectorFactory.java index b5dcd6b136a1..3f596126ac51 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ReactorClientHttpConnectorFactory.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ReactorClientHttpConnectorFactory.java @@ -31,8 +31,8 @@ import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslManagerBundle; import org.springframework.boot.ssl.SslOptions; +import org.springframework.http.client.ReactorResourceFactory; import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.http.client.reactive.ReactorResourceFactory; import org.springframework.util.function.ThrowingConsumer; /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java index 4e29ba2e0f10..e32f25072d2d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java @@ -89,7 +89,6 @@ public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) { DispatcherServlet dispatcherServlet = new DispatcherServlet(); dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest()); dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest()); - dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound()); dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents()); dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails()); return dispatcherServlet; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/MultipartAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/MultipartAutoConfiguration.java index f91fa1ec8ac2..9bac3910ac20 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/MultipartAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/MultipartAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,6 +46,7 @@ * @author Greg Turnquist * @author Josh Long * @author Toshiaki Maki + * @author Yanming Zhou * @since 2.0.0 */ @AutoConfiguration @@ -72,6 +73,7 @@ public MultipartConfigElement multipartConfigElement() { public StandardServletMultipartResolver multipartResolver() { StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver(); multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily()); + multipartResolver.setStrictServletCompliance(this.multipartProperties.isStrictServletCompliance()); return multipartResolver; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/MultipartProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/MultipartProperties.java index 1388ae73bbb1..6e38a93927dd 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/MultipartProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/MultipartProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,6 +43,7 @@ * @author Josh Long * @author Toshiaki Maki * @author Stephane Nicoll + * @author Yanming Zhou * @since 2.0.0 */ @ConfigurationProperties(prefix = "spring.servlet.multipart", ignoreUnknownFields = false) @@ -79,6 +80,12 @@ public class MultipartProperties { */ private boolean resolveLazily = false; + /** + * Whether to resolve the multipart request strictly complying with the Servlet + * specification, only to be used for "multipart/form-data" requests. + */ + private boolean strictServletCompliance = false; + public boolean getEnabled() { return this.enabled; } @@ -127,6 +134,14 @@ public void setResolveLazily(boolean resolveLazily) { this.resolveLazily = resolveLazily; } + public boolean isStrictServletCompliance() { + return this.strictServletCompliance; + } + + public void setStrictServletCompliance(boolean strictServletCompliance) { + this.strictServletCompliance = strictServletCompliance; + } + /** * Create a new {@link MultipartConfigElement} using the properties. * @return a new {@link MultipartConfigElement} configured using there properties diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration.java index b33e8eaf4ced..266b298eeb25 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration.java @@ -20,9 +20,9 @@ import jakarta.servlet.Servlet; import org.apache.catalina.startup.Tomcat; import org.apache.coyote.UpgradeProtocol; +import org.eclipse.jetty.ee10.webapp.WebAppContext; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.Loader; -import org.eclipse.jetty.webapp.WebAppContext; import org.xnio.SslClientAuthMode; import org.springframework.beans.factory.ObjectProvider; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryCustomizer.java index 70706f939749..bc1dbd4e2489 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,6 +38,7 @@ * @author Olivier Lamy * @author Yunkun Huang * @author Scott Frederick + * @author Lasse Wulff * @since 2.0.0 */ public class ServletWebServerFactoryCustomizer @@ -94,6 +95,7 @@ public void customize(ConfigurableServletWebServerFactory factory) { map.from(() -> this.cookieSameSiteSuppliers) .whenNot(CollectionUtils::isEmpty) .to(factory::setCookieSameSiteSuppliers); + map.from(this.serverProperties::getMimeMappings).to(factory::addMimeMappings); this.webListenerRegistrars.forEach((registrar) -> registrar.register(factory)); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java index ada52f53cd8c..937d4b4f0edf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java @@ -31,7 +31,6 @@ import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -96,6 +95,7 @@ import org.springframework.web.servlet.FlashMapManager; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.RequestToViewNameTranslator; import org.springframework.web.servlet.View; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; @@ -403,24 +403,6 @@ public EnableWebMvcConfiguration(WebMvcProperties mvcProperties, WebProperties w this.beanFactory = beanFactory; } - @Bean - @Override - public RequestMappingHandlerAdapter requestMappingHandlerAdapter( - @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, - @Qualifier("mvcConversionService") FormattingConversionService conversionService, - @Qualifier("mvcValidator") Validator validator) { - RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager, - conversionService, validator); - setIgnoreDefaultModelOnRedirect(adapter); - return adapter; - } - - @SuppressWarnings({ "deprecation", "removal" }) - private void setIgnoreDefaultModelOnRedirect(RequestMappingHandlerAdapter adapter) { - adapter.setIgnoreDefaultModelOnRedirect( - this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect()); - } - @Override protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() { if (this.mvcRegistrations != null) { @@ -488,6 +470,13 @@ public FlashMapManager flashMapManager() { return super.flashMapManager(); } + @Override + @Bean + @ConditionalOnMissingBean(name = DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME) + public RequestToViewNameTranslator viewNameTranslator() { + return super.viewNameTranslator(); + } + private Resource getIndexHtmlResource() { for (String location : this.resourceProperties.getStaticLocations()) { Resource indexHtml = getIndexHtmlResource(location); @@ -682,6 +671,7 @@ static class ProblemDetailsErrorHandlingConfiguration { @Bean @ConditionalOnMissingBean(ResponseEntityExceptionHandler.class) + @Order(0) ProblemDetailsExceptionHandler problemDetailsExceptionHandler() { return new ProblemDetailsExceptionHandler(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java index 0f84b1b4b2fd..e046b2fea269 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,6 @@ import java.util.Map; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.validation.DefaultMessageCodesResolver; @@ -57,23 +56,11 @@ public class WebMvcProperties { */ private boolean dispatchOptionsRequest = true; - /** - * Whether the content of the "default" model should be ignored during redirect - * scenarios. - */ - private boolean ignoreDefaultModelOnRedirect = true; - /** * Whether to publish a ServletRequestHandledEvent at the end of each request. */ private boolean publishRequestHandledEvents = true; - /** - * Whether a "NoHandlerFoundException" should be thrown if no Handler was found to - * process a request. - */ - private boolean throwExceptionIfNoHandlerFound = false; - /** * Whether logging of (potentially sensitive) request details at DEBUG and TRACE level * is allowed. @@ -120,17 +107,6 @@ public Format getFormat() { return this.format; } - @Deprecated(since = "3.0.0", forRemoval = true) - @DeprecatedConfigurationProperty(reason = "Deprecated for removal in Spring MVC") - public boolean isIgnoreDefaultModelOnRedirect() { - return this.ignoreDefaultModelOnRedirect; - } - - @Deprecated(since = "3.0.0", forRemoval = true) - public void setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect) { - this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect; - } - public boolean isPublishRequestHandledEvents() { return this.publishRequestHandledEvents; } @@ -139,14 +115,6 @@ public void setPublishRequestHandledEvents(boolean publishRequestHandledEvents) this.publishRequestHandledEvents = publishRequestHandledEvents; } - public boolean isThrowExceptionIfNoHandlerFound() { - return this.throwExceptionIfNoHandlerFound; - } - - public void setThrowExceptionIfNoHandlerFound(boolean throwExceptionIfNoHandlerFound) { - this.throwExceptionIfNoHandlerFound = throwExceptionIfNoHandlerFound; - } - public boolean isLogRequestDetails() { return this.logRequestDetails; } @@ -395,17 +363,20 @@ public void setMatchingStrategy(MatchingStrategy matchingStrategy) { public static class Format { /** - * Date format to use, for example 'dd/MM/yyyy'. + * Date format to use, for example 'dd/MM/yyyy'. Used for formatting of + * java.util.Date and java.time.LocalDate. */ private String date; /** - * Time format to use, for example 'HH:mm:ss'. + * Time format to use, for example 'HH:mm:ss'. Used for formatting of java.time's + * LocalTime and OffsetTime. */ private String time; /** - * Date-time format to use, for example 'yyyy-MM-dd HH:mm:ss'. + * Date-time format to use, for example 'yyyy-MM-dd HH:mm:ss'. Used for formatting + * of java.time's LocalDateTime, OffsetDateTime, and ZonedDateTime. */ private String dateTime; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/AbstractErrorController.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/AbstractErrorController.java index 4777a492d666..3fbfbd3b38c1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/AbstractErrorController.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/AbstractErrorController.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ * @author Dave Syer * @author Phillip Webb * @author Scott Frederick + * @author Moritz Halbritter * @since 1.3.0 * @see ErrorAttributes */ @@ -74,18 +75,43 @@ protected Map getErrorAttributes(HttpServletRequest request, Err return this.errorAttributes.getErrorAttributes(webRequest, options); } + /** + * Returns whether the trace parameter is set. + * @param request the request + * @return whether the trace parameter is set + */ protected boolean getTraceParameter(HttpServletRequest request) { return getBooleanParameter(request, "trace"); } + /** + * Returns whether the message parameter is set. + * @param request the request + * @return whether the message parameter is set + */ protected boolean getMessageParameter(HttpServletRequest request) { return getBooleanParameter(request, "message"); } + /** + * Returns whether the errors parameter is set. + * @param request the request + * @return whether the errors parameter is set + */ protected boolean getErrorsParameter(HttpServletRequest request) { return getBooleanParameter(request, "errors"); } + /** + * Returns whether the path parameter is set. + * @param request the request + * @return whether the path parameter is set + * @since 3.3.0 + */ + protected boolean getPathParameter(HttpServletRequest request) { + return getBooleanParameter(request, "path"); + } + protected boolean getBooleanParameter(HttpServletRequest request, String parameterName) { String parameter = request.getParameter(parameterName); if (parameter == null) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java index 610f24517f3f..add6851f2ec0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,6 +49,7 @@ * @author Michael Stummvoll * @author Stephane Nicoll * @author Scott Frederick + * @author Moritz Halbritter * @since 1.0.0 * @see ErrorAttributes * @see ErrorProperties @@ -121,6 +122,7 @@ protected ErrorAttributeOptions getErrorAttributeOptions(HttpServletRequest requ if (isIncludeBindingErrors(request, mediaType)) { options = options.including(Include.BINDING_ERRORS); } + options = isIncludePath(request, mediaType) ? options.including(Include.PATH) : options.excluding(Include.PATH); return options; } @@ -134,7 +136,7 @@ protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType prod return switch (getErrorProperties().getIncludeStacktrace()) { case ALWAYS -> true; case ON_PARAM -> getTraceParameter(request); - default -> false; + case NEVER -> false; }; } @@ -148,7 +150,7 @@ protected boolean isIncludeMessage(HttpServletRequest request, MediaType produce return switch (getErrorProperties().getIncludeMessage()) { case ALWAYS -> true; case ON_PARAM -> getMessageParameter(request); - default -> false; + case NEVER -> false; }; } @@ -162,7 +164,22 @@ protected boolean isIncludeBindingErrors(HttpServletRequest request, MediaType p return switch (getErrorProperties().getIncludeBindingErrors()) { case ALWAYS -> true; case ON_PARAM -> getErrorsParameter(request); - default -> false; + case NEVER -> false; + }; + } + + /** + * Determine if the path attribute should be included. + * @param request the source request + * @param produces the media type produced (or {@code MediaType.ALL}) + * @return if the path attribute should be included + * @since 3.3.0 + */ + protected boolean isIncludePath(HttpServletRequest request, MediaType produces) { + return switch (getErrorProperties().getIncludePath()) { + case ALWAYS -> true; + case ON_PARAM -> getPathParameter(request); + case NEVER -> false; }; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/reactive/JettyWebSocketReactiveWebServerCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/reactive/JettyWebSocketReactiveWebServerCustomizer.java index 4c050c4237ed..00ca570b9d6c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/reactive/JettyWebSocketReactiveWebServerCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/reactive/JettyWebSocketReactiveWebServerCustomizer.java @@ -17,15 +17,13 @@ package org.springframework.boot.autoconfigure.websocket.reactive; import jakarta.servlet.ServletContext; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.websocket.jakarta.server.JakartaWebSocketServerContainer; +import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServerContainer; +import org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter; import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.handler.HandlerCollection; -import org.eclipse.jetty.server.handler.HandlerWrapper; -import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.websocket.core.server.WebSocketMappings; import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents; -import org.eclipse.jetty.websocket.jakarta.server.internal.JakartaWebSocketServerContainer; -import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer; -import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter; import org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; @@ -47,13 +45,13 @@ public void customize(JettyReactiveWebServerFactory factory) { if (servletContextHandler != null) { ServletContext servletContext = servletContextHandler.getServletContext(); if (JettyWebSocketServerContainer.getContainer(servletContext) == null) { - WebSocketServerComponents.ensureWebSocketComponents(server, servletContext); + WebSocketServerComponents.ensureWebSocketComponents(server, servletContextHandler); JettyWebSocketServerContainer.ensureContainer(servletContext); } if (JakartaWebSocketServerContainer.getContainer(servletContext) == null) { - WebSocketServerComponents.ensureWebSocketComponents(server, servletContext); + WebSocketServerComponents.ensureWebSocketComponents(server, servletContextHandler); WebSocketUpgradeFilter.ensureFilter(servletContext); - WebSocketMappings.ensureMappings(servletContext); + WebSocketMappings.ensureMappings(servletContextHandler); JakartaWebSocketServerContainer.ensureContainer(servletContext); } } @@ -64,10 +62,10 @@ private ServletContextHandler findServletContextHandler(Handler handler) { if (handler instanceof ServletContextHandler servletContextHandler) { return servletContextHandler; } - if (handler instanceof HandlerWrapper handlerWrapper) { + if (handler instanceof Handler.Wrapper handlerWrapper) { return findServletContextHandler(handlerWrapper.getHandler()); } - if (handler instanceof HandlerCollection handlerCollection) { + if (handler instanceof Handler.Collection handlerCollection) { for (Handler contained : handlerCollection.getHandlers()) { ServletContextHandler servletContextHandler = findServletContextHandler(contained); if (servletContextHandler != null) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/reactive/WebSocketReactiveAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/reactive/WebSocketReactiveAutoConfiguration.java index 2f26b1698582..3592ba14b3cd 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/reactive/WebSocketReactiveAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/reactive/WebSocketReactiveAutoConfiguration.java @@ -20,7 +20,7 @@ import jakarta.websocket.server.ServerContainer; import org.apache.catalina.startup.Tomcat; import org.apache.tomcat.websocket.server.WsSci; -import org.eclipse.jetty.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; +import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/JettyWebSocketServletWebServerCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/JettyWebSocketServletWebServerCustomizer.java index 8ee41bc966ca..ccf0ef8f379e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/JettyWebSocketServletWebServerCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/JettyWebSocketServletWebServerCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,13 @@ package org.springframework.boot.autoconfigure.websocket.servlet; -import org.eclipse.jetty.webapp.AbstractConfiguration; -import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.ee10.webapp.AbstractConfiguration; +import org.eclipse.jetty.ee10.webapp.WebAppContext; +import org.eclipse.jetty.ee10.websocket.jakarta.server.JakartaWebSocketServerContainer; +import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServerContainer; +import org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter; import org.eclipse.jetty.websocket.core.server.WebSocketMappings; import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents; -import org.eclipse.jetty.websocket.jakarta.server.internal.JakartaWebSocketServerContainer; -import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer; -import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter; import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; @@ -41,20 +41,20 @@ public class JettyWebSocketServletWebServerCustomizer @Override public void customize(JettyServletWebServerFactory factory) { - factory.addConfigurations(new AbstractConfiguration() { + factory.addConfigurations(new AbstractConfiguration(new AbstractConfiguration.Builder()) { @Override public void configure(WebAppContext context) throws Exception { if (JettyWebSocketServerContainer.getContainer(context.getServletContext()) == null) { WebSocketServerComponents.ensureWebSocketComponents(context.getServer(), - context.getServletContext()); + context.getContext().getContextHandler()); JettyWebSocketServerContainer.ensureContainer(context.getServletContext()); } if (JakartaWebSocketServerContainer.getContainer(context.getServletContext()) == null) { WebSocketServerComponents.ensureWebSocketComponents(context.getServer(), - context.getServletContext()); + context.getContext().getContextHandler()); WebSocketUpgradeFilter.ensureFilter(context.getServletContext()); - WebSocketMappings.ensureMappings(context.getServletContext()); + WebSocketMappings.ensureMappings(context.getContext().getContextHandler()); JakartaWebSocketServerContainer.ensureContainer(context.getServletContext()); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketMessagingAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketMessagingAutoConfiguration.java index 939827774f3f..23afdce7b5a1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketMessagingAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketMessagingAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.websocket.servlet; import java.util.List; +import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; @@ -28,14 +29,17 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.messaging.converter.ByteArrayMessageConverter; import org.springframework.messaging.converter.DefaultContentTypeResolver; import org.springframework.messaging.converter.MappingJackson2MessageConverter; import org.springframework.messaging.converter.MessageConverter; import org.springframework.messaging.converter.StringMessageConverter; import org.springframework.messaging.simp.config.AbstractMessageBrokerConfiguration; +import org.springframework.messaging.simp.config.ChannelRegistration; import org.springframework.util.MimeTypeUtils; import org.springframework.web.socket.config.annotation.DelegatingWebSocketMessageBrokerConfiguration; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; @@ -44,6 +48,8 @@ * {@link EnableAutoConfiguration Auto-configuration} for WebSocket-based messaging. * * @author Andy Wilkinson + * @author Lasse Wulff + * @author Moritz Halbritter * @since 1.3.0 */ @AutoConfiguration(after = JacksonAutoConfiguration.class) @@ -58,14 +64,24 @@ static class WebSocketMessageConverterConfiguration implements WebSocketMessageB private final ObjectMapper objectMapper; - WebSocketMessageConverterConfiguration(ObjectMapper objectMapper) { + private final AsyncTaskExecutor executor; + + WebSocketMessageConverterConfiguration(ObjectMapper objectMapper, + Map taskExecutors) { this.objectMapper = objectMapper; + this.executor = determineAsyncTaskExecutor(taskExecutors); + } + + private static AsyncTaskExecutor determineAsyncTaskExecutor(Map taskExecutors) { + if (taskExecutors.size() == 1) { + return taskExecutors.values().iterator().next(); + } + return taskExecutors.get(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME); } @Override public boolean configureMessageConverters(List messageConverters) { - MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); - converter.setObjectMapper(this.objectMapper); + MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(this.objectMapper); DefaultContentTypeResolver resolver = new DefaultContentTypeResolver(); resolver.setDefaultMimeType(MimeTypeUtils.APPLICATION_JSON); converter.setContentTypeResolver(resolver); @@ -75,6 +91,20 @@ public boolean configureMessageConverters(List messageConverte return false; } + @Override + public void configureClientInboundChannel(ChannelRegistration registration) { + if (this.executor != null) { + registration.executor(this.executor); + } + } + + @Override + public void configureClientOutboundChannel(ChannelRegistration registration) { + if (this.executor != null) { + registration.executor(this.executor); + } + } + @Bean static LazyInitializationExcludeFilter eagerStompWebSocketHandlerMapping() { return (name, definition, type) -> name.equals("stompWebSocketHandlerMapping"); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketServletAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketServletAutoConfiguration.java index f7fed2b0d9d6..71dda5859aef 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketServletAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketServletAutoConfiguration.java @@ -24,8 +24,8 @@ import jakarta.websocket.server.ServerContainer; import org.apache.catalina.startup.Tomcat; import org.apache.tomcat.websocket.server.WsSci; -import org.eclipse.jetty.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; -import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter; +import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; +import org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index cfa1a2545619..b95c2ac173e2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -115,6 +115,13 @@ "level": "error" } }, + { + "name": "server.max-http-header-size", + "deprecation": { + "replacement": "server.max-http-request-header-size", + "level": "error" + } + }, { "name": "server.max-http-post-size", "type": "java.lang.Integer", @@ -125,6 +132,13 @@ "level": "error" } }, + { + "name": "server.netty.max-chunk-size", + "deprecation": { + "reason": "Deprecated for removal in Reactor Netty.", + "level": "error" + } + }, { "name": "server.port", "defaultValue": 8080 @@ -210,7 +224,10 @@ }, { "name": "server.servlet.session.cookie.comment", - "description": "Comment for the cookie." + "description": "Comment for the cookie.", + "deprecation": { + "level": "error" + } }, { "name": "server.servlet.session.cookie.domain", @@ -320,6 +337,10 @@ "description": "SSL protocol to use.", "defaultValue": "TLS" }, + { + "name": "server.ssl.server-name-bundles", + "description": "Mapping of host names to SSL bundles for SNI configuration." + }, { "name": "server.ssl.trust-certificate", "description": "Path to a PEM-encoded SSL certificate authority file." @@ -653,6 +674,26 @@ "level": "error" } }, + { + "name": "spring.couchbase.env.ssl.key-store", + "type": "java.lang.String", + "description": "Path to the JVM key store that holds the certificates.", + "deprecation": { + "replacement": "spring.couchbase.env.ssl.bundle", + "level": "error", + "since": "3.1.0" + } + }, + { + "name": "spring.couchbase.env.ssl.key-store-password", + "type": "java.lang.String", + "description": "Password used to access the key store.", + "deprecation": { + "replacement": "spring.couchbase.env.ssl.bundle", + "level": "error", + "since": "3.1.0" + } + }, { "name": "spring.couchbase.env.timeouts.socket-connect", "type": "java.time.Duration", @@ -1298,6 +1339,17 @@ "level": "error" } }, + { + "name": "spring.flyway.cherry-pick", + "description": "Migrations that Flyway should consider when migrating or undoing. When empty all available migrations are considered. Requires Flyway Teams.", + "deprecation": { + "level": "error", + "reason": "Removed in Flyway 10" + } + },{ + "name": "spring.flyway.community-db-support-enabled", + "defaultValue": false + }, { "name": "spring.flyway.dry-run-output", "type": "java.io.OutputStream", @@ -1354,6 +1406,14 @@ "replacement": "spring.flyway.ignore-migration-patterns" } }, + { + "name": "spring.flyway.license-key", + "description": "License key for Flyway Teams.", + "deprecation": { + "level": "error", + "reason": "Removed in Flyway 10" + } + }, { "name": "spring.flyway.locations", "sourceType": "org.springframework.boot.autoconfigure.flyway.FlywayProperties", @@ -1389,7 +1449,7 @@ "type": "java.lang.String", "deprecation": { "level": "error", - "reason": "Flyway Teams only." + "reason": "Removed in Flyway 10" } }, { @@ -1526,6 +1586,27 @@ "level": "error" } }, + { + "name": "spring.influx.password", + "deprecation": { + "level": "error", + "reason": "The new InfluxDb Java client provides Spring Boot integration." + } + }, + { + "name": "spring.influx.url", + "deprecation": { + "level": "error", + "reason": "The new InfluxDb Java client provides Spring Boot integration." + } + }, + { + "name": "spring.influx.user", + "deprecation": { + "level": "error", + "reason": "The new InfluxDb Java client provides Spring Boot integration." + } + }, { "name": "spring.info.build.location", "defaultValue": "classpath:META-INF/build-info.properties" @@ -1542,6 +1623,10 @@ "name": "spring.jackson.constructor-detector", "defaultValue": "default" }, + { + "name": "spring.jackson.datatype.enum", + "description": "Jackson on/off features for enums." + }, { "name": "spring.jackson.joda-date-time-format", "type": "java.lang.String", @@ -1554,6 +1639,14 @@ "name": "spring.jersey.type", "defaultValue": "servlet" }, + { + "name": "spring.jms.listener.session.acknowledge-mode", + "defaultValue": "auto" + }, + { + "name": "spring.jms.template.session.acknowledge-mode", + "defaultValue": "auto" + }, { "name": "spring.jmx.registration-policy", "defaultValue": "fail-on-existing" @@ -1571,167 +1664,6 @@ "name": "spring.jpa.open-in-view", "defaultValue": true }, - { - "name": "spring.jta.bitronix.properties.allow-multiple-lrc", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.asynchronous2-pc", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.background-recovery-interval", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.background-recovery-interval-seconds", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.current-node-only-recovery", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.debug-zero-resource-transaction", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.default-transaction-timeout", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.disable-jmx", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.exception-analyzer", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.filter-log-status", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.force-batching-enabled", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.forced-write-enabled", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.graceful-shutdown-interval", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.jndi-transaction-synchronization-registry-name", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.jndi-user-transaction-name", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.journal", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.log-part1-filename", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.log-part2-filename", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.max-log-size-in-mb", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.resource-configuration-filename", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.server-id", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.skip-corrupted-logs", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.warn-about-zero-resource-transaction", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, { "name": "spring.jta.enabled", "type": "java.lang.Boolean", @@ -2033,10 +1965,19 @@ "name": "spring.kafka.streams.cache-max-bytes-buffering", "type": "java.lang.Integer", "deprecation": { - "replacement": "spring.kafka.streams.cache-max-size-buffering", + "replacement": "spring.kafka.streams.state-store-cache-max-size", "level": "error" } }, + { + "name": "spring.kafka.streams.cache-max-size-buffering", + "type": "java.lang.Integer", + "deprecation": { + "replacement": "spring.kafka.streams.state-store-cache-max-size", + "level": "error", + "since": "3.1.0" + } + }, { "name": "spring.liquibase.check-change-log-location", "type": "java.lang.Boolean", @@ -2047,6 +1988,25 @@ "level": "error" } }, + { + "name": "spring.liquibase.labels", + "deprecation": { + "replacement": "spring.liquibase.label-filter", + "level": "error" + } + }, + { + "name": "spring.liquibase.show-summary", + "defaultValue": "summary" + }, + { + "name": "spring.liquibase.show-summary-output", + "defaultValue": "log" + }, + { + "name": "spring.liquibase.ui-service", + "defaultValue": "logger" + }, { "name": "spring.mail.test-connection", "description": "Whether to test that the mail server is available on startup.", @@ -2110,6 +2070,13 @@ "description": "Whether to enable Spring's HiddenHttpMethodFilter.", "defaultValue": false }, + { + "name": "spring.mvc.ignore-default-model-on-redirect", + "deprecation": { + "reason": "Deprecated for removal in Spring MVC.", + "level": "error" + } + }, { "name": "spring.mvc.locale", "type": "java.util.Locale", @@ -2130,6 +2097,13 @@ "name": "spring.mvc.pathmatch.matching-strategy", "defaultValue": "path-pattern-parser" }, + { + "name": "spring.mvc.throw-exception-if-no-handler-found", + "deprecation": { + "reason": "DispatcherServlet property is deprecated for removal and should no longer need to be configured.", + "level": "error" + } + }, { "name": "spring.neo4j.security.trust-strategy", "defaultValue": "trust-system-ca-signed-certificates" @@ -2138,6 +2112,22 @@ "name": "spring.neo4j.uri", "defaultValue": "bolt://localhost:7687" }, + { + "name": "spring.pulsar.client.failover.policy", + "defaultValue": "order" + }, + { + "name": "spring.pulsar.function.enabled", + "type": "java.lang.Boolean", + "description": "Whether to enable function support.", + "defaultValue": true + }, + { + "name": "spring.pulsar.producer.cache.enabled", + "type": "java.lang.Boolean", + "description": "Whether to enable caching in the PulsarProducerFactory.", + "defaultValue": true + }, { "name": "spring.quartz.jdbc.comment-prefix", "defaultValue": [ @@ -2201,6 +2191,10 @@ "level": "error" } }, + { + "name": "spring.reactor.context-propagation", + "defaultValue": "limited" + }, { "name": "spring.reactor.stacktrace-mode.enabled", "description": "Whether Reactor should collect stacktrace information at runtime.", @@ -2718,6 +2712,10 @@ "description": "SSL protocol to use.", "defaultValue": "TLS" }, + { + "name": "spring.rsocket.server.ssl.server-name-bundles", + "description": "Mapping of host names to SSL bundles for SNI configuration." + }, { "name": "spring.rsocket.server.ssl.trust-certificate", "description": "Path to a PEM-encoded SSL certificate authority file." @@ -2830,6 +2828,12 @@ "name": "spring.sql.init.mode", "defaultValue": "embedded" }, + { + "name": "spring.threads.virtual.enabled", + "type": "java.lang.Boolean", + "description": "Whether to use virtual threads.", + "defaultValue": false + }, { "name": "spring.thymeleaf.prefix", "defaultValue": "classpath:/templates/" @@ -3112,6 +3116,40 @@ } ] }, + { + "name": "spring.jms.listener.session.acknowledge-mode", + "values": [ + { + "value": "auto", + "description": "Messages sent or received from the session are automatically acknowledged. This is the simplest mode and enables once-only message delivery guarantee." + }, + { + "value": "client", + "description": "Messages are acknowledged once the message listener implementation has called \"jakarta.jms.Message#acknowledge()\". This mode gives the application (rather than the JMS provider) complete control over message acknowledgement." + }, + { + "value": "dups_ok", + "description": "Similar to auto acknowledgment except that said acknowledgment is lazy. As a consequence, the messages might be delivered more than once. This mode enables at-least-once message delivery guarantee." + } + ] + }, + { + "name": "spring.jms.template.session.acknowledge-mode", + "values": [ + { + "value": "auto", + "description": "Messages sent or received from the session are automatically acknowledged. This is the simplest mode and enables once-only message delivery guarantee." + }, + { + "value": "client", + "description": "Messages are acknowledged once the message listener implementation has called \"jakarta.jms.Message#acknowledge()\". This mode gives the application (rather than the JMS provider) complete control over message acknowledgement." + }, + { + "value": "dups_ok", + "description": "Similar to auto acknowledgment except that said acknowledgment is lazy. As a consequence, the messages might be delivered more than once. This mode enables at-least-once message delivery guarantee." + } + ] + }, { "name": "spring.jmx.server", "providers": [ diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 3accbafa2265..6c4f3a005bee 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -31,7 +31,8 @@ org.springframework.boot.autoconfigure.jooq.NoDslContextBeanFailureAnalyzer,\ org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\ org.springframework.boot.autoconfigure.r2dbc.MissingR2dbcPoolDependencyFailureAnalyzer,\ org.springframework.boot.autoconfigure.r2dbc.MultipleConnectionPoolConfigurationsFailureAnalyzer,\ -org.springframework.boot.autoconfigure.r2dbc.NoConnectionFactoryBeanFailureAnalyzer +org.springframework.boot.autoconfigure.r2dbc.NoConnectionFactoryBeanFailureAnalyzer,\ +org.springframework.boot.autoconfigure.ssl.BundleContentNotWatchableFailureAnalyzer # Template Availability Providers org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\ diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index f0018406978d..4250c7a355e7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -63,11 +63,11 @@ org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration -org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration +org.springframework.boot.autoconfigure.jdbc.JdbcClientAutoConfiguration org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration @@ -93,9 +93,13 @@ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration org.springframework.boot.autoconfigure.netty.NettyAutoConfiguration org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration +org.springframework.boot.autoconfigure.pulsar.PulsarAutoConfiguration +org.springframework.boot.autoconfigure.pulsar.PulsarReactiveAutoConfiguration org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration +org.springframework.boot.autoconfigure.r2dbc.R2dbcProxyAutoConfiguration org.springframework.boot.autoconfigure.r2dbc.R2dbcTransactionManagerAutoConfiguration +org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration @@ -121,8 +125,10 @@ org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration +org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizationAutoConfiguration org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration +org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration @@ -143,4 +149,4 @@ org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoC org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration -org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration \ No newline at end of file +org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java index 608b631bb60a..1f3e84d18e78 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,9 @@ package org.springframework.boot.autoconfigure.amqp; import java.security.NoSuchAlgorithmException; +import java.util.Collection; import java.util.List; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; import javax.net.ssl.SSLSocketFactory; @@ -29,13 +31,19 @@ import com.rabbitmq.client.impl.CredentialsRefreshService; import com.rabbitmq.client.impl.DefaultCredentialsProvider; import org.aopalliance.aop.Advice; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.InOrder; import org.springframework.amqp.core.AcknowledgeMode; import org.springframework.amqp.core.AmqpAdmin; import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.rabbit.annotation.EnableRabbit; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.config.AbstractRabbitListenerContainerFactory; @@ -55,9 +63,13 @@ import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory; import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; import org.springframework.amqp.rabbit.retry.MessageRecoverer; +import org.springframework.amqp.support.converter.MessageConversionException; import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.amqp.support.converter.SerializerMessageConverter; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; +import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -69,6 +81,7 @@ import org.springframework.context.annotation.Primary; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.core.task.VirtualThreadTaskExecutor; import org.springframework.retry.RetryPolicy; import org.springframework.retry.backoff.BackOffPolicy; import org.springframework.retry.backoff.ExponentialBackOffPolicy; @@ -100,12 +113,14 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick + * @author Yanming Zhou */ @ExtendWith(OutputCaptureExtension.class) class RabbitAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class, SslAutoConfiguration.class)) .withClassLoader(new FilteredClassLoader("org.springframework.rabbit.stream")); // gh-38750 @Test @@ -149,6 +164,8 @@ void testDefaultConnectionFactoryConfiguration() { com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory = getTargetConnectionFactory(context); assertThat(rabbitConnectionFactory.getUsername()).isEqualTo(properties.getUsername()); assertThat(rabbitConnectionFactory.getPassword()).isEqualTo(properties.getPassword()); + assertThat(rabbitConnectionFactory).extracting("maxInboundMessageBodySize") + .isEqualTo((int) properties.getMaxInboundMessageBodySize().toBytes()); }); } @@ -159,7 +176,8 @@ void testConnectionFactoryWithOverrides() { .withPropertyValues("spring.rabbitmq.host:remote-server", "spring.rabbitmq.port:9000", "spring.rabbitmq.address-shuffle-mode=random", "spring.rabbitmq.username:alice", "spring.rabbitmq.password:secret", "spring.rabbitmq.virtual_host:/vhost", - "spring.rabbitmq.connection-timeout:123", "spring.rabbitmq.channel-rpc-timeout:140") + "spring.rabbitmq.connection-timeout:123", "spring.rabbitmq.channel-rpc-timeout:140", + "spring.rabbitmq.max-inbound-message-body-size:128MB") .run((context) -> { CachingConnectionFactory connectionFactory = context.getBean(CachingConnectionFactory.class); assertThat(connectionFactory.getHost()).isEqualTo("remote-server"); @@ -171,6 +189,7 @@ void testConnectionFactoryWithOverrides() { assertThat(rcf.getConnectionTimeout()).isEqualTo(123); assertThat(rcf.getChannelRpcTimeout()).isEqualTo(140); assertThat((List
) ReflectionTestUtils.getField(connectionFactory, "addresses")).hasSize(1); + assertThat(rcf).hasFieldOrPropertyWithValue("maxInboundMessageBodySize", 1024 * 1024 * 128); }); } @@ -310,10 +329,10 @@ void testRabbitTemplateMessageConverters() { void testRabbitTemplateRetry() { this.contextRunner.withUserConfiguration(TestConfiguration.class) .withPropertyValues("spring.rabbitmq.template.retry.enabled:true", - "spring.rabbitmq.template.retry.maxAttempts:4", - "spring.rabbitmq.template.retry.initialInterval:2000", - "spring.rabbitmq.template.retry.multiplier:1.5", "spring.rabbitmq.template.retry.maxInterval:5000", - "spring.rabbitmq.template.receiveTimeout:123", "spring.rabbitmq.template.replyTimeout:456") + "spring.rabbitmq.template.retry.max-attempts:4", + "spring.rabbitmq.template.retry.initial-interval:2000", + "spring.rabbitmq.template.retry.multiplier:1.5", "spring.rabbitmq.template.retry.max-interval:5000", + "spring.rabbitmq.template.receive-timeout:123", "spring.rabbitmq.template.reply-timeout:456") .run((context) -> { RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class); assertThat(rabbitTemplate).hasFieldOrPropertyWithValue("receiveTimeout", 123L); @@ -336,7 +355,7 @@ void testRabbitTemplateRetry() { void testRabbitTemplateRetryWithCustomizer() { this.contextRunner.withUserConfiguration(RabbitRetryTemplateCustomizerConfiguration.class) .withPropertyValues("spring.rabbitmq.template.retry.enabled:true", - "spring.rabbitmq.template.retry.initialInterval:2000") + "spring.rabbitmq.template.retry.initial-interval:2000") .run((context) -> { RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class); RetryTemplate retryTemplate = (RetryTemplate) ReflectionTestUtils.getField(rabbitTemplate, @@ -362,6 +381,16 @@ void testRabbitTemplateExchangeAndRoutingKey() { }); } + @Test + void shouldConfigureObservationEnabledOnTemplate() { + this.contextRunner.withUserConfiguration(TestConfiguration.class) + .withPropertyValues("spring.rabbitmq.template.observation-enabled:true") + .run((context) -> { + RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class); + assertThat(rabbitTemplate).extracting("observationEnabled", InstanceOfAssertFactories.BOOLEAN).isTrue(); + }); + } + @Test void testRabbitTemplateDefaultReceiveQueue() { this.contextRunner.withUserConfiguration(TestConfiguration.class) @@ -448,7 +477,7 @@ void testConnectionFactoryBackOff() { void testConnectionFactoryCacheSettings() { this.contextRunner.withUserConfiguration(TestConfiguration.class) .withPropertyValues("spring.rabbitmq.cache.channel.size=23", - "spring.rabbitmq.cache.channel.checkoutTimeout=1000", + "spring.rabbitmq.cache.channel.checkout-timeout=1000", "spring.rabbitmq.cache.connection.mode=CONNECTION", "spring.rabbitmq.cache.connection.size=2") .run((context) -> { CachingConnectionFactory connectionFactory = context.getBean(CachingConnectionFactory.class); @@ -510,18 +539,20 @@ void testSimpleRabbitListenerContainerFactoryWithCustomSettings() { this.contextRunner .withUserConfiguration(MessageConvertersConfiguration.class, MessageRecoverersConfiguration.class) .withPropertyValues("spring.rabbitmq.listener.simple.retry.enabled:true", - "spring.rabbitmq.listener.simple.retry.maxAttempts:4", - "spring.rabbitmq.listener.simple.retry.initialInterval:2000", + "spring.rabbitmq.listener.simple.retry.max-attempts:4", + "spring.rabbitmq.listener.simple.retry.initial-interval:2000", "spring.rabbitmq.listener.simple.retry.multiplier:1.5", - "spring.rabbitmq.listener.simple.retry.maxInterval:5000", - "spring.rabbitmq.listener.simple.autoStartup:false", - "spring.rabbitmq.listener.simple.acknowledgeMode:manual", + "spring.rabbitmq.listener.simple.retry.max-interval:5000", + "spring.rabbitmq.listener.simple.auto-startup:false", + "spring.rabbitmq.listener.simple.acknowledge-mode:manual", "spring.rabbitmq.listener.simple.concurrency:5", - "spring.rabbitmq.listener.simple.maxConcurrency:10", "spring.rabbitmq.listener.simple.prefetch:40", - "spring.rabbitmq.listener.simple.defaultRequeueRejected:false", - "spring.rabbitmq.listener.simple.idleEventInterval:5", - "spring.rabbitmq.listener.simple.batchSize:20", - "spring.rabbitmq.listener.simple.missingQueuesFatal:false") + "spring.rabbitmq.listener.simple.max-concurrency:10", "spring.rabbitmq.listener.simple.prefetch:40", + "spring.rabbitmq.listener.simple.default-requeue-rejected:false", + "spring.rabbitmq.listener.simple.idle-event-interval:5", + "spring.rabbitmq.listener.simple.batch-size:20", + "spring.rabbitmq.listener.simple.missing-queues-fatal:false", + "spring.rabbitmq.listener.simple.force-stop:true", + "spring.rabbitmq.listener.simple.observation-enabled:true") .run((context) -> { SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory = context .getBean("rabbitListenerContainerFactory", SimpleRabbitListenerContainerFactory.class); @@ -529,41 +560,99 @@ void testSimpleRabbitListenerContainerFactoryWithCustomSettings() { assertThat(rabbitListenerContainerFactory).hasFieldOrPropertyWithValue("maxConcurrentConsumers", 10); assertThat(rabbitListenerContainerFactory).hasFieldOrPropertyWithValue("batchSize", 20); assertThat(rabbitListenerContainerFactory).hasFieldOrPropertyWithValue("missingQueuesFatal", false); + assertThat(rabbitListenerContainerFactory).hasFieldOrPropertyWithValue("observationEnabled", true); checkCommonProps(context, rabbitListenerContainerFactory); }); } + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void shouldConfigureVirtualThreadsForSimpleListener() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> { + SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory = context + .getBean("rabbitListenerContainerFactory", SimpleRabbitListenerContainerFactory.class); + assertThat(rabbitListenerContainerFactory).extracting("taskExecutor") + .isInstanceOf(VirtualThreadTaskExecutor.class); + Object taskExecutor = ReflectionTestUtils.getField(rabbitListenerContainerFactory, "taskExecutor"); + Object virtualThread = ReflectionTestUtils.getField(taskExecutor, "virtualThreadFactory"); + Thread threadCreated = ((ThreadFactory) virtualThread).newThread(mock(Runnable.class)); + assertThat(threadCreated.getName()).containsPattern("rabbit-simple-[0-9]+"); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void shouldConfigureVirtualThreadsForDirectListener() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> { + DirectRabbitListenerContainerFactoryConfigurer rabbitListenerContainerFactory = context.getBean( + "directRabbitListenerContainerFactoryConfigurer", + DirectRabbitListenerContainerFactoryConfigurer.class); + assertThat(rabbitListenerContainerFactory).extracting("taskExecutor") + .isInstanceOf(VirtualThreadTaskExecutor.class); + Object taskExecutor = ReflectionTestUtils.getField(rabbitListenerContainerFactory, "taskExecutor"); + Object virtualThread = ReflectionTestUtils.getField(taskExecutor, "virtualThreadFactory"); + Thread threadCreated = ((ThreadFactory) virtualThread).newThread(mock(Runnable.class)); + assertThat(threadCreated.getName()).containsPattern("rabbit-direct-[0-9]+"); + }); + } + + @Test + void testSimpleRabbitListenerContainerFactoryWithDefaultForceStop() { + this.contextRunner + .withUserConfiguration(MessageConvertersConfiguration.class, MessageRecoverersConfiguration.class) + .run((context) -> { + SimpleRabbitListenerContainerFactory containerFactory = context + .getBean("rabbitListenerContainerFactory", SimpleRabbitListenerContainerFactory.class); + assertThat(containerFactory).hasFieldOrPropertyWithValue("forceStop", false); + }); + } + @Test void testDirectRabbitListenerContainerFactoryWithCustomSettings() { this.contextRunner .withUserConfiguration(MessageConvertersConfiguration.class, MessageRecoverersConfiguration.class) .withPropertyValues("spring.rabbitmq.listener.type:direct", "spring.rabbitmq.listener.direct.retry.enabled:true", - "spring.rabbitmq.listener.direct.retry.maxAttempts:4", - "spring.rabbitmq.listener.direct.retry.initialInterval:2000", + "spring.rabbitmq.listener.direct.retry.max-attempts:4", + "spring.rabbitmq.listener.direct.retry.initial-interval:2000", "spring.rabbitmq.listener.direct.retry.multiplier:1.5", - "spring.rabbitmq.listener.direct.retry.maxInterval:5000", - "spring.rabbitmq.listener.direct.autoStartup:false", - "spring.rabbitmq.listener.direct.acknowledgeMode:manual", + "spring.rabbitmq.listener.direct.retry.max-interval:5000", + "spring.rabbitmq.listener.direct.auto-startup:false", + "spring.rabbitmq.listener.direct.acknowledge-mode:manual", "spring.rabbitmq.listener.direct.consumers-per-queue:5", "spring.rabbitmq.listener.direct.prefetch:40", - "spring.rabbitmq.listener.direct.defaultRequeueRejected:false", - "spring.rabbitmq.listener.direct.idleEventInterval:5", - "spring.rabbitmq.listener.direct.missingQueuesFatal:true") + "spring.rabbitmq.listener.direct.default-requeue-rejected:false", + "spring.rabbitmq.listener.direct.idle-event-interval:5", + "spring.rabbitmq.listener.direct.missing-queues-fatal:true", + "spring.rabbitmq.listener.direct.force-stop:true", + "spring.rabbitmq.listener.direct.observation-enabled:true") .run((context) -> { DirectRabbitListenerContainerFactory rabbitListenerContainerFactory = context .getBean("rabbitListenerContainerFactory", DirectRabbitListenerContainerFactory.class); assertThat(rabbitListenerContainerFactory).hasFieldOrPropertyWithValue("consumersPerQueue", 5); assertThat(rabbitListenerContainerFactory).hasFieldOrPropertyWithValue("missingQueuesFatal", true); + assertThat(rabbitListenerContainerFactory).hasFieldOrPropertyWithValue("observationEnabled", true); checkCommonProps(context, rabbitListenerContainerFactory); }); } + @Test + void testDirectRabbitListenerContainerFactoryWithDefaultForceStop() { + this.contextRunner + .withUserConfiguration(MessageConvertersConfiguration.class, MessageRecoverersConfiguration.class) + .withPropertyValues("spring.rabbitmq.listener.type:direct") + .run((context) -> { + DirectRabbitListenerContainerFactory containerFactory = context + .getBean("rabbitListenerContainerFactory", DirectRabbitListenerContainerFactory.class); + assertThat(containerFactory).hasFieldOrPropertyWithValue("forceStop", false); + }); + } + @Test void testSimpleRabbitListenerContainerFactoryRetryWithCustomizer() { this.contextRunner.withUserConfiguration(RabbitRetryTemplateCustomizerConfiguration.class) .withPropertyValues("spring.rabbitmq.listener.simple.retry.enabled:true", - "spring.rabbitmq.listener.simple.retry.maxAttempts:4") + "spring.rabbitmq.listener.simple.retry.max-attempts:4") .run((context) -> { SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory = context .getBean("rabbitListenerContainerFactory", SimpleRabbitListenerContainerFactory.class); @@ -577,7 +666,7 @@ void testDirectRabbitListenerContainerFactoryRetryWithCustomizer() { this.contextRunner.withUserConfiguration(RabbitRetryTemplateCustomizerConfiguration.class) .withPropertyValues("spring.rabbitmq.listener.type:direct", "spring.rabbitmq.listener.direct.retry.enabled:true", - "spring.rabbitmq.listener.direct.retry.maxAttempts:4") + "spring.rabbitmq.listener.direct.retry.max-attempts:4") .run((context) -> { DirectRabbitListenerContainerFactory rabbitListenerContainerFactory = context .getBean("rabbitListenerContainerFactory", DirectRabbitListenerContainerFactory.class); @@ -600,7 +689,7 @@ private void assertListenerRetryTemplate(AbstractRabbitListenerContainerFactory< void testRabbitListenerContainerFactoryConfigurersAreAvailable() { this.contextRunner.withUserConfiguration(TestConfiguration.class) .withPropertyValues("spring.rabbitmq.listener.simple.concurrency:5", - "spring.rabbitmq.listener.simple.maxConcurrency:10", "spring.rabbitmq.listener.simple.prefetch:40", + "spring.rabbitmq.listener.simple.max-concurrency:10", "spring.rabbitmq.listener.simple.prefetch:40", "spring.rabbitmq.listener.direct.consumers-per-queue:5", "spring.rabbitmq.listener.direct.prefetch:40") .run((context) -> { @@ -613,7 +702,7 @@ void testRabbitListenerContainerFactoryConfigurersAreAvailable() { void testSimpleRabbitListenerContainerFactoryConfigurerUsesConfig() { this.contextRunner.withUserConfiguration(TestConfiguration.class) .withPropertyValues("spring.rabbitmq.listener.simple.concurrency:5", - "spring.rabbitmq.listener.simple.maxConcurrency:10", "spring.rabbitmq.listener.simple.prefetch:40") + "spring.rabbitmq.listener.simple.max-concurrency:10", "spring.rabbitmq.listener.simple.prefetch:40") .run((context) -> { SimpleRabbitListenerContainerFactoryConfigurer configurer = context .getBean(SimpleRabbitListenerContainerFactoryConfigurer.class); @@ -664,6 +753,7 @@ private void checkCommonProps(AssertableApplicationContext context, context.getBean("myMessageConverter")); assertThat(containerFactory).hasFieldOrPropertyWithValue("defaultRequeueRejected", Boolean.FALSE); assertThat(containerFactory).hasFieldOrPropertyWithValue("idleEventInterval", 5L); + assertThat(containerFactory).hasFieldOrPropertyWithValue("forceStop", true); Advice[] adviceChain = containerFactory.getAdviceChain(); assertThat(adviceChain).isNotNull(); assertThat(adviceChain).hasSize(1); @@ -697,7 +787,7 @@ void enableRabbitAutomatically() { @Test void customizeRequestedHeartBeat() { this.contextRunner.withUserConfiguration(TestConfiguration.class) - .withPropertyValues("spring.rabbitmq.requestedHeartbeat:20") + .withPropertyValues("spring.rabbitmq.requested-heartbeat:20") .run((context) -> { com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory = getTargetConnectionFactory(context); assertThat(rabbitConnectionFactory.getRequestedHeartbeat()).isEqualTo(20); @@ -707,13 +797,34 @@ void customizeRequestedHeartBeat() { @Test void customizeRequestedChannelMax() { this.contextRunner.withUserConfiguration(TestConfiguration.class) - .withPropertyValues("spring.rabbitmq.requestedChannelMax:12") + .withPropertyValues("spring.rabbitmq.requested-channel-max:12") .run((context) -> { com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory = getTargetConnectionFactory(context); assertThat(rabbitConnectionFactory.getRequestedChannelMax()).isEqualTo(12); }); } + @ParameterizedTest + @ValueSource(classes = { TestConfiguration.class, TestConfiguration6.class }) + @SuppressWarnings("unchecked") + void customizeAllowedListPatterns(Class configuration) { + this.contextRunner.withUserConfiguration(configuration) + .withPropertyValues("spring.rabbitmq.template.allowed-list-patterns:*") + .run((context) -> { + MessageConverter messageConverter = context.getBean(RabbitTemplate.class).getMessageConverter(); + assertThat(messageConverter).extracting("allowedListPatterns") + .isInstanceOfSatisfying(Collection.class, (set) -> assertThat(set).contains("*")); + }); + } + + @Test + void customizeAllowedListPatternsWhenHasNoAllowedListDeserializingMessageConverter() { + this.contextRunner.withUserConfiguration(CustomMessageConverterConfiguration.class) + .withPropertyValues("spring.rabbitmq.template.allowed-list-patterns:*") + .run((context) -> assertThat(context).getFailure() + .hasRootCauseInstanceOf(InvalidConfigurationPropertyValueException.class)); + } + @Test void noSslByDefault() { this.contextRunner.withUserConfiguration(TestConfiguration.class).run((context) -> { @@ -735,12 +846,22 @@ void enableSsl() { }); } + @Test + void enableSslWithInvalidSslBundleFails() { + this.contextRunner.withUserConfiguration(TestConfiguration.class) + .withPropertyValues("spring.rabbitmq.ssl.bundle=invalid") + .run((context) -> { + assertThat(context).hasFailed(); + assertThat(context).getFailure().hasMessageContaining("SSL bundle name 'invalid' cannot be found"); + }); + } + @Test // Make sure that we at least attempt to load the store void enableSslWithNonExistingKeystoreShouldFail() { this.contextRunner.withUserConfiguration(TestConfiguration.class) - .withPropertyValues("spring.rabbitmq.ssl.enabled:true", "spring.rabbitmq.ssl.keyStore=foo", - "spring.rabbitmq.ssl.keyStorePassword=secret") + .withPropertyValues("spring.rabbitmq.ssl.enabled:true", "spring.rabbitmq.ssl.key-store=foo", + "spring.rabbitmq.ssl.key-store-password=secret") .run((context) -> { assertThat(context).hasFailed(); assertThat(context).getFailure().hasMessageContaining("foo"); @@ -752,8 +873,8 @@ void enableSslWithNonExistingKeystoreShouldFail() { // Make sure that we at least attempt to load the store void enableSslWithNonExistingTrustStoreShouldFail() { this.contextRunner.withUserConfiguration(TestConfiguration.class) - .withPropertyValues("spring.rabbitmq.ssl.enabled:true", "spring.rabbitmq.ssl.trustStore=bar", - "spring.rabbitmq.ssl.trustStorePassword=secret") + .withPropertyValues("spring.rabbitmq.ssl.enabled:true", "spring.rabbitmq.ssl.trust-store=bar", + "spring.rabbitmq.ssl.trust-store-password=secret") .run((context) -> { assertThat(context).hasFailed(); assertThat(context).getFailure().hasMessageContaining("bar"); @@ -764,8 +885,8 @@ void enableSslWithNonExistingTrustStoreShouldFail() { @Test void enableSslWithInvalidKeystoreTypeShouldFail() { this.contextRunner.withUserConfiguration(TestConfiguration.class) - .withPropertyValues("spring.rabbitmq.ssl.enabled:true", "spring.rabbitmq.ssl.keyStore=foo", - "spring.rabbitmq.ssl.keyStoreType=fooType") + .withPropertyValues("spring.rabbitmq.ssl.enabled:true", "spring.rabbitmq.ssl.key-store=foo", + "spring.rabbitmq.ssl.key-store-type=fooType") .run((context) -> { assertThat(context).hasFailed(); assertThat(context).getFailure().hasMessageContaining("fooType"); @@ -776,8 +897,8 @@ void enableSslWithInvalidKeystoreTypeShouldFail() { @Test void enableSslWithInvalidTrustStoreTypeShouldFail() { this.contextRunner.withUserConfiguration(TestConfiguration.class) - .withPropertyValues("spring.rabbitmq.ssl.enabled:true", "spring.rabbitmq.ssl.trustStore=bar", - "spring.rabbitmq.ssl.trustStoreType=barType") + .withPropertyValues("spring.rabbitmq.ssl.enabled:true", "spring.rabbitmq.ssl.trust-store=bar", + "spring.rabbitmq.ssl.trust-store-type=barType") .run((context) -> { assertThat(context).hasFailed(); assertThat(context).getFailure().hasMessageContaining("barType"); @@ -785,14 +906,27 @@ void enableSslWithInvalidTrustStoreTypeShouldFail() { }); } + @Test + void enableSslWithBundle() { + this.contextRunner.withUserConfiguration(TestConfiguration.class) + .withPropertyValues("spring.rabbitmq.ssl.bundle=test-bundle", + "spring.ssl.bundle.jks.test-bundle.keystore.location=classpath:test.jks", + "spring.ssl.bundle.jks.test-bundle.keystore.password=secret", + "spring.ssl.bundle.jks.test-bundle.key.password=password") + .run((context) -> { + com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory = getTargetConnectionFactory(context); + assertThat(rabbitConnectionFactory.isSSL()).isTrue(); + }); + } + @Test void enableSslWithKeystoreTypeAndTrustStoreTypeShouldWork() { this.contextRunner.withUserConfiguration(TestConfiguration.class) .withPropertyValues("spring.rabbitmq.ssl.enabled:true", - "spring.rabbitmq.ssl.keyStore=/org/springframework/boot/autoconfigure/amqp/test.jks", - "spring.rabbitmq.ssl.keyStoreType=jks", "spring.rabbitmq.ssl.keyStorePassword=secret", - "spring.rabbitmq.ssl.trustStore=/org/springframework/boot/autoconfigure/amqp/test.jks", - "spring.rabbitmq.ssl.trustStoreType=jks", "spring.rabbitmq.ssl.trustStorePassword=secret") + "spring.rabbitmq.ssl.key-store=/org/springframework/boot/autoconfigure/amqp/test.jks", + "spring.rabbitmq.ssl.key-store-type=jks", "spring.rabbitmq.ssl.key-store-password=secret", + "spring.rabbitmq.ssl.trust-store=/org/springframework/boot/autoconfigure/amqp/test.jks", + "spring.rabbitmq.ssl.trust-store-type=jks", "spring.rabbitmq.ssl.trust-store-password=secret") .run((context) -> assertThat(context).hasNotFailed()); } @@ -800,7 +934,7 @@ void enableSslWithKeystoreTypeAndTrustStoreTypeShouldWork() { void enableSslWithValidateServerCertificateFalse(CapturedOutput output) { this.contextRunner.withUserConfiguration(TestConfiguration.class) .withPropertyValues("spring.rabbitmq.ssl.enabled:true", - "spring.rabbitmq.ssl.validateServerCertificate=false") + "spring.rabbitmq.ssl.validate-server-certificate=false") .run((context) -> { com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory = getTargetConnectionFactory(context); assertThat(rabbitConnectionFactory.isSSL()).isTrue(); @@ -823,12 +957,12 @@ void enableSslWithValidateServerCertificateDefault(CapturedOutput output) { void enableSslWithValidStoreAlgorithmShouldWork() { this.contextRunner.withUserConfiguration(TestConfiguration.class) .withPropertyValues("spring.rabbitmq.ssl.enabled:true", - "spring.rabbitmq.ssl.keyStore=/org/springframework/boot/autoconfigure/amqp/test.jks", - "spring.rabbitmq.ssl.keyStoreType=jks", "spring.rabbitmq.ssl.keyStorePassword=secret", - "spring.rabbitmq.ssl.keyStoreAlgorithm=PKIX", - "spring.rabbitmq.ssl.trustStore=/org/springframework/boot/autoconfigure/amqp/test.jks", - "spring.rabbitmq.ssl.trustStoreType=jks", "spring.rabbitmq.ssl.trustStorePassword=secret", - "spring.rabbitmq.ssl.trustStoreAlgorithm=PKIX") + "spring.rabbitmq.ssl.key-store=/org/springframework/boot/autoconfigure/amqp/test.jks", + "spring.rabbitmq.ssl.key-store-type=jks", "spring.rabbitmq.ssl.key-store-password=secret", + "spring.rabbitmq.ssl.key-store-algorithm=PKIX", + "spring.rabbitmq.ssl.trust-store=/org/springframework/boot/autoconfigure/amqp/test.jks", + "spring.rabbitmq.ssl.trust-store-type=jks", "spring.rabbitmq.ssl.trust-store-password=secret", + "spring.rabbitmq.ssl.trust-store-algorithm=PKIX") .run((context) -> assertThat(context).hasNotFailed()); } @@ -836,9 +970,9 @@ void enableSslWithValidStoreAlgorithmShouldWork() { void enableSslWithInvalidKeyStoreAlgorithmShouldFail() { this.contextRunner.withUserConfiguration(TestConfiguration.class) .withPropertyValues("spring.rabbitmq.ssl.enabled:true", - "spring.rabbitmq.ssl.keyStore=/org/springframework/boot/autoconfigure/amqp/test.jks", - "spring.rabbitmq.ssl.keyStoreType=jks", "spring.rabbitmq.ssl.keyStorePassword=secret", - "spring.rabbitmq.ssl.keyStoreAlgorithm=test-invalid-algo") + "spring.rabbitmq.ssl.key-store=/org/springframework/boot/autoconfigure/amqp/test.jks", + "spring.rabbitmq.ssl.key-store-type=jks", "spring.rabbitmq.ssl.key-store-password=secret", + "spring.rabbitmq.ssl.key-store-algorithm=test-invalid-algo") .run((context) -> { assertThat(context).hasFailed(); assertThat(context).getFailure().hasMessageContaining("test-invalid-algo"); @@ -850,9 +984,9 @@ void enableSslWithInvalidKeyStoreAlgorithmShouldFail() { void enableSslWithInvalidTrustStoreAlgorithmShouldFail() { this.contextRunner.withUserConfiguration(TestConfiguration.class) .withPropertyValues("spring.rabbitmq.ssl.enabled:true", - "spring.rabbitmq.ssl.trustStore=/org/springframework/boot/autoconfigure/amqp/test.jks", - "spring.rabbitmq.ssl.trustStoreType=jks", "spring.rabbitmq.ssl.trustStorePassword=secret", - "spring.rabbitmq.ssl.trustStoreAlgorithm=test-invalid-algo") + "spring.rabbitmq.ssl.trust-store=/org/springframework/boot/autoconfigure/amqp/test.jks", + "spring.rabbitmq.ssl.trust-store-type=jks", "spring.rabbitmq.ssl.trust-store-password=secret", + "spring.rabbitmq.ssl.trust-store-algorithm=test-invalid-algo") .run((context) -> { assertThat(context).hasFailed(); assertThat(context).getFailure().hasMessageContaining("test-invalid-algo"); @@ -1008,6 +1142,16 @@ RabbitListenerContainerFactory rabbitListenerContainerFactory() { } + @Configuration(proxyBeanMethods = false) + static class TestConfiguration6 { + + @Bean + MessageConverter messageConverter() { + return new SerializerMessageConverter(); + } + + } + @Configuration(proxyBeanMethods = false) static class MessageConvertersConfiguration { @@ -1282,6 +1426,29 @@ public List
getAddresses() { } + @Configuration + static class CustomMessageConverterConfiguration { + + @Bean + MessageConverter messageConverter() { + return new MessageConverter() { + + @Override + public Message toMessage(Object object, MessageProperties messageProperties) + throws MessageConversionException { + return new Message(object.toString().getBytes()); + } + + @Override + public Object fromMessage(Message message) throws MessageConversionException { + return new String(message.getBody()); + } + + }; + } + + } + static class TestListener { @RabbitListener(queues = "test", autoStartup = "false") diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitPropertiesTests.java index 0d251a72129f..06708ae217d0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitPropertiesTests.java @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.amqp; +import com.rabbitmq.client.ConnectionFactory; import org.junit.jupiter.api.Test; import org.springframework.amqp.rabbit.config.DirectRabbitListenerContainerFactory; @@ -34,6 +35,7 @@ * @author Andy Wilkinson * @author Stephane Nicoll * @author Rafael Carvalho + * @author Scott Frederick */ class RabbitPropertiesTests { @@ -320,6 +322,19 @@ void determineSslReturnFlagPropertyWhenNoAddresses() { assertThat(this.properties.getSsl().determineEnabled()).isTrue(); } + @Test + void determineSslEnabledIsTrueWhenBundleIsSetAndNoAddresses() { + this.properties.getSsl().setBundle("test"); + assertThat(this.properties.getSsl().determineEnabled()).isTrue(); + } + + @Test + void propertiesUseConsistentDefaultValues() { + ConnectionFactory connectionFactory = new ConnectionFactory(); + assertThat(connectionFactory).hasFieldOrPropertyWithValue("maxInboundMessageBodySize", + (int) this.properties.getMaxInboundMessageBodySize().toBytes()); + } + @Test void simpleContainerUseConsistentDefaultValues() { SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); @@ -329,6 +344,7 @@ void simpleContainerUseConsistentDefaultValues() { assertThat(container).hasFieldOrPropertyWithValue("missingQueuesFatal", simple.isMissingQueuesFatal()); assertThat(container).hasFieldOrPropertyWithValue("deBatchingEnabled", simple.isDeBatchingEnabled()); assertThat(container).hasFieldOrPropertyWithValue("consumerBatchEnabled", simple.isConsumerBatchEnabled()); + assertThat(container).hasFieldOrPropertyWithValue("forceStop", simple.isForceStop()); } @Test @@ -339,6 +355,7 @@ void directContainerUseConsistentDefaultValues() { assertThat(direct.isAutoStartup()).isEqualTo(container.isAutoStartup()); assertThat(container).hasFieldOrPropertyWithValue("missingQueuesFatal", direct.isMissingQueuesFatal()); assertThat(container).hasFieldOrPropertyWithValue("deBatchingEnabled", direct.isDeBatchingEnabled()); + assertThat(container).hasFieldOrPropertyWithValue("forceStop", direct.isForceStop()); } @Test diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitStreamConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitStreamConfigurationTests.java index eec28db57a32..e7d05326cfaa 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitStreamConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitStreamConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,6 +54,7 @@ * @author Gary Russell * @author Andy Wilkinson * @author Eddú Meléndez + * @author Moritz Halbritter */ class RabbitStreamConfigurationTests { @@ -88,6 +89,16 @@ void whenNativeListenerIsEnabledThenContainerFactoryIsConfiguredToUseNativeListe .isTrue()); } + @Test + void shouldConfigureObservations() { + this.contextRunner + .withPropertyValues("spring.rabbitmq.listener.type:stream", + "spring.rabbitmq.listener.stream.observation-enabled:true") + .run((context) -> assertThat(context.getBean(StreamRabbitListenerContainerFactory.class)) + .extracting("observationEnabled", InstanceOfAssertFactories.BOOLEAN) + .isTrue()); + } + @Test void environmentIsAutoConfiguredByDefault() { this.contextRunner.run((context) -> assertThat(context).hasSingleBean(Environment.class)); @@ -143,6 +154,24 @@ void whenStreamHostIsSetThenEnvironmentUsesCustomHost() { then(builder).should().host("stream.rabbit.example.com"); } + @Test + void whenStreamVirtualHostIsSetThenEnvironmentUsesCustomVirtualHost() { + EnvironmentBuilder builder = mock(EnvironmentBuilder.class); + RabbitProperties properties = new RabbitProperties(); + properties.getStream().setVirtualHost("stream-virtual-host"); + RabbitStreamConfiguration.configure(builder, properties); + then(builder).should().virtualHost("stream-virtual-host"); + } + + @Test + void whenStreamVirtualHostIsNotSetButDefaultVirtualHostIsSetThenEnvironmentUsesDefaultVirtualHost() { + EnvironmentBuilder builder = mock(EnvironmentBuilder.class); + RabbitProperties properties = new RabbitProperties(); + properties.setVirtualHost("default-virtual-host"); + RabbitStreamConfiguration.configure(builder, properties); + then(builder).should().virtualHost("default-virtual-host"); + } + @Test void whenStreamCredentialsAreNotSetThenEnvironmentUsesRabbitCredentials() { EnvironmentBuilder builder = mock(EnvironmentBuilder.class); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java index 192dd2f53f56..bf8bad0de84d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java @@ -39,12 +39,14 @@ import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration; -import org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor; import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.job.AbstractJob; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.repository.ExecutionContextSerializer; import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.dao.DefaultExecutionContextSerializer; +import org.springframework.batch.core.repository.dao.Jackson2ExecutionContextStringSerializer; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; @@ -59,13 +61,12 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; -import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.test.City; import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; +import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizationAutoConfiguration; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer; -import org.springframework.boot.logging.LogLevel; import org.springframework.boot.sql.init.DatabaseInitializationMode; import org.springframework.boot.sql.init.DatabaseInitializationSettings; import org.springframework.boot.test.context.FilteredClassLoader; @@ -97,18 +98,20 @@ * @author Stephane Nicoll * @author Vedran Pavic * @author Kazuki Shimizu + * @author Mahmoud Ben Hassine + * @author Lars Uffmann + * @author Lasse Wulff */ @ExtendWith(OutputCaptureExtension.class) class BatchAutoConfigurationTests { - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(BatchAutoConfiguration.class, TransactionAutoConfiguration.class, - DataSourceTransactionManagerAutoConfiguration.class)); + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration( + AutoConfigurations.of(BatchAutoConfiguration.class, TransactionManagerCustomizationAutoConfiguration.class, + TransactionAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class)); @Test void testDefaultContext() { - this.contextRunner.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO)) - .withUserConfiguration(TestConfiguration.class, EmbeddedDataSourceConfiguration.class) + this.contextRunner.withUserConfiguration(TestConfiguration.class, EmbeddedDataSourceConfiguration.class) .run((context) -> { assertThat(context).hasSingleBean(JobRepository.class); assertThat(context).hasSingleBean(JobLauncher.class); @@ -346,6 +349,18 @@ void testBatchDataSource() { }); } + @Test + void testBatchTransactionManager() { + this.contextRunner.withUserConfiguration(TestConfiguration.class, BatchTransactionManagerConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(SpringBootBatchConfiguration.class); + PlatformTransactionManager batchTransactionManager = context.getBean("batchTransactionManager", + PlatformTransactionManager.class); + assertThat(context.getBean(SpringBootBatchConfiguration.class).getTransactionManager()) + .isEqualTo(batchTransactionManager); + }); + } + @Test void jobRepositoryBeansDependOnBatchDataSourceInitializer() { this.contextRunner.withUserConfiguration(TestConfiguration.class, EmbeddedDataSourceConfiguration.class) @@ -461,6 +476,27 @@ void whenTheUserDefinesAJobNameThatDoesNotExistWithRegisteredJobFailsFast() { .withMessage("No job found with name 'three'"); } + @Test + void customExecutionContextSerializerIsUsed() { + this.contextRunner.withUserConfiguration(TestConfiguration.class, EmbeddedDataSourceConfiguration.class) + .withUserConfiguration(CustomExecutionContextConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(Jackson2ExecutionContextStringSerializer.class); + assertThat(context.getBean(SpringBootBatchConfiguration.class).getExecutionContextSerializer()) + .isInstanceOf(Jackson2ExecutionContextStringSerializer.class); + }); + } + + @Test + void defaultExecutionContextSerializerIsUsed() { + this.contextRunner.withUserConfiguration(TestConfiguration.class, EmbeddedDataSourceConfiguration.class) + .run((context) -> { + assertThat(context).doesNotHaveBean(ExecutionContextSerializer.class); + assertThat(context.getBean(SpringBootBatchConfiguration.class).getExecutionContextSerializer()) + .isInstanceOf(DefaultExecutionContextSerializer.class); + }); + } + private JobLauncherApplicationRunner createInstance(String... registeredJobNames) { JobLauncherApplicationRunner runner = new JobLauncherApplicationRunner(mock(JobLauncher.class), mock(JobExplorer.class), mock(JobRepository.class)); @@ -477,22 +513,44 @@ private Job mockJob(String name) { } @Configuration(proxyBeanMethods = false) - protected static class BatchDataSourceConfiguration { + static class BatchDataSourceConfiguration { @Bean @Primary - public DataSource normalDataSource() { + DataSource normalDataSource() { return DataSourceBuilder.create().url("jdbc:hsqldb:mem:normal").username("sa").build(); } @BatchDataSource @Bean - public DataSource batchDataSource() { + DataSource batchDataSource() { return DataSourceBuilder.create().url("jdbc:hsqldb:mem:batchdatasource").username("sa").build(); } } + @Configuration(proxyBeanMethods = false) + static class BatchTransactionManagerConfiguration { + + @Bean + DataSource dataSource() { + return DataSourceBuilder.create().url("jdbc:hsqldb:mem:database").username("sa").build(); + } + + @Bean + @Primary + PlatformTransactionManager normalTransactionManager() { + return mock(PlatformTransactionManager.class); + } + + @BatchTransactionManager + @Bean + PlatformTransactionManager batchTransactionManager() { + return mock(PlatformTransactionManager.class); + } + + } + @Configuration(proxyBeanMethods = false) static class EmptyConfiguration { @@ -519,13 +577,6 @@ static class NamedJobConfigurationWithRegisteredAndLocalJob { @Autowired private JobRepository jobRepository; - @Bean - static JobRegistryBeanPostProcessor registryProcessor(JobRegistry jobRegistry) { - JobRegistryBeanPostProcessor processor = new JobRegistryBeanPostProcessor(); - processor.setJobRegistry(jobRegistry); - return processor; - } - @Bean Job discreteJob() { AbstractJob job = new AbstractJob("discreteRegisteredJob") { @@ -690,7 +741,17 @@ protected void doExecute(JobExecution execution) { @Bean Job job2() { - return mock(Job.class); + return new Job() { + @Override + public String getName() { + return "discreteLocalJob2"; + } + + @Override + public void execute(JobExecution execution) { + execution.setStatus(BatchStatus.COMPLETED); + } + }; } } @@ -774,4 +835,14 @@ BatchConversionServiceCustomizer anotherBatchConversionServiceCustomizer() { } + @Configuration(proxyBeanMethods = false) + static class CustomExecutionContextConfiguration { + + @Bean + ExecutionContextSerializer executionContextSerializer() { + return new Jackson2ExecutionContextStringSerializer(); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationWithoutJpaTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationWithoutJpaTests.java index 85b1f8543e6a..a907a075b251 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationWithoutJpaTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationWithoutJpaTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,10 +29,8 @@ import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration.SpringBootBatchConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; -import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener; import org.springframework.boot.autoconfigure.orm.jpa.test.City; import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; -import org.springframework.boot.logging.LogLevel; import org.springframework.boot.sql.init.DatabaseInitializationMode; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; @@ -57,7 +55,6 @@ class BatchAutoConfigurationWithoutJpaTests { void jdbcWithDefaultSettings() { this.contextRunner.withUserConfiguration(DefaultConfiguration.class, EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.datasource.generate-unique-name=true") - .withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO)) .run((context) -> { assertThat(context).hasSingleBean(JobLauncher.class); assertThat(context).hasSingleBean(JobExplorer.class); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchDataSourceScriptDatabaseInitializerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchDataSourceScriptDatabaseInitializerTests.java index 6b04e877c619..87a87f90c460 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchDataSourceScriptDatabaseInitializerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchDataSourceScriptDatabaseInitializerTests.java @@ -33,6 +33,7 @@ import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.boot.sql.init.DatabaseInitializationSettings; import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.test.util.ReflectionTestUtils; @@ -76,7 +77,7 @@ void batchSchemaCanBeLocated(DatabaseDriver driver) throws SQLException { DatabaseInitializationSettings settings = BatchDataSourceScriptDatabaseInitializer.getSettings(dataSource, properties.getJdbc()); List schemaLocations = settings.getSchemaLocations(); - assertThat(schemaLocations) + assertThat(schemaLocations).isNotEmpty() .allSatisfy((location) -> assertThat(resourceLoader.getResource(location).exists()).isTrue()); } @@ -85,7 +86,7 @@ void batchHasExpectedBuiltInSchemas() throws IOException { PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); List schemaNames = Stream .of(resolver.getResources("classpath:org/springframework/batch/core/schema-*.sql")) - .map((resource) -> resource.getFilename()) + .map(Resource::getFilename) .filter((resourceName) -> !resourceName.contains("-drop-")) .toList(); assertThat(schemaNames).containsExactlyInAnyOrder("schema-derby.sql", "schema-sqlserver.sql", diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java index 2c887c890bd2..b29f64cce6cf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Properties; import java.util.function.Consumer; import javax.cache.Caching; @@ -69,14 +70,17 @@ import org.springframework.data.couchbase.cache.CouchbaseCache; import org.springframework.data.couchbase.cache.CouchbaseCacheConfiguration; import org.springframework.data.couchbase.cache.CouchbaseCacheManager; +import org.springframework.data.redis.cache.FixedDurationTtlFunction; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; +import static org.mockito.BDDMockito.willAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -273,7 +277,10 @@ void redisCacheExplicit() { RedisCacheManager cacheManager = getCacheManager(context, RedisCacheManager.class); assertThat(cacheManager.getCacheNames()).isEmpty(); RedisCacheConfiguration redisCacheConfiguration = getDefaultRedisCacheConfiguration(cacheManager); - assertThat(redisCacheConfiguration.getTtl()).isEqualTo(java.time.Duration.ofSeconds(15)); + assertThat(redisCacheConfiguration).extracting(RedisCacheConfiguration::getTtlFunction) + .isInstanceOf(FixedDurationTtlFunction.class) + .extracting("duration") + .isEqualTo(java.time.Duration.ofSeconds(15)); assertThat(redisCacheConfiguration.getAllowCacheNullValues()).isFalse(); assertThat(redisCacheConfiguration.getKeyPrefixFor("MyCache")).isEqualTo("prefixMyCache::"); assertThat(redisCacheConfiguration.usePrefix()).isTrue(); @@ -289,7 +296,10 @@ void redisCacheWithRedisCacheConfiguration() { RedisCacheManager cacheManager = getCacheManager(context, RedisCacheManager.class); assertThat(cacheManager.getCacheNames()).isEmpty(); RedisCacheConfiguration redisCacheConfiguration = getDefaultRedisCacheConfiguration(cacheManager); - assertThat(redisCacheConfiguration.getTtl()).isEqualTo(java.time.Duration.ofSeconds(30)); + assertThat(redisCacheConfiguration).extracting(RedisCacheConfiguration::getTtlFunction) + .isInstanceOf(FixedDurationTtlFunction.class) + .extracting("duration") + .isEqualTo(java.time.Duration.ofSeconds(30)); assertThat(redisCacheConfiguration.getKeyPrefixFor("")).isEqualTo("bar::"); }); } @@ -301,7 +311,10 @@ void redisCacheWithRedisCacheManagerBuilderCustomizer() { .run((context) -> { RedisCacheManager cacheManager = getCacheManager(context, RedisCacheManager.class); RedisCacheConfiguration redisCacheConfiguration = getDefaultRedisCacheConfiguration(cacheManager); - assertThat(redisCacheConfiguration.getTtl()).isEqualTo(java.time.Duration.ofSeconds(10)); + assertThat(redisCacheConfiguration).extracting(RedisCacheConfiguration::getTtlFunction) + .isInstanceOf(FixedDurationTtlFunction.class) + .extracting("duration") + .isEqualTo(java.time.Duration.ofSeconds(10)); }); } @@ -321,7 +334,10 @@ void redisCacheExplicitWithCaches() { RedisCacheManager cacheManager = getCacheManager(context, RedisCacheManager.class); assertThat(cacheManager.getCacheNames()).containsOnly("foo", "bar"); RedisCacheConfiguration redisCacheConfiguration = getDefaultRedisCacheConfiguration(cacheManager); - assertThat(redisCacheConfiguration.getTtl()).isEqualTo(java.time.Duration.ofMinutes(0)); + assertThat(redisCacheConfiguration).extracting(RedisCacheConfiguration::getTtlFunction) + .isInstanceOf(FixedDurationTtlFunction.class) + .extracting("duration") + .isEqualTo(java.time.Duration.ofSeconds(0)); assertThat(redisCacheConfiguration.getAllowCacheNullValues()).isTrue(); assertThat(redisCacheConfiguration.getKeyPrefixFor("test")).isEqualTo("test::"); assertThat(redisCacheConfiguration.usePrefix()).isTrue(); @@ -447,6 +463,23 @@ void jCacheCacheUseBeanClassLoader() { }); } + @Test + void jCacheCacheWithPropertiesCustomizer() { + JCachePropertiesCustomizer customizer = mock(JCachePropertiesCustomizer.class); + willAnswer((invocation) -> { + invocation.getArgument(0, Properties.class).setProperty("customized", "true"); + return null; + }).given(customizer).customize(any(Properties.class)); + String cachingProviderFqn = MockCachingProvider.class.getName(); + this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) + .withPropertyValues("spring.cache.type=jcache", "spring.cache.jcache.provider=" + cachingProviderFqn) + .withBean(JCachePropertiesCustomizer.class, () -> customizer) + .run((context) -> { + JCacheCacheManager cacheManager = getCacheManager(context, JCacheCacheManager.class); + assertThat(cacheManager.getCacheManager().getProperties()).containsEntry("customized", "true"); + }); + } + @Test void hazelcastCacheExplicit() { this.contextRunner.withConfiguration(AutoConfigurations.of(HazelcastAutoConfiguration.class)) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReportTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReportTests.java index 2e130ff8c955..fc1df6ace290 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReportTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReportTests.java @@ -164,7 +164,7 @@ private void prepareMatches(boolean m1, boolean m2, boolean m3) { void springBootConditionPopulatesReport() { ConditionEvaluationReport report = ConditionEvaluationReport .get(new AnnotationConfigApplicationContext(Config.class).getBeanFactory()); - assertThat(report.getConditionAndOutcomesBySource().size()).isNotZero(); + assertThat(report.getConditionAndOutcomesBySource()).isNotEmpty(); } @Test diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnCheckpointRestoreTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnCheckpointRestoreTests.java new file mode 100644 index 000000000000..7e50b6423e69 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnCheckpointRestoreTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.condition; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.testsupport.classpath.ClassPathOverrides; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConditionalOnCheckpointRestore @ConditionalOnCheckpointRestore}. + * + * @author Andy Wilkinson + */ +class ConditionalOnCheckpointRestoreTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(BasicConfiguration.class); + + @Test + void whenCracIsUnavailableThenConditionDoesNotMatch() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean("someBean")); + } + + @Test + @ClassPathOverrides("org.crac:crac:1.3.0") + void whenCracIsAvailableThenConditionMatches() { + this.contextRunner.run((context) -> assertThat(context).hasBean("someBean")); + } + + @Configuration(proxyBeanMethods = false) + static class BasicConfiguration { + + @Bean + @ConditionalOnCheckpointRestore + String someBean() { + return "someBean"; + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java index 06072ca23151..9ca96314e9fa 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java @@ -155,7 +155,7 @@ void testOnMissingBeanConditionWithFactoryBean() { this.contextRunner .withUserConfiguration(FactoryBeanConfiguration.class, ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) - .run((context) -> assertThat(context.getBean(ExampleBean.class).toString()).isEqualTo("fromFactory")); + .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @Test @@ -163,7 +163,7 @@ void testOnMissingBeanConditionWithComponentScannedFactoryBean() { this.contextRunner .withUserConfiguration(ComponentScannedFactoryBeanBeanMethodConfiguration.class, ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) - .run((context) -> assertThat(context.getBean(ScanBean.class).toString()).isEqualTo("fromFactory")); + .run((context) -> assertThat(context.getBean(ScanBean.class)).hasToString("fromFactory")); } @Test @@ -171,7 +171,7 @@ void testOnMissingBeanConditionWithComponentScannedFactoryBeanWithBeanMethodArgu this.contextRunner .withUserConfiguration(ComponentScannedFactoryBeanBeanMethodWithArgumentsConfiguration.class, ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) - .run((context) -> assertThat(context.getBean(ScanBean.class).toString()).isEqualTo("fromFactory")); + .run((context) -> assertThat(context.getBean(ScanBean.class)).hasToString("fromFactory")); } @Test @@ -180,7 +180,7 @@ void testOnMissingBeanConditionWithFactoryBeanWithBeanMethodArguments() { .withUserConfiguration(FactoryBeanWithBeanMethodArgumentsConfiguration.class, ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) .withPropertyValues("theValue=foo") - .run((context) -> assertThat(context.getBean(ExampleBean.class).toString()).isEqualTo("fromFactory")); + .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @Test @@ -188,7 +188,7 @@ void testOnMissingBeanConditionWithConcreteFactoryBean() { this.contextRunner .withUserConfiguration(ConcreteFactoryBeanConfiguration.class, ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) - .run((context) -> assertThat(context.getBean(ExampleBean.class).toString()).isEqualTo("fromFactory")); + .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @Test @@ -205,7 +205,7 @@ void testOnMissingBeanConditionWithRegisteredFactoryBean() { this.contextRunner .withUserConfiguration(RegisteredFactoryBeanConfiguration.class, ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) - .run((context) -> assertThat(context.getBean(ExampleBean.class).toString()).isEqualTo("fromFactory")); + .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @Test @@ -213,7 +213,7 @@ void testOnMissingBeanConditionWithNonspecificFactoryBeanWithClassAttribute() { this.contextRunner .withUserConfiguration(NonspecificFactoryBeanClassAttributeConfiguration.class, ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) - .run((context) -> assertThat(context.getBean(ExampleBean.class).toString()).isEqualTo("fromFactory")); + .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @Test @@ -221,7 +221,7 @@ void testOnMissingBeanConditionWithNonspecificFactoryBeanWithStringAttribute() { this.contextRunner .withUserConfiguration(NonspecificFactoryBeanStringAttributeConfiguration.class, ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) - .run((context) -> assertThat(context.getBean(ExampleBean.class).toString()).isEqualTo("fromFactory")); + .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @Test @@ -229,7 +229,7 @@ void testOnMissingBeanConditionWithFactoryBeanInXml() { this.contextRunner .withUserConfiguration(FactoryBeanXmlConfiguration.class, ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) - .run((context) -> assertThat(context.getBean(ExampleBean.class).toString()).isEqualTo("fromFactory")); + .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @Test @@ -468,18 +468,6 @@ static class NonspecificFactoryBeanStringAttributeConfiguration { } - static class NonspecificFactoryBeanStringAttributeRegistrar implements ImportBeanDefinitionRegistrar { - - @Override - public void registerBeanDefinitions(AnnotationMetadata meta, BeanDefinitionRegistry registry) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(NonspecificFactoryBean.class); - builder.addConstructorArgValue("foo"); - builder.getBeanDefinition().setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, ExampleBean.class.getName()); - registry.registerBeanDefinition("exampleBeanFactoryBean", builder.getBeanDefinition()); - } - - } - @Configuration(proxyBeanMethods = false) @Import(FactoryBeanRegistrar.class) static class RegisteredFactoryBeanConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnThreadingTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnThreadingTests.java new file mode 100644 index 000000000000..a455b22f0f91 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnThreadingTests.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.condition; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; + +import org.springframework.boot.autoconfigure.thread.Threading; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConditionalOnThreading}. + * + * @author Moritz Halbritter + */ +class ConditionalOnThreadingTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(BasicConfiguration.class); + + @Test + @EnabledForJreRange(max = JRE.JAVA_20) + void platformThreadsOnJdkBelow21IfVirtualThreadsPropertyIsEnabled() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true") + .run((context) -> assertThat(context.getBean(ThreadType.class)).isEqualTo(ThreadType.PLATFORM)); + } + + @Test + @EnabledForJreRange(max = JRE.JAVA_20) + void platformThreadsOnJdkBelow21IfVirtualThreadsPropertyIsDisabled() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=false") + .run((context) -> assertThat(context.getBean(ThreadType.class)).isEqualTo(ThreadType.PLATFORM)); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void virtualThreadsOnJdk21IfVirtualThreadsPropertyIsEnabled() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true") + .run((context) -> assertThat(context.getBean(ThreadType.class)).isEqualTo(ThreadType.VIRTUAL)); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void platformThreadsOnJdk21IfVirtualThreadsPropertyIsDisabled() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=false") + .run((context) -> assertThat(context.getBean(ThreadType.class)).isEqualTo(ThreadType.PLATFORM)); + } + + private enum ThreadType { + + PLATFORM, VIRTUAL + + } + + @Configuration(proxyBeanMethods = false) + static class BasicConfiguration { + + @Bean + @ConditionalOnThreading(Threading.VIRTUAL) + ThreadType virtual() { + return ThreadType.VIRTUAL; + } + + @Bean + @ConditionalOnThreading(Threading.PLATFORM) + ThreadType platform() { + return ThreadType.PLATFORM; + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/context/MessageSourceAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/context/MessageSourceAutoConfigurationTests.java index 313624d8779f..e30983164136 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/context/MessageSourceAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/context/MessageSourceAutoConfigurationTests.java @@ -21,7 +21,10 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration.MessageSourceRuntimeHints; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ContextConsumer; @@ -40,6 +43,7 @@ * @author Eddú Meléndez * @author Stephane Nicoll * @author Kedar Joshi + * @author Marc Becker */ class MessageSourceAutoConfigurationTests { @@ -180,6 +184,15 @@ void messageSourceWithNonStandardBeanNameIsIgnored() { .run((context) -> assertThat(context.getMessage("foo", null, Locale.US)).isEqualTo("bar")); } + @Test + void shouldRegisterDefaultHints() { + RuntimeHints hints = new RuntimeHints(); + new MessageSourceRuntimeHints().registerHints(hints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.resource().forResource("messages.properties")).accepts(hints); + assertThat(RuntimeHintsPredicates.resource().forResource("messages_de.properties")).accepts(hints); + assertThat(RuntimeHintsPredicates.resource().forResource("messages_zh-CN.properties")).accepts(hints); + } + @Configuration(proxyBeanMethods = false) @PropertySource("classpath:/switch-messages.properties") static class Config { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationTests.java index 3dc81e27767a..6b38551d5f8d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -83,7 +83,7 @@ void shouldUseCustomConnectionDetailsWhenDefined() { .doesNotHaveBean(PropertiesCouchbaseConnectionDetails.class); Cluster cluster = context.getBean(Cluster.class); assertThat(cluster.core()).extracting("connectionString.hosts") - .asList() + .asInstanceOf(InstanceOfAssertFactories.LIST) .extractingResultOf("host") .containsExactly("couchbase.example.com"); }); @@ -109,7 +109,7 @@ void connectionDetailsShouldOverrideProperties() { assertThat(context).hasSingleBean(ClusterEnvironment.class).hasSingleBean(Cluster.class); Cluster cluster = context.getBean(Cluster.class); assertThat(cluster.core()).extracting("connectionString.hosts") - .asList() + .asInstanceOf(InstanceOfAssertFactories.LIST) .extractingResultOf("host") .containsExactly("couchbase.example.com"); }); @@ -189,15 +189,6 @@ void enableSsl() { }, "spring.couchbase.env.ssl.enabled=true"); } - @Test - void enableSslWithKeyStore() { - testClusterEnvironment((env) -> { - SecurityConfig securityConfig = env.securityConfig(); - assertThat(securityConfig.tlsEnabled()).isTrue(); - assertThat(securityConfig.trustManagerFactory()).isNotNull(); - }, "spring.couchbase.env.ssl.keyStore=classpath:test.jks", "spring.couchbase.env.ssl.keyStorePassword=secret"); - } - @Test void enableSslWithBundle() { testClusterEnvironment((env) -> { @@ -222,16 +213,6 @@ void enableSslWithInvalidBundle() { }); } - @Test - void disableSslEvenWithKeyStore() { - testClusterEnvironment((env) -> { - SecurityConfig securityConfig = env.securityConfig(); - assertThat(securityConfig.tlsEnabled()).isFalse(); - assertThat(securityConfig.trustManagerFactory()).isNull(); - }, "spring.couchbase.env.ssl.enabled=false", "spring.couchbase.env.ssl.keyStore=classpath:test.jks", - "spring.couchbase.env.ssl.keyStorePassword=secret"); - } - @Test void disableSslEvenWithBundle() { testClusterEnvironment((env) -> { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jdbc/JdbcRepositoriesAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jdbc/JdbcRepositoriesAutoConfigurationTests.java index 0ed97c82ab28..1b481cb5062a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jdbc/JdbcRepositoriesAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jdbc/JdbcRepositoriesAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,6 +40,7 @@ import org.springframework.data.jdbc.core.convert.DataAccessStrategy; import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; +import org.springframework.data.jdbc.core.dialect.JdbcPostgresDialect; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration; import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; @@ -58,6 +59,7 @@ * @author Andy Wilkinson * @author Stephane Nicoll * @author Mark Paluch + * @author Jens Schauder */ class JdbcRepositoriesAutoConfigurationTests { @@ -181,6 +183,16 @@ void allowsUserToDefineCustomDialect() { allowsUserToDefineCustomBean(DialectConfiguration.class, Dialect.class, "customDialect"); } + @Test + void allowsConfigurationOfDialectByProperty() { + this.contextRunner.with(database()) + .withPropertyValues("spring.data.jdbc.dialect=postgresql") + .withConfiguration(AutoConfigurations.of(JdbcTemplateAutoConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class)) + .withUserConfiguration(TestConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(JdbcPostgresDialect.class)); + } + private void allowsUserToDefineCustomBean(Class configuration, Class beanType, String beanName) { this.contextRunner.with(database()) .withConfiguration(AutoConfigurations.of(JdbcTemplateAutoConfiguration.class, @@ -232,7 +244,7 @@ static class JdbcMappingContextConfiguration { @Bean JdbcMappingContext customJdbcMappingContext() { - return mock(JdbcMappingContext.class); + return mock(JdbcMappingContext.class, Answers.RETURNS_MOCKS); } } @@ -242,7 +254,7 @@ static class JdbcConverterConfiguration { @Bean JdbcConverter customJdbcConverter() { - return mock(JdbcConverter.class); + return mock(JdbcConverter.class, Answers.RETURNS_MOCKS); } } @@ -262,7 +274,7 @@ static class JdbcAggregateTemplateConfiguration { @Bean JdbcAggregateTemplate customJdbcAggregateTemplate() { - return mock(JdbcAggregateTemplate.class); + return mock(JdbcAggregateTemplate.class, Answers.RETURNS_MOCKS); } } @@ -272,7 +284,7 @@ static class DataAccessStrategyConfiguration { @Bean DataAccessStrategy customDataAccessStrategy() { - return mock(DataAccessStrategy.class); + return mock(DataAccessStrategy.class, Answers.RETURNS_MOCKS); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataAutoConfigurationTests.java index 590fb36bdf1f..77a4b1497479 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,14 @@ import java.time.LocalDateTime; import java.util.Arrays; +import java.util.function.Supplier; import com.mongodb.ConnectionString; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.gridfs.GridFSBucket; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.BeanCreationException; @@ -46,6 +50,8 @@ import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; +import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoCustomConversions; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; @@ -78,32 +84,43 @@ void templateExists() { } @Test + @SuppressWarnings("unchecked") void whenGridFsDatabaseIsConfiguredThenGridFsTemplateIsAutoConfiguredAndUsesIt() { this.contextRunner.withPropertyValues("spring.data.mongodb.gridfs.database:grid").run((context) -> { assertThat(context).hasSingleBean(GridFsTemplate.class); GridFsTemplate template = context.getBean(GridFsTemplate.class); - MongoDatabaseFactory factory = (MongoDatabaseFactory) ReflectionTestUtils.getField(template, "dbFactory"); - assertThat(factory.getMongoDatabase().getName()).isEqualTo("grid"); + GridFSBucket bucket = ((Supplier) ReflectionTestUtils.getField(template, "bucketSupplier")) + .get(); + assertThat(bucket).extracting("filesCollection", InstanceOfAssertFactories.type(MongoCollection.class)) + .extracting((collection) -> collection.getNamespace().getDatabaseName()) + .isEqualTo("grid"); }); } @Test + @SuppressWarnings("unchecked") void usesMongoConnectionDetailsIfAvailable() { this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class).run((context) -> { assertThat(context).hasSingleBean(GridFsTemplate.class); GridFsTemplate template = context.getBean(GridFsTemplate.class); - assertThat(template).hasFieldOrPropertyWithValue("bucket", "connection-details-bucket"); - MongoDatabaseFactory factory = (MongoDatabaseFactory) ReflectionTestUtils.getField(template, "dbFactory"); - assertThat(factory.getMongoDatabase().getName()).isEqualTo("grid-database-1"); + GridFSBucket bucket = ((Supplier) ReflectionTestUtils.getField(template, "bucketSupplier")) + .get(); + assertThat(bucket.getBucketName()).isEqualTo("connection-details-bucket"); + assertThat(bucket).extracting("filesCollection", InstanceOfAssertFactories.type(MongoCollection.class)) + .extracting((collection) -> collection.getNamespace().getDatabaseName()) + .isEqualTo("grid-database-1"); }); } @Test + @SuppressWarnings("unchecked") void whenGridFsBucketIsConfiguredThenGridFsTemplateIsAutoConfiguredAndUsesIt() { this.contextRunner.withPropertyValues("spring.data.mongodb.gridfs.bucket:test-bucket").run((context) -> { assertThat(context).hasSingleBean(GridFsTemplate.class); GridFsTemplate template = context.getBean(GridFsTemplate.class); - assertThat(template).hasFieldOrPropertyWithValue("bucket", "test-bucket"); + GridFSBucket bucket = ((Supplier) ReflectionTestUtils.getField(template, "bucketSupplier")) + .get(); + assertThat(bucket.getBucketName()).isEqualTo("test-bucket"); }); } @@ -304,6 +321,14 @@ public ConnectionString getConnectionString() { .doesNotHaveBean(PropertiesMongoConnectionDetails.class)); } + @Test + void mappingMongoConverterHasADefaultDbRefResolver() { + this.contextRunner.run((context) -> { + MappingMongoConverter converter = context.getBean(MappingMongoConverter.class); + assertThat(converter).extracting("dbRefResolver").isInstanceOf(DefaultDbRefResolver.class); + }); + } + private static void assertDomainTypesDiscovered(MongoMappingContext mappingContext, Class... types) { ManagedTypes managedTypes = (ManagedTypes) ReflectionTestUtils.getField(mappingContext, "managedTypes"); assertThat(managedTypes.toList()).containsOnly(types); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoReactiveDataAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoReactiveDataAutoConfigurationTests.java index df9a5f264ee2..7f72ac71823f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoReactiveDataAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoReactiveDataAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,13 @@ package org.springframework.boot.autoconfigure.data.mongo; +import java.time.Duration; + import com.mongodb.ConnectionString; +import com.mongodb.reactivestreams.client.MongoCollection; +import com.mongodb.reactivestreams.client.gridfs.GridFSBucket; import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; @@ -30,6 +35,8 @@ import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory; import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; import org.springframework.data.mongodb.gridfs.ReactiveGridFsTemplate; import org.springframework.test.util.ReflectionTestUtils; @@ -68,20 +75,26 @@ void whenGridFsDatabaseIsConfiguredThenGridFsTemplateUsesIt() { } @Test + @SuppressWarnings("unchecked") void usesMongoConnectionDetailsIfAvailable() { this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class).run((context) -> { assertThat(grisFsTemplateDatabaseName(context)).isEqualTo("grid-database-1"); ReactiveGridFsTemplate template = context.getBean(ReactiveGridFsTemplate.class); - assertThat(template).hasFieldOrPropertyWithValue("bucket", "connection-details-bucket"); + GridFSBucket bucket = ((Mono) ReflectionTestUtils.getField(template, "bucketSupplier")) + .block(Duration.ofSeconds(30)); + assertThat(bucket.getBucketName()).isEqualTo("connection-details-bucket"); }); } @Test + @SuppressWarnings("unchecked") void whenGridFsBucketIsConfiguredThenGridFsTemplateUsesIt() { this.contextRunner.withPropertyValues("spring.data.mongodb.gridfs.bucket:test-bucket").run((context) -> { assertThat(context).hasSingleBean(ReactiveGridFsTemplate.class); ReactiveGridFsTemplate template = context.getBean(ReactiveGridFsTemplate.class); - assertThat(template).hasFieldOrPropertyWithValue("bucket", "test-bucket"); + GridFSBucket bucket = ((Mono) ReflectionTestUtils.getField(template, "bucketSupplier")) + .block(Duration.ofSeconds(30)); + assertThat(bucket.getBucketName()).isEqualTo("test-bucket"); }); } @@ -150,12 +163,22 @@ void contextFailsWhenDatabaseNotSet() { .run((context) -> assertThat(context).getFailure().hasMessageContaining("Database name must not be empty")); } + @Test + void mappingMongoConverterHasANoOpDbRefResolver() { + this.contextRunner.run((context) -> { + MappingMongoConverter converter = context.getBean(MappingMongoConverter.class); + assertThat(converter).extracting("dbRefResolver").isInstanceOf(NoOpDbRefResolver.class); + }); + } + + @SuppressWarnings("unchecked") private String grisFsTemplateDatabaseName(AssertableApplicationContext context) { assertThat(context).hasSingleBean(ReactiveGridFsTemplate.class); ReactiveGridFsTemplate template = context.getBean(ReactiveGridFsTemplate.class); - ReactiveMongoDatabaseFactory factory = (ReactiveMongoDatabaseFactory) ReflectionTestUtils.getField(template, - "dbFactory"); - return factory.getMongoDatabase().block().getName(); + GridFSBucket bucket = ((Mono) ReflectionTestUtils.getField(template, "bucketSupplier")) + .block(Duration.ofSeconds(30)); + MongoCollection collection = (MongoCollection) ReflectionTestUtils.getField(bucket, "filesCollection"); + return collection.getNamespace().getDatabaseName(); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java index 164e222fd800..517b4d5a3bef 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java @@ -19,14 +19,18 @@ import java.time.Duration; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.testsupport.assertj.SimpleAsyncTaskExecutorAssert; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; @@ -270,6 +274,25 @@ void testRedisConfigurationWithSslDisabledAndBundle() { }); } + @Test + void shouldUsePlatformThreadsByDefault() { + this.contextRunner.run((context) -> { + JedisConnectionFactory factory = context.getBean(JedisConnectionFactory.class); + assertThat(factory).extracting("executor").isNull(); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void shouldUseVirtualThreadsIfEnabled() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> { + JedisConnectionFactory factory = context.getBean(JedisConnectionFactory.class); + assertThat(factory).extracting("executor") + .satisfies((executor) -> SimpleAsyncTaskExecutorAssert.assertThat((SimpleAsyncTaskExecutor) executor) + .usesVirtualThreads()); + }); + } + private String getUserName(JedisConnectionFactory factory) { return ReflectionTestUtils.invokeMethod(factory, "getRedisUsername"); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java index b1fd47fb37dc..4e0802ff5893 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java @@ -31,6 +31,8 @@ import io.lettuce.core.tracing.Tracing; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool; @@ -38,8 +40,10 @@ import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ContextConsumer; +import org.springframework.boot.testsupport.assertj.SimpleAsyncTaskExecutorAssert; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisNode; @@ -123,6 +127,8 @@ void testCustomizeRedisConfiguration() { this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class); assertThat(cf.isUseSsl()).isTrue(); + assertThat(cf.getClientConfiguration().getClientOptions()) + .hasValueSatisfying((options) -> assertThat(options.isAutoReconnect()).isFalse()); }); } @@ -587,6 +593,25 @@ void testRedisConfigurationWithSslDisabledBundle() { }); } + @Test + void shouldUsePlatformThreadsByDefault() { + this.contextRunner.run((context) -> { + LettuceConnectionFactory factory = context.getBean(LettuceConnectionFactory.class); + assertThat(factory).extracting("executor").isNull(); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void shouldUseVirtualThreadsIfEnabled() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> { + LettuceConnectionFactory factory = context.getBean(LettuceConnectionFactory.class); + assertThat(factory).extracting("executor") + .satisfies((executor) -> SimpleAsyncTaskExecutorAssert.assertThat((SimpleAsyncTaskExecutor) executor) + .usesVirtualThreads()); + }); + } + private ContextConsumer assertClientOptions( Class expectedType, Consumer options) { return (context) -> { @@ -615,6 +640,11 @@ LettuceClientConfigurationBuilderCustomizer customizer() { return LettuceClientConfigurationBuilder::useSsl; } + @Bean + LettuceClientOptionsBuilderCustomizer clientOptionsBuilderCustomizer() { + return (builder) -> builder.autoReconnect(false); + } + } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway100AutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway100AutoConfigurationTests.java new file mode 100644 index 000000000000..047e37c40b95 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway100AutoConfigurationTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.flyway; + +import org.flywaydb.core.Flyway; +import org.flywaydb.core.api.Location; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; +import org.springframework.boot.testsupport.classpath.ClassPathOverrides; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link FlywayAutoConfiguration} with Flyway 10.0. + * + * @author Andy Wilkinson + */ +@ClassPathExclusions({ "flyway-core-*.jar", "flyway-sqlserver-*.jar" }) +@ClassPathOverrides({ "org.flywaydb:flyway-core:10.0.0", "com.h2database:h2:2.1.210" }) +class Flyway100AutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class)) + .withPropertyValues("spring.datasource.generate-unique-name=true"); + + @Test + void defaultFlyway() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(Flyway.class); + Flyway flyway = context.getBean(Flyway.class); + assertThat(flyway.getConfiguration().getLocations()) + .containsExactly(new Location("classpath:db/migration")); + }); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway10xAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway10xAutoConfigurationTests.java deleted file mode 100644 index e1e8ebdddbe3..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway10xAutoConfigurationTests.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.flyway; - -import org.flywaydb.core.Flyway; -import org.flywaydb.core.api.Location; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledForJreRange; -import org.junit.jupiter.api.condition.JRE; - -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.testsupport.classpath.ClassPathExclusions; -import org.springframework.boot.testsupport.classpath.ClassPathOverrides; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link FlywayAutoConfiguration} with Flyway 10.x. - * - * @author Andy Wilkinson - */ -@ClassPathExclusions({ "flyway-core-*.jar", "flyway-sqlserver-*.jar" }) -@ClassPathOverrides({ "org.flywaydb:flyway-core:10.0.0", "com.h2database:h2:2.1.210" }) -@EnabledForJreRange(min = JRE.JAVA_17) -class Flyway10xAutoConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class)) - .withPropertyValues("spring.datasource.generate-unique-name=true"); - - @Test - void defaultFlyway() { - this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> { - assertThat(context).hasSingleBean(Flyway.class); - Flyway flyway = context.getBean(Flyway.class); - assertThat(flyway.getConfiguration().getLocations()) - .containsExactly(new Location("classpath:db/migration")); - }); - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway90AutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway90AutoConfigurationTests.java deleted file mode 100644 index 6acaa1851ecb..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway90AutoConfigurationTests.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.flyway; - -import java.util.UUID; - -import org.flywaydb.core.Flyway; -import org.flywaydb.core.api.Location; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.testsupport.classpath.ClassPathOverrides; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link FlywayAutoConfiguration} with Flyway 9.0.x. - * - * @author Andy Wilkinson - */ -@ClassPathOverrides("org.flywaydb:flyway-core:9.0.4") -class Flyway90AutoConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class)) - .withPropertyValues("spring.datasource.url:jdbc:hsqldb:mem:" + UUID.randomUUID()); - - @Test - void flywayCanBeAutoConfigured() { - this.contextRunner.withUserConfiguration(DataSourceAutoConfiguration.class).run((context) -> { - assertThat(context).hasSingleBean(Flyway.class); - Flyway flyway = context.getBean(Flyway.class); - assertThat(flyway.getConfiguration().getLocations()) - .containsExactly(new Location("classpath:db/migration")); - }); - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway920AutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway920AutoConfigurationTests.java deleted file mode 100644 index 770a618e4725..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway920AutoConfigurationTests.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.flyway; - -import org.flywaydb.core.Flyway; -import org.flywaydb.core.api.Location; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.testsupport.classpath.ClassPathOverrides; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link FlywayAutoConfiguration} with Flyway 9.20. - * - * @author Andy Wilkinson - */ -@ClassPathOverrides({ "org.flywaydb:flyway-core:9.20.0", "org.flywaydb:flyway-sqlserver:9.20.0", - "com.h2database:h2:2.1.210" }) -class Flyway920AutoConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class)) - .withPropertyValues("spring.datasource.generate-unique-name=true"); - - @Test - void defaultFlyway() { - this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> { - assertThat(context).hasSingleBean(Flyway.class); - Flyway flyway = context.getBean(Flyway.class); - assertThat(flyway.getConfiguration().getLocations()) - .containsExactly(new Location("classpath:db/migration")); - }); - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java index b2148d973e5d..8522befc2596 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,8 +31,12 @@ import org.flywaydb.core.api.callback.Callback; import org.flywaydb.core.api.callback.Context; import org.flywaydb.core.api.callback.Event; +import org.flywaydb.core.api.configuration.FluentConfiguration; import org.flywaydb.core.api.migration.JavaMigration; -import org.flywaydb.core.internal.license.FlywayTeamsUpgradeRequiredException; +import org.flywaydb.core.internal.license.FlywayEditionUpgradeRequiredException; +import org.flywaydb.database.oracle.OracleConfigurationExtension; +import org.flywaydb.database.postgresql.PostgreSQLConfigurationExtension; +import org.flywaydb.database.sqlserver.SQLServerConfigurationExtension; import org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform; import org.jooq.DSLContext; import org.jooq.SQLDialect; @@ -48,6 +52,9 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayAutoConfigurationRuntimeHints; +import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.OracleFlywayConfigurationCustomizer; +import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.PostgresqlFlywayConfigurationCustomizer; +import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.SqlServerFlywayConfigurationCustomizer; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; @@ -59,7 +66,6 @@ import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ContextConsumer; -import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -83,6 +89,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -576,7 +583,10 @@ void callbackAndMigrationBeansAreAppliedToConfigurationBeforeCustomizersAreCalle void batchIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.batch=true") - .run(validateFlywayTeamsPropertyOnly("batch")); + .run((context) -> { + Flyway flyway = context.getBean(Flyway.class); + assertThat(flyway.getConfiguration().getModernConfig().getFlyway().getBatch()).isTrue(); + }); } @Test @@ -594,39 +604,114 @@ void errorOverridesIsCorrectlyMapped() { } @Test - void licenseKeyIsCorrectlyMapped(CapturedOutput output) { - this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.license-key=<>") - .run((context) -> assertThat(output).contains("License key detected - in order to use Teams or " - + "Enterprise features, download Flyway Teams Edition & Flyway Enterprise Edition")); + void oracleExtensionIsNotLoadedByDefault() { + FluentConfiguration configuration = mock(FluentConfiguration.class); + new OracleFlywayConfigurationCustomizer(new FlywayProperties()).customize(configuration); + then(configuration).shouldHaveNoInteractions(); } @Test void oracleSqlplusIsCorrectlyMapped() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.oracle.sqlplus=true") + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(OracleConfigurationExtension.class) + .getSqlplus()).isTrue()); + + } + + @Test + @Deprecated(since = "3.2.0", forRemoval = true) + void oracleSqlplusIsCorrectlyMappedWithDeprecatedProperty() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.oracle-sqlplus=true") - .run(validateFlywayTeamsPropertyOnly("oracle.sqlplus")); + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(OracleConfigurationExtension.class) + .getSqlplus()).isTrue()); + } @Test void oracleSqlplusWarnIsCorrectlyMapped() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.oracle.sqlplus-warn=true") + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(OracleConfigurationExtension.class) + .getSqlplusWarn()).isTrue()); + } + + @Test + @Deprecated(since = "3.2.0", forRemoval = true) + void oracleSqlplusWarnIsCorrectlyMappedWithDeprecatedProperty() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.oracle-sqlplus-warn=true") - .run(validateFlywayTeamsPropertyOnly("oracle.sqlplusWarn")); + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(OracleConfigurationExtension.class) + .getSqlplusWarn()).isTrue()); } @Test - void streamIsCorrectlyMapped() { + void oracleWallerLocationIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.stream=true") - .run(validateFlywayTeamsPropertyOnly("stream")); + .withPropertyValues("spring.flyway.oracle.wallet-location=/tmp/my.wallet") + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(OracleConfigurationExtension.class) + .getWalletLocation()).isEqualTo("/tmp/my.wallet")); } @Test - void undoSqlMigrationPrefix() { + @Deprecated(since = "3.2.0", forRemoval = true) + void oracleWallerLocationIsCorrectlyMappedWithDeprecatedProperty() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.undo-sql-migration-prefix=undo") - .run(validateFlywayTeamsPropertyOnly("undoSqlMigrationPrefix")); + .withPropertyValues("spring.flyway.oracle-wallet-location=/tmp/my.wallet") + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(OracleConfigurationExtension.class) + .getWalletLocation()).isEqualTo("/tmp/my.wallet")); + } + + @Test + void oracleKerberosCacheFileIsCorrectlyMapped() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.oracle.kerberos-cache-file=/tmp/cache") + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(OracleConfigurationExtension.class) + .getKerberosCacheFile()).isEqualTo("/tmp/cache")); + } + + @Test + @Deprecated(since = "3.2.0", forRemoval = true) + void oracleKerberosCacheFileIsCorrectlyMappedWithDeprecatedProperty() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.oracle-kerberos-cache-file=/tmp/cache") + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(OracleConfigurationExtension.class) + .getKerberosCacheFile()).isEqualTo("/tmp/cache")); + } + + @Test + void streamIsCorrectlyMapped() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.stream=true") + .run((context) -> { + Flyway flyway = context.getBean(Flyway.class); + assertThat(flyway.getConfiguration().getModernConfig().getFlyway().getStream()).isTrue(); + }); } @Test @@ -661,18 +746,17 @@ void initSqlsWithFlywayUrl() { }); } - @Test - void cherryPickIsCorrectlyMapped() { - this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.cherry-pick=1.1") - .run(validateFlywayTeamsPropertyOnly("cherryPick")); - } - @Test void jdbcPropertiesAreCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.jdbc-properties.prop=value") - .run(validateFlywayTeamsPropertyOnly("jdbcProperties")); + .run((context) -> { + Flyway flyway = context.getBean(Flyway.class); + assertThat(flyway.getConfiguration() + .getCachedResolvedEnvironments() + .get(flyway.getConfiguration().getCurrentEnvironmentName()) + .getJdbcProperties()).containsEntry("prop", "value"); + }); } @Test @@ -683,31 +767,76 @@ void kerberosConfigFileIsCorrectlyMapped() { } @Test - void oracleKerberosCacheFileIsCorrectlyMapped() { + void outputQueryResultsIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.oracle-kerberos-cache-file=/tmp/cache") - .run(validateFlywayTeamsPropertyOnly("oracle.kerberosCacheFile")); + .withPropertyValues("spring.flyway.output-query-results=false") + .run((context) -> { + Flyway flyway = context.getBean(Flyway.class); + assertThat(flyway.getConfiguration().getModernConfig().getFlyway().getOutputQueryResults()).isFalse(); + }); } @Test - void outputQueryResultsIsCorrectlyMapped() { + void postgresqlExtensionIsNotLoadedByDefault() { + FluentConfiguration configuration = mock(FluentConfiguration.class); + new PostgresqlFlywayConfigurationCustomizer(new FlywayProperties()).customize(configuration); + then(configuration).shouldHaveNoInteractions(); + } + + @Test + void postgresqlTransactionalLockIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.output-query-results=false") - .run(validateFlywayTeamsPropertyOnly("outputQueryResults")); + .withPropertyValues("spring.flyway.postgresql.transactional-lock=false") + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(PostgreSQLConfigurationExtension.class) + .isTransactionalLock()).isFalse()); + } + + @Test + void sqlServerExtensionIsNotLoadedByDefault() { + FluentConfiguration configuration = mock(FluentConfiguration.class); + new SqlServerFlywayConfigurationCustomizer(new FlywayProperties()).customize(configuration); + then(configuration).shouldHaveNoInteractions(); } @Test void sqlServerKerberosLoginFileIsCorrectlyMapped() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.sqlserver.kerberos-login-file=/tmp/config") + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(SQLServerConfigurationExtension.class) + .getKerberos() + .getLogin() + .getFile()).isEqualTo("/tmp/config")); + } + + @Test + @Deprecated(since = "3.2.0", forRemoval = true) + void sqlServerKerberosLoginFileIsCorrectlyMappedWithDeprecatedProperty() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.sql-server-kerberos-login-file=/tmp/config") - .run(validateFlywayTeamsPropertyOnly("sqlserver.kerberos.login.file")); + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(SQLServerConfigurationExtension.class) + .getKerberos() + .getLogin() + .getFile()).isEqualTo("/tmp/config")); } @Test void skipExecutingMigrationsIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.skip-executing-migrations=true") - .run(validateFlywayTeamsPropertyOnly("skipExecutingMigrations")); + .run((context) -> { + Flyway flyway = context.getBean(Flyway.class); + assertThat(flyway.getConfiguration().getModernConfig().getFlyway().getSkipExecutingMigrations()) + .isTrue(); + }); } @Test @@ -790,7 +919,7 @@ private ContextConsumer validateFlywayTeamsPropert return (context) -> { assertThat(context).hasFailed(); Throwable failure = context.getStartupFailure(); - assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class); + assertThat(failure).hasRootCauseInstanceOf(FlywayEditionUpgradeRequiredException.class); assertThat(failure).hasMessageContaining(String.format(" %s ", propertyName)); }; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java index 98c3454b227f..5a2c2c7b07f3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -93,6 +93,7 @@ void defaultValuesAreConsistent() { assertThat(properties.getScriptPlaceholderPrefix()).isEqualTo(configuration.getScriptPlaceholderPrefix()); assertThat(properties.getScriptPlaceholderSuffix()).isEqualTo(configuration.getScriptPlaceholderSuffix()); assertThat(properties.isExecuteInTransaction()).isEqualTo(configuration.isExecuteInTransaction()); + assertThat(properties.getCommunityDbSupportEnabled()).isNull(); } @Test @@ -109,14 +110,21 @@ void expectedPropertiesAreManaged() { PropertyAccessorFactory.forBeanPropertyAccess(new ClassicConfiguration())); // Properties specific settings ignoreProperties(properties, "url", "driverClassName", "user", "password", "enabled"); - // Property that moved to a separate SQL plugin - ignoreProperties(properties, "sqlServerKerberosLoginFile"); + // Deprecated properties + ignoreProperties(properties, "oracleKerberosCacheFile", "oracleSqlplus", "oracleSqlplusWarn", + "oracleWalletLocation", "sqlServerKerberosLoginFile"); + // Properties that are managed by specific extensions + ignoreProperties(properties, "oracle", "postgresql", "sqlserver"); + // https://github.com/flyway/flyway/issues/3732 + ignoreProperties(configuration, "environment"); // High level object we can't set with properties ignoreProperties(configuration, "callbacks", "classLoader", "dataSource", "javaMigrations", "javaMigrationClassProvider", "pluginRegister", "resourceProvider", "resolvers"); // Properties we don't want to expose ignoreProperties(configuration, "resolversAsClassNames", "callbacksAsClassNames", "driver", "modernConfig", - "currentResolvedEnvironment", "reportFilename"); + "currentResolvedEnvironment", "reportFilename", "reportEnabled", "workingDirectory", + "cachedDataSources", "cachedResolvedEnvironments", "currentEnvironmentName", "allEnvironments", + "environmentProvisionMode"); // Handled by the conversion service ignoreProperties(configuration, "baselineVersionAsString", "encodingAsString", "locationsAsStrings", "targetAsString"); @@ -128,9 +136,12 @@ void expectedPropertiesAreManaged() { // Handled as createSchemas ignoreProperties(configuration, "shouldCreateSchemas"); // Getters for the DataSource settings rather than actual properties - ignoreProperties(configuration, "password", "url", "user"); + ignoreProperties(configuration, "databaseType", "password", "url", "user"); // Properties not exposed by Flyway ignoreProperties(configuration, "failOnMissingTarget"); + // Properties managed by a proprietary extension + ignoreProperties(configuration, "cherryPick"); + aliasProperty(configuration, "communityDBSupportEnabled", "communityDbSupportEnabled"); List configurationKeys = new ArrayList<>(configuration.keySet()); Collections.sort(configurationKeys); List propertiesKeys = new ArrayList<>(properties.keySet()); @@ -145,6 +156,12 @@ private void ignoreProperties(Map index, String... propertyNames) { } } + private void aliasProperty(Map index, String originalName, String alias) { + PropertyDescriptor descriptor = index.remove(originalName); + assertThat(descriptor).describedAs("Property to alias should be present " + originalName).isNotNull(); + index.put(alias, descriptor); + } + private Map indexProperties(BeanWrapper beanWrapper) { Map descriptor = new HashMap<>(); for (PropertyDescriptor propertyDescriptor : beanWrapper.getPropertyDescriptors()) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/DefaultGraphQlSchemaConditionTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/DefaultGraphQlSchemaConditionTests.java index 7e54b9a1e9b6..197eaab69ec0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/DefaultGraphQlSchemaConditionTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/DefaultGraphQlSchemaConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,7 +54,7 @@ void matchesWhenCustomizerIsDetected() { didMatch(context); assertThat(conditionReportMessage(context)).contains( "@ConditionalOnGraphQlSchema did not find schema files in locations 'classpath:graphql/missing/'") - .contains("@ConditionalOnGraphQlSchema found customizer myBuilderCuystomizer"); + .contains("@ConditionalOnGraphQlSchema found customizer myBuilderCustomizer"); }); } @@ -98,7 +98,7 @@ String success() { static class CustomCustomizerConfiguration { @Bean - GraphQlSourceBuilderCustomizer myBuilderCuystomizer() { + GraphQlSourceBuilderCustomizer myBuilderCustomizer() { return (builder) -> { }; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfigurationTests.java index 4d935591da6c..75a44a5ce596 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfigurationTests.java @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.graphql; import java.nio.charset.StandardCharsets; +import java.util.concurrent.Executor; import graphql.GraphQL; import graphql.execution.instrumentation.ChainedInstrumentation; @@ -29,12 +30,16 @@ import graphql.schema.visibility.DefaultGraphqlFieldVisibility; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration.GraphQlResourcesRuntimeHints; +import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ByteArrayResource; @@ -54,6 +59,7 @@ /** * Tests for {@link GraphQlAutoConfiguration}. */ +@ExtendWith(OutputCaptureExtension.class) class GraphQlAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() @@ -120,7 +126,9 @@ void shouldConfigureDataFetcherExceptionResolvers() { assertThat(graphQL.getQueryStrategy()).extracting("dataFetcherExceptionHandler") .satisfies((exceptionHandler) -> { assertThat(exceptionHandler.getClass().getName()).endsWith("ExceptionResolversExceptionHandler"); - assertThat(exceptionHandler).extracting("resolvers").asList().hasSize(2); + assertThat(exceptionHandler).extracting("resolvers") + .asInstanceOf(InstanceOfAssertFactories.LIST) + .hasSize(2); }); }); } @@ -156,6 +164,11 @@ void shouldApplyGraphQlSourceBuilderCustomizer() { }); } + @Test + void schemaInspectionShouldBeEnabledByDefault(CapturedOutput output) { + this.contextRunner.run((context) -> assertThat(output).contains("GraphQL schema inspection")); + } + @Test void fieldIntrospectionShouldBeEnabledByDefault() { this.contextRunner.run((context) -> { @@ -202,12 +215,32 @@ void shouldContributeConnectionTypeDefinitionConfigurer() { GraphQlSource graphQlSource = context.getBean(GraphQlSource.class); GraphQLSchema schema = graphQlSource.schema(); GraphQLOutputType bookConnection = schema.getQueryType().getField("books").getType(); - assertThat(bookConnection).isNotNull().isInstanceOf(GraphQLObjectType.class); + assertThat(bookConnection).isInstanceOf(GraphQLObjectType.class); assertThat((GraphQLObjectType) bookConnection) .satisfies((connection) -> assertThat(connection.getFieldDefinition("edges")).isNotNull()); }); } + @Test + void whenApplicationTaskExecutorIsDefinedThenAnnotatedControllerConfigurerShouldUseIt() { + this.contextRunner.withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class)) + .run((context) -> { + AnnotatedControllerConfigurer annotatedControllerConfigurer = context + .getBean(AnnotatedControllerConfigurer.class); + assertThat(annotatedControllerConfigurer).extracting("executor") + .isSameAs(context.getBean("applicationTaskExecutor")); + }); + } + + @Test + void whenCustomExecutorIsDefinedThenAnnotatedControllerConfigurerDoesNotUseIt() { + this.contextRunner.withUserConfiguration(CustomExecutorConfiguration.class).run((context) -> { + AnnotatedControllerConfigurer annotatedControllerConfigurer = context + .getBean(AnnotatedControllerConfigurer.class); + assertThat(annotatedControllerConfigurer).extracting("executor").isNull(); + }); + } + @Configuration(proxyBeanMethods = false) static class CustomGraphQlBuilderConfiguration { @@ -293,4 +326,14 @@ public void customize(GraphQlSource.SchemaResourceBuilder builder) { } + @Configuration(proxyBeanMethods = false) + static class CustomExecutorConfiguration { + + @Bean + Executor customExecutor() { + return mock(Executor.class); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQueryByExampleAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQueryByExampleAutoConfigurationTests.java index 0998bfe10a92..964bf0ebaff6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQueryByExampleAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQueryByExampleAutoConfigurationTests.java @@ -49,7 +49,7 @@ class GraphQlQueryByExampleAutoConfigurationTests { .withConfiguration( AutoConfigurations.of(GraphQlAutoConfiguration.class, GraphQlQueryByExampleAutoConfiguration.class)) .withUserConfiguration(MockRepositoryConfig.class) - .withPropertyValues("spring.main.web-application-type=reactive"); + .withPropertyValues("spring.main.web-application-type=servlet"); @Test void shouldRegisterDataFetcherForQueryByExampleRepositories() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslAutoConfigurationTests.java index 3bbb3df8a03b..ff2624099b2d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlQuerydslAutoConfigurationTests.java @@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.graphql.Book; import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -33,6 +34,7 @@ import org.springframework.graphql.test.tester.ExecutionGraphQlServiceTester; import org.springframework.graphql.test.tester.GraphQlTester; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -50,7 +52,7 @@ class GraphQlQuerydslAutoConfigurationTests { .withConfiguration( AutoConfigurations.of(GraphQlAutoConfiguration.class, GraphQlQuerydslAutoConfiguration.class)) .withUserConfiguration(MockRepositoryConfig.class) - .withPropertyValues("spring.main.web-application-type=reactive"); + .withPropertyValues("spring.main.web-application-type=servlet"); @Test void shouldRegisterDataFetcherForQueryDslRepositories() { @@ -65,6 +67,13 @@ void shouldRegisterDataFetcherForQueryDslRepositories() { }); } + @Test + void shouldBackOffWithoutQueryDsl() { + this.contextRunner.withClassLoader(new FilteredClassLoader("com.querydsl.core")) + .run((context) -> assertThat(context).doesNotHaveBean("querydslRegistrar") + .doesNotHaveBean(GraphQlQuerydslAutoConfiguration.class)); + } + @Configuration(proxyBeanMethods = false) static class MockRepositoryConfig { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQuerydslAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQuerydslAutoConfigurationTests.java index 9901d096cb75..8c807fe98984 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQuerydslAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/data/GraphQlReactiveQuerydslAutoConfigurationTests.java @@ -22,6 +22,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.graphql.Book; import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -32,6 +33,7 @@ import org.springframework.graphql.test.tester.ExecutionGraphQlServiceTester; import org.springframework.graphql.test.tester.GraphQlTester; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -64,6 +66,13 @@ void shouldRegisterDataFetcherForQueryDslRepositories() { }); } + @Test + void shouldBackOffWithoutQueryDsl() { + this.contextRunner.withClassLoader(new FilteredClassLoader("com.querydsl.core")) + .run((context) -> assertThat(context).doesNotHaveBean("querydslRegistrar") + .doesNotHaveBean(GraphQlReactiveQuerydslAutoConfiguration.class)); + } + @Configuration(proxyBeanMethods = false) static class MockRepositoryConfig { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfigurationTests.java index f695221ab07a..bf4dc6695da8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.graphql.reactive; +import java.time.Duration; import java.util.Collections; import java.util.Map; import java.util.function.Consumer; @@ -44,6 +45,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.EntityExchangeResult; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.reactive.function.server.RouterFunction; @@ -93,6 +95,29 @@ void simpleQueryShouldWork() { }); } + @Test + void SseSubscriptionShouldWork() { + testWithWebClient((client) -> { + String query = "{ booksOnSale(minPages: 50){ id name pageCount author } }"; + EntityExchangeResult result = client.post() + .uri("/graphql") + .accept(MediaType.TEXT_EVENT_STREAM) + .bodyValue("{ \"query\": \"subscription TestSubscription " + query + "\"}") + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentTypeCompatibleWith(MediaType.TEXT_EVENT_STREAM) + .expectBody(String.class) + .returnResult(); + assertThat(result.getResponseBody()).contains("event:next", + "data:{\"data\":{\"booksOnSale\":{\"id\":\"book-1\",\"name\":\"GraphQL for beginners\",\"pageCount\":100,\"author\":\"John GraphQL\"}}}", + "event:next", + "data:{\"data\":{\"booksOnSale\":{\"id\":\"book-2\",\"name\":\"Harry Potter and the Philosopher's Stone\",\"pageCount\":223,\"author\":\"Joanne Rowling\"}}}", + "event:complete"); + }); + } + @Test void httpGetQueryShouldBeSupported() { testWithWebClient((client) -> { @@ -196,6 +221,20 @@ void shouldConfigureWebSocketBeans() { .run((context) -> assertThat(context).hasSingleBean(GraphQlWebSocketHandler.class)); } + @Test + void shouldConfigureWebSocketProperties() { + this.contextRunner + .withPropertyValues("spring.graphql.websocket.path=/ws", + "spring.graphql.websocket.connection-init-timeout=120s", "spring.graphql.websocket.keep-alive=30s") + .run((context) -> { + assertThat(context).hasSingleBean(GraphQlWebSocketHandler.class); + GraphQlWebSocketHandler graphQlWebSocketHandler = context.getBean(GraphQlWebSocketHandler.class); + assertThat(graphQlWebSocketHandler).extracting("initTimeoutDuration") + .isEqualTo(Duration.ofSeconds(120)); + assertThat(graphQlWebSocketHandler).extracting("keepAliveDuration").isEqualTo(Duration.ofSeconds(30)); + }); + } + @Test void routerFunctionShouldHaveOrderZero() { this.contextRunner.withUserConfiguration(CustomRouterFunctions.class).run((context) -> { @@ -233,8 +272,12 @@ static class DataFetchersConfiguration { @Bean RuntimeWiringConfigurer bookDataFetcher() { - return (builder) -> builder.type(TypeRuntimeWiring.newTypeWiring("Query") - .dataFetcher("bookById", GraphQlTestDataFetchers.getBookByIdDataFetcher())); + return (builder) -> { + builder.type(TypeRuntimeWiring.newTypeWiring("Query") + .dataFetcher("bookById", GraphQlTestDataFetchers.getBookByIdDataFetcher())); + builder.type(TypeRuntimeWiring.newTypeWiring("Subscription") + .dataFetcher("booksOnSale", GraphQlTestDataFetchers.getBooksOnSaleDataFetcher())); + }; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebFluxSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebFluxSecurityAutoConfigurationTests.java index f388ac354114..3d8fcc9fa677 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebFluxSecurityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebFluxSecurityAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 the original author or authors. + * Copyright 2020-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,6 +45,7 @@ import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity.CsrfSpec; import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; @@ -161,7 +162,7 @@ static class SecurityConfig { @Bean SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) { - return http.csrf((spec) -> spec.disable()) + return http.csrf(CsrfSpec::disable) // Demonstrate that method security works // Best practice to use both for defense in depth .authorizeExchange((requests) -> requests.anyExchange().permitAll()) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebMvcSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebMvcSecurityAutoConfigurationTests.java index 45ea3fdcc818..809668c190bd 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebMvcSecurityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebMvcSecurityAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.graphql.security; import graphql.schema.idl.TypeRuntimeWiring; +import org.assertj.core.api.ThrowingConsumer; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -41,21 +42,18 @@ import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.DefaultSecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link GraphQlWebMvcSecurityAutoConfiguration}. @@ -80,46 +78,46 @@ void contributesSecurityComponents() { @Test void anonymousUserShouldBeUnauthorized() { - testWith((mockMvc) -> { + withMockMvc((mvc) -> { String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author }}"; - mockMvc.perform(post("/graphql").content("{\"query\": \"" + query + "\"}")) - .andExpect(status().isOk()) - .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("data.bookById.name").doesNotExist()) - .andExpect(jsonPath("errors[0].extensions.classification").value(ErrorType.UNAUTHORIZED.toString())); + assertThat(mvc.post().uri("/graphql").content("{\"query\": \"" + query + "\"}")).satisfies((result) -> { + assertThat(result).hasStatusOk().hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON); + assertThat(result).bodyJson() + .doesNotHavePath("data.bookById.name") + .extractingPath("errors[0].extensions.classification") + .asString() + .isEqualTo(ErrorType.UNAUTHORIZED.toString()); + }); }); } @Test void authenticatedUserShouldGetData() { - testWith((mockMvc) -> { + withMockMvc((mvc) -> { String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author }}"; - mockMvc.perform(post("/graphql").content("{\"query\": \"" + query + "\"}").with(user("rob"))) - .andExpect(status().isOk()) - .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("data.bookById.name").value("GraphQL for beginners")) - .andExpect(jsonPath("errors").doesNotExist()); + assertThat(mvc.post().uri("/graphql").content("{\"query\": \"" + query + "\"}").with(user("rob"))) + .satisfies((result) -> { + assertThat(result).hasStatusOk().hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON); + assertThat(result).bodyJson() + .doesNotHavePath("errors") + .extractingPath("data.bookById.name") + .asString() + .isEqualTo("GraphQL for beginners"); + }); }); - } - private void testWith(MockMvcConsumer mockMvcConsumer) { + private void withMockMvc(ThrowingConsumer mvc) { this.contextRunner.run((context) -> { MediaType mediaType = MediaType.APPLICATION_JSON; - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context) - .defaultRequest(post("/graphql").contentType(mediaType).accept(mediaType)) - .apply(springSecurity()) - .build(); - mockMvcConsumer.accept(mockMvc); + MockMvcTester mockMVc = MockMvcTester.from(context, + (builder) -> builder.defaultRequest(post("/graphql").contentType(mediaType).accept(mediaType)) + .apply(springSecurity()) + .build()); + mvc.accept(mockMVc); }); } - private interface MockMvcConsumer { - - void accept(MockMvc mockMvc) throws Exception; - - } - @Configuration(proxyBeanMethods = false) static class DataFetchersConfiguration { @@ -154,7 +152,7 @@ static class SecurityConfig { @Bean DefaultSecurityFilterChain springWebFilterChain(HttpSecurity http) throws Exception { - return http.csrf((c) -> c.disable()) + return http.csrf(CsrfConfigurer::disable) // Demonstrate that method security works // Best practice to use both for defense in depth .authorizeHttpRequests((requests) -> requests.anyRequest().permitAll()) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java index a1e03f3f73db..260f122c1410 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java @@ -16,10 +16,12 @@ package org.springframework.boot.autoconfigure.graphql.servlet; +import java.time.Duration; +import java.util.List; import java.util.Map; import graphql.schema.idl.TypeRuntimeWiring; -import org.hamcrest.Matchers; +import org.assertj.core.api.ThrowingConsumer; import org.junit.jupiter.api.Test; import org.springframework.aot.hint.RuntimeHints; @@ -41,19 +43,17 @@ import org.springframework.graphql.server.webmvc.GraphQlHttpHandler; import org.springframework.graphql.server.webmvc.GraphQlWebSocketHandler; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.web.servlet.assertj.MockMvcTester; +import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.function.RouterFunction; +import org.springframework.web.servlet.function.support.RouterFunctionMapping; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import org.springframework.web.socket.server.support.WebSocketHandlerMapping; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link GraphQlWebMvcAutoConfiguration}. @@ -80,84 +80,125 @@ void shouldContributeDefaultBeans() { @Test void simpleQueryShouldWork() { - testWith((mockMvc) -> { + withMockMvc((mvc) -> { String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author } }"; - mockMvc.perform(post("/graphql").content("{\"query\": \"" + query + "\"}")) - .andExpect(status().isOk()) - .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_GRAPHQL_RESPONSE)) - .andExpect(jsonPath("data.bookById.name").value("GraphQL for beginners")); + assertThat(mvc.post().uri("/graphql").content("{\"query\": \"" + query + "\"}")).satisfies((result) -> { + assertThat(result).hasStatusOk().hasContentTypeCompatibleWith(MediaType.APPLICATION_GRAPHQL_RESPONSE); + assertThat(result).bodyJson() + .extractingPath("data.bookById.name") + .asString() + .isEqualTo("GraphQL for beginners"); + }); + }); + } + + @Test + void SseSubscriptionShouldWork() { + withMockMvc((mvc) -> { + String query = "{ booksOnSale(minPages: 50){ id name pageCount author } }"; + assertThat(mvc.post() + .uri("/graphql") + .accept(MediaType.TEXT_EVENT_STREAM) + .content("{\"query\": \"subscription TestSubscription " + query + "\"}")).satisfies((result) -> { + assertThat(result).hasStatusOk().hasContentTypeCompatibleWith(MediaType.TEXT_EVENT_STREAM); + assertThat(result).bodyText() + .containsSubsequence("event:next", + "data:{\"data\":{\"booksOnSale\":{\"id\":\"book-1\",\"name\":\"GraphQL for beginners\",\"pageCount\":100,\"author\":\"John GraphQL\"}}}", + "event:next", + "data:{\"data\":{\"booksOnSale\":{\"id\":\"book-2\",\"name\":\"Harry Potter and the Philosopher's Stone\",\"pageCount\":223,\"author\":\"Joanne Rowling\"}}}"); + }); }); } @Test void httpGetQueryShouldBeSupported() { - testWith((mockMvc) -> { + withMockMvc((mvc) -> { String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author } }"; - mockMvc.perform(get("/graphql?query={query}", "{\"query\": \"" + query + "\"}")) - .andExpect(status().isMethodNotAllowed()) - .andExpect(header().string("Allow", "POST")); + assertThat(mvc.get().uri("/graphql?query={query}", "{\"query\": \"" + query + "\"}")) + .hasStatus(HttpStatus.METHOD_NOT_ALLOWED) + .headers() + .hasValue("Allow", "POST"); }); } @Test void shouldRejectMissingQuery() { - testWith((mockMvc) -> mockMvc.perform(post("/graphql").content("{}")).andExpect(status().isBadRequest())); + withMockMvc((mvc) -> assertThat(mvc.post().uri("/graphql").content("{}")).hasStatus(HttpStatus.BAD_REQUEST)); } @Test void shouldRejectQueryWithInvalidJson() { - testWith((mockMvc) -> mockMvc.perform(post("/graphql").content(":)")).andExpect(status().isBadRequest())); + withMockMvc((mvc) -> assertThat(mvc.post().uri("/graphql").content(":)")).hasStatus(HttpStatus.BAD_REQUEST)); } @Test void shouldConfigureWebInterceptors() { - testWith((mockMvc) -> { + withMockMvc((mvc) -> { String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author } }"; - mockMvc.perform(post("/graphql").content("{\"query\": \"" + query + "\"}")) - .andExpect(status().isOk()) - .andExpect(header().string("X-Custom-Header", "42")); + assertThat(mvc.post().uri("/graphql").content("{\"query\": \"" + query + "\"}")).hasStatusOk() + .headers() + .hasValue("X-Custom-Header", "42"); }); } @Test void shouldExposeSchemaEndpoint() { - testWith((mockMvc) -> mockMvc.perform(get("/graphql/schema")) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.TEXT_PLAIN)) - .andExpect(content().string(Matchers.containsString("type Book")))); + withMockMvc((mvc) -> assertThat(mvc.get().uri("/graphql/schema")).hasStatusOk() + .hasContentType(MediaType.TEXT_PLAIN) + .bodyText() + .contains("type Book")); } @Test void shouldExposeGraphiqlEndpoint() { - testWith((mockMvc) -> { - mockMvc.perform(get("/graphiql")) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/graphiql?path=/graphql")); - mockMvc.perform(get("/graphiql?path=/graphql")) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.TEXT_HTML)); + withMockMvc((mvc) -> { + assertThat(mvc.get().uri("/graphiql")).hasStatus3xxRedirection() + .hasRedirectedUrl("http://localhost/graphiql?path=/graphql"); + assertThat(mvc.get().uri("/graphiql?path=/graphql")).hasStatusOk() + .contentType() + .isEqualTo(MediaType.TEXT_HTML); }); } @Test void shouldSupportCors() { - testWith((mockMvc) -> { + withMockMvc((mvc) -> { String query = "{" + " bookById(id: \\\"book-1\\\"){ " + " id" + " name" + " pageCount" + " author" + " }" + "}"; - mockMvc - .perform(post("/graphql").header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST") - .header(HttpHeaders.ORIGIN, "https://example.com") - .content("{\"query\": \"" + query + "\"}")) - .andExpect(status().isOk()) - .andExpect(header().stringValues(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "https://example.com")) - .andExpect(header().stringValues(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true")); + assertThat(mvc.post() + .uri("/graphql") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST") + .header(HttpHeaders.ORIGIN, "https://example.com") + .content("{\"query\": \"" + query + "\"}")) + .satisfies((result) -> assertThat(result).hasStatusOk() + .headers() + .containsEntry(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, List.of("https://example.com")) + .containsEntry(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, List.of("true"))); }); } @Test void shouldConfigureWebSocketBeans() { - this.contextRunner.withPropertyValues("spring.graphql.websocket.path=/ws") - .run((context) -> assertThat(context).hasSingleBean(GraphQlWebSocketHandler.class)); + this.contextRunner.withPropertyValues("spring.graphql.websocket.path=/ws").run((context) -> { + assertThat(context).hasSingleBean(GraphQlWebSocketHandler.class); + assertThat(context.getBeanProvider(HandlerMapping.class).orderedStream().toList()).containsSubsequence( + context.getBean(WebSocketHandlerMapping.class), context.getBean(RouterFunctionMapping.class), + context.getBean(RequestMappingHandlerMapping.class)); + }); + } + + @Test + void shouldConfigureWebSocketProperties() { + this.contextRunner + .withPropertyValues("spring.graphql.websocket.path=/ws", + "spring.graphql.websocket.connection-init-timeout=120s", "spring.graphql.websocket.keep-alive=30s") + .run((context) -> { + assertThat(context).hasSingleBean(GraphQlWebSocketHandler.class); + GraphQlWebSocketHandler graphQlWebSocketHandler = context.getBean(GraphQlWebSocketHandler.class); + assertThat(graphQlWebSocketHandler).extracting("initTimeoutDuration") + .isEqualTo(Duration.ofSeconds(120)); + assertThat(graphQlWebSocketHandler).extracting("keepAliveDuration").isEqualTo(Duration.ofSeconds(30)); + }); } @Test @@ -178,29 +219,28 @@ void shouldRegisterHints() { assertThat(RuntimeHintsPredicates.resource().forResource("graphiql/index.html")).accepts(hints); } - private void testWith(MockMvcConsumer mockMvcConsumer) { + private void withMockMvc(ThrowingConsumer mvc) { this.contextRunner.run((context) -> { - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context) - .defaultRequest(post("/graphql").contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_GRAPHQL_RESPONSE)) - .build(); - mockMvcConsumer.accept(mockMvc); + MockMvcTester mockMVc = MockMvcTester.from(context, + (builder) -> builder + .defaultRequest(post("/graphql").contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_GRAPHQL_RESPONSE)) + .build()); + mvc.accept(mockMVc); }); } - private interface MockMvcConsumer { - - void accept(MockMvc mockMvc) throws Exception; - - } - @Configuration(proxyBeanMethods = false) static class DataFetchersConfiguration { @Bean RuntimeWiringConfigurer bookDataFetcher() { - return (builder) -> builder.type(TypeRuntimeWiring.newTypeWiring("Query") - .dataFetcher("bookById", GraphQlTestDataFetchers.getBookByIdDataFetcher())); + return (builder) -> { + builder.type(TypeRuntimeWiring.newTypeWiring("Query") + .dataFetcher("bookById", GraphQlTestDataFetchers.getBookByIdDataFetcher())); + builder.type(TypeRuntimeWiring.newTypeWiring("Subscription") + .dataFetcher("booksOnSale", GraphQlTestDataFetchers.getBooksOnSaleDataFetcher())); + }; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/gson/GsonAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/gson/GsonAutoConfigurationTests.java index e53b013aefaf..17d2fa3b9af7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/gson/GsonAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/gson/GsonAutoConfigurationTests.java @@ -28,6 +28,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.LongSerializationPolicy; +import com.google.gson.Strictness; import org.junit.jupiter.api.Test; import org.skyscreamer.jsonassert.JSONAssert; @@ -209,26 +210,61 @@ void withPrettyPrintingFalse() { } @Test + @Deprecated(since = "3.4.0", forRemoval = true) void withoutLenient() { this.contextRunner.run((context) -> { Gson gson = context.getBean(Gson.class); - assertThat(gson).hasFieldOrPropertyWithValue("lenient", false); + assertThat(gson).hasFieldOrPropertyWithValue("strictness", null); }); } @Test + @Deprecated(since = "3.4.0", forRemoval = true) void withLenientTrue() { this.contextRunner.withPropertyValues("spring.gson.lenient:true").run((context) -> { Gson gson = context.getBean(Gson.class); - assertThat(gson).hasFieldOrPropertyWithValue("lenient", true); + assertThat(gson).hasFieldOrPropertyWithValue("strictness", Strictness.LENIENT); }); } @Test + @Deprecated(since = "3.4.0", forRemoval = true) void withLenientFalse() { this.contextRunner.withPropertyValues("spring.gson.lenient:false").run((context) -> { Gson gson = context.getBean(Gson.class); - assertThat(gson).hasFieldOrPropertyWithValue("lenient", false); + assertThat(gson).hasFieldOrPropertyWithValue("strictness", Strictness.STRICT); + }); + } + + @Test + void withoutStrictness() { + this.contextRunner.run((context) -> { + Gson gson = context.getBean(Gson.class); + assertThat(gson).hasFieldOrPropertyWithValue("strictness", null); + }); + } + + @Test + void withStrictnessStrict() { + this.contextRunner.withPropertyValues("spring.gson.strictness:strict").run((context) -> { + Gson gson = context.getBean(Gson.class); + assertThat(gson).hasFieldOrPropertyWithValue("strictness", Strictness.STRICT); + }); + } + + @Test + void withStrictnessLegacyStrict() { + this.contextRunner.withPropertyValues("spring.gson.strictness:legacy-strict").run((context) -> { + Gson gson = context.getBean(Gson.class); + assertThat(gson).hasFieldOrPropertyWithValue("strictness", Strictness.LEGACY_STRICT); + }); + } + + @Test + void withStrictnessLenient() { + this.contextRunner.withPropertyValues("spring.gson.strictness:lenient").run((context) -> { + Gson gson = context.getBean(Gson.class); + assertThat(gson).hasFieldOrPropertyWithValue("strictness", Strictness.LENIENT); }); } @@ -244,7 +280,7 @@ void withoutDisableHtmlEscaping() { void withDisableHtmlEscapingTrue() { this.contextRunner.withPropertyValues("spring.gson.disable-html-escaping:true").run((context) -> { Gson gson = context.getBean(Gson.class); - assertThat(gson.htmlSafe()).isTrue(); + assertThat(gson.htmlSafe()).isFalse(); }); } @@ -252,7 +288,7 @@ void withDisableHtmlEscapingTrue() { void withDisableHtmlEscapingFalse() { this.contextRunner.withPropertyValues("spring.gson.disable-html-escaping:false").run((context) -> { Gson gson = context.getBean(Gson.class); - assertThat(gson.htmlSafe()).isFalse(); + assertThat(gson.htmlSafe()).isTrue(); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersTests.java index 4a93124f2d5e..17dcc631d124 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -146,7 +146,7 @@ protected List> postProcessPartConverters( } assertThat(converterClasses).containsExactly(ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class, ResourceHttpMessageConverter.class, - MappingJackson2HttpMessageConverter.class); + MappingJackson2HttpMessageConverter.class, MappingJackson2CborHttpMessageConverter.class); } private List> extractFormPartConverters(List> converters) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/influx/InfluxDbAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/influx/InfluxDbAutoConfigurationTests.java deleted file mode 100644 index 7d37ee994cc4..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/influx/InfluxDbAutoConfigurationTests.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.influx; - -import java.util.concurrent.TimeUnit; - -import okhttp3.OkHttpClient; -import org.influxdb.InfluxDB; -import org.junit.jupiter.api.Test; -import retrofit2.Retrofit; - -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.assertj.AssertableApplicationContext; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.test.util.ReflectionTestUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link InfluxDbAutoConfiguration}. - * - * @author Sergey Kuptsov - * @author Stephane Nicoll - * @author Eddú Meléndez - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - */ -class InfluxDbAutoConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(InfluxDbAutoConfiguration.class)); - - @Test - void influxDbRequiresUrl() { - this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(InfluxDB.class)); - } - - @Test - void influxDbCanBeCustomized() { - this.contextRunner - .withPropertyValues("spring.influx.url=http://localhost", "spring.influx.user=user", - "spring.influx.password=password") - .run((context) -> assertThat(context).hasSingleBean(InfluxDB.class)); - } - - @Test - void influxDbCanBeCreatedWithoutCredentials() { - this.contextRunner.withPropertyValues("spring.influx.url=http://localhost").run((context) -> { - assertThat(context).hasSingleBean(InfluxDB.class); - int readTimeout = getReadTimeoutProperty(context); - assertThat(readTimeout).isEqualTo(10_000); - }); - } - - @Test - void influxDbWithOkHttpClientBuilderProvider() { - this.contextRunner.withUserConfiguration(CustomOkHttpClientBuilderProviderConfig.class) - .withPropertyValues("spring.influx.url=http://localhost") - .run((context) -> { - assertThat(context).hasSingleBean(InfluxDB.class); - int readTimeout = getReadTimeoutProperty(context); - assertThat(readTimeout).isEqualTo(40_000); - }); - } - - @Test - void influxDbWithCustomizer() { - this.contextRunner.withBean(InfluxDbCustomizer.class, () -> (influxDb) -> influxDb.setDatabase("test")) - .withPropertyValues("spring.influx.url=http://localhost") - .run((context) -> { - assertThat(context).hasSingleBean(InfluxDB.class); - InfluxDB influxDb = context.getBean(InfluxDB.class); - assertThat(influxDb).hasFieldOrPropertyWithValue("database", "test"); - }); - } - - private int getReadTimeoutProperty(AssertableApplicationContext context) { - InfluxDB influxDb = context.getBean(InfluxDB.class); - Retrofit retrofit = (Retrofit) ReflectionTestUtils.getField(influxDb, "retrofit"); - OkHttpClient callFactory = (OkHttpClient) retrofit.callFactory(); - return callFactory.readTimeoutMillis(); - } - - @Configuration(proxyBeanMethods = false) - static class CustomOkHttpClientBuilderProviderConfig { - - @Bean - InfluxDbOkHttpClientBuilderProvider influxDbOkHttpClientBuilderProvider() { - return () -> new OkHttpClient.Builder().readTimeout(40, TimeUnit.SECONDS); - } - - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfigurationTests.java index 9d8680999811..7a65682b9306 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfigurationTests.java @@ -119,7 +119,7 @@ void buildPropertiesCustomLocation() { @Test void buildPropertiesCustomInvalidLocation() { this.contextRunner.withPropertyValues("spring.info.build.location=classpath:/org/acme/no-build-info.properties") - .run((context) -> assertThat(context.getBeansOfType(BuildProperties.class)).hasSize(0)); + .run((context) -> assertThat(context.getBeansOfType(BuildProperties.class)).isEmpty()); } @Test diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationTests.java index 43849aa4361e..f4c7bca9e87a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,13 @@ package org.springframework.boot.autoconfigure.integration; +import java.beans.PropertyDescriptor; import java.time.Duration; +import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; import javax.management.MBeanServer; import javax.sql.DataSource; @@ -32,6 +35,7 @@ import reactor.core.publisher.Mono; import org.springframework.beans.DirectFieldAccessor; +import org.springframework.beans.PropertyAccessorFactory; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration.IntegrationComponentScanConfiguration; @@ -82,6 +86,7 @@ import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.support.CronTrigger; import org.springframework.scheduling.support.PeriodicTrigger; +import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -295,55 +300,63 @@ void taskSchedulerCanBeCustomized() { @Test void integrationGlobalPropertiesAutoConfigured() { - this.contextRunner.withPropertyValues("spring.integration.channel.auto-create=false", + String[] propertyValues = { "spring.integration.channel.auto-create=false", "spring.integration.channel.max-unicast-subscribers=2", "spring.integration.channel.max-broadcast-subscribers=3", "spring.integration.error.require-subscribers=false", "spring.integration.error.ignore-failures=false", + "spring.integration.endpoint.defaultTimeout=60s", "spring.integration.endpoint.throw-exception-on-late-reply=true", "spring.integration.endpoint.read-only-headers=ignoredHeader", - "spring.integration.endpoint.no-auto-startup=notStartedEndpoint,_org.springframework.integration.errorLogger") - .run((context) -> { - assertThat(context).hasSingleBean(org.springframework.integration.context.IntegrationProperties.class); - org.springframework.integration.context.IntegrationProperties integrationProperties = context - .getBean(org.springframework.integration.context.IntegrationProperties.class); - assertThat(integrationProperties.isChannelsAutoCreate()).isFalse(); - assertThat(integrationProperties.getChannelsMaxUnicastSubscribers()).isEqualTo(2); - assertThat(integrationProperties.getChannelsMaxBroadcastSubscribers()).isEqualTo(3); - assertThat(integrationProperties.isErrorChannelRequireSubscribers()).isFalse(); - assertThat(integrationProperties.isErrorChannelIgnoreFailures()).isFalse(); - assertThat(integrationProperties.isMessagingTemplateThrowExceptionOnLateReply()).isTrue(); - assertThat(integrationProperties.getReadOnlyHeaders()).containsOnly("ignoredHeader"); - assertThat(integrationProperties.getNoAutoStartupEndpoints()).containsOnly("notStartedEndpoint", - "_org.springframework.integration.errorLogger"); - }); + "spring.integration.endpoint.no-auto-startup=notStartedEndpoint,_org.springframework.integration.errorLogger" }; + assertThat(propertyValues).hasSameSizeAs(globalIntegrationPropertyNames()); + this.contextRunner.withPropertyValues(propertyValues).run((context) -> { + assertThat(context).hasSingleBean(org.springframework.integration.context.IntegrationProperties.class); + org.springframework.integration.context.IntegrationProperties integrationProperties = context + .getBean(org.springframework.integration.context.IntegrationProperties.class); + assertThat(integrationProperties.isChannelsAutoCreate()).isFalse(); + assertThat(integrationProperties.getChannelsMaxUnicastSubscribers()).isEqualTo(2); + assertThat(integrationProperties.getChannelsMaxBroadcastSubscribers()).isEqualTo(3); + assertThat(integrationProperties.isErrorChannelRequireSubscribers()).isFalse(); + assertThat(integrationProperties.isErrorChannelIgnoreFailures()).isFalse(); + assertThat(integrationProperties.getEndpointsDefaultTimeout()).isEqualTo(60000); + assertThat(integrationProperties.isMessagingTemplateThrowExceptionOnLateReply()).isTrue(); + assertThat(integrationProperties.getReadOnlyHeaders()).containsOnly("ignoredHeader"); + assertThat(integrationProperties.getNoAutoStartupEndpoints()).containsOnly("notStartedEndpoint", + "_org.springframework.integration.errorLogger"); + }); } @Test void integrationGlobalPropertiesUseConsistentDefault() { + List properties = List + .of("isChannelsAutoCreate", "getChannelsMaxUnicastSubscribers", "getChannelsMaxBroadcastSubscribers", + "isErrorChannelRequireSubscribers", "isErrorChannelIgnoreFailures", "getEndpointsDefaultTimeout", + "isMessagingTemplateThrowExceptionOnLateReply", "getReadOnlyHeaders", "getNoAutoStartupEndpoints") + .stream() + .map(PropertyAccessor::new) + .toList(); + assertThat(properties).hasSameSizeAs(globalIntegrationPropertyNames()); org.springframework.integration.context.IntegrationProperties defaultIntegrationProperties = new org.springframework.integration.context.IntegrationProperties(); this.contextRunner.run((context) -> { assertThat(context).hasSingleBean(org.springframework.integration.context.IntegrationProperties.class); org.springframework.integration.context.IntegrationProperties integrationProperties = context .getBean(org.springframework.integration.context.IntegrationProperties.class); - assertThat(integrationProperties.isChannelsAutoCreate()) - .isEqualTo(defaultIntegrationProperties.isChannelsAutoCreate()); - assertThat(integrationProperties.getChannelsMaxUnicastSubscribers()) - .isEqualTo(defaultIntegrationProperties.getChannelsMaxBroadcastSubscribers()); - assertThat(integrationProperties.getChannelsMaxBroadcastSubscribers()) - .isEqualTo(defaultIntegrationProperties.getChannelsMaxBroadcastSubscribers()); - assertThat(integrationProperties.isErrorChannelRequireSubscribers()) - .isEqualTo(defaultIntegrationProperties.isErrorChannelIgnoreFailures()); - assertThat(integrationProperties.isErrorChannelIgnoreFailures()) - .isEqualTo(defaultIntegrationProperties.isErrorChannelIgnoreFailures()); - assertThat(integrationProperties.isMessagingTemplateThrowExceptionOnLateReply()) - .isEqualTo(defaultIntegrationProperties.isMessagingTemplateThrowExceptionOnLateReply()); - assertThat(integrationProperties.getReadOnlyHeaders()) - .isEqualTo(defaultIntegrationProperties.getReadOnlyHeaders()); - assertThat(integrationProperties.getNoAutoStartupEndpoints()) - .isEqualTo(defaultIntegrationProperties.getNoAutoStartupEndpoints()); + properties.forEach((property) -> assertThat(property.get(integrationProperties)) + .isEqualTo(property.get(defaultIntegrationProperties))); }); } + private List globalIntegrationPropertyNames() { + return Stream + .of(PropertyAccessorFactory + .forBeanPropertyAccess(new org.springframework.integration.context.IntegrationProperties()) + .getPropertyDescriptors()) + .map(PropertyDescriptor::getName) + .filter((name) -> !"class".equals(name)) + .filter((name) -> !"taskSchedulerPoolSize".equals(name)) + .toList(); + } + @Test void integrationGlobalPropertiesUserBeanOverridesAutoConfiguration() { org.springframework.integration.context.IntegrationProperties userIntegrationProperties = new org.springframework.integration.context.IntegrationProperties(); @@ -604,4 +617,23 @@ MessageHandler handler(BlockingQueue> sink) { } + static class PropertyAccessor { + + private final String name; + + PropertyAccessor(String name) { + this.name = name; + } + + Object get(org.springframework.integration.context.IntegrationProperties properties) { + return ReflectionTestUtils.invokeMethod(properties, this.name); + } + + @Override + public String toString() { + return this.name; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationPropertiesEnvironmentPostProcessorTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationPropertiesEnvironmentPostProcessorTests.java index a7bf3aa28749..1b4bbab88445 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationPropertiesEnvironmentPostProcessorTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationPropertiesEnvironmentPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,23 @@ package org.springframework.boot.autoconfigure.integration; import java.io.FileNotFoundException; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.function.Consumer; +import io.lettuce.core.dynamic.support.ReflectionUtils; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.springframework.boot.SpringApplication; +import org.springframework.boot.context.properties.bind.BindResult; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.OriginLookup; import org.springframework.boot.origin.TextResourceOrigin; @@ -32,6 +43,10 @@ import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; +import org.springframework.integration.context.IntegrationProperties; +import org.springframework.mock.env.MockEnvironment; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.util.ClassUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; @@ -114,6 +129,56 @@ void registerIntegrationPropertiesPropertySourceWithResourceCanRetrieveOrigin() .satisfies(textOrigin(resource, 2, 52)); } + @Test + @SuppressWarnings("unchecked") + void hasMappingsForAllMappableProperties() throws Exception { + Class propertySource = ClassUtils.forName("%s.IntegrationPropertiesPropertySource" + .formatted(IntegrationPropertiesEnvironmentPostProcessor.class.getName()), getClass().getClassLoader()); + Map mappings = (Map) ReflectionTestUtils.getField(propertySource, + "KEYS_MAPPING"); + assertThat(mappings.values()).containsExactlyInAnyOrderElementsOf(integrationPropertyNames()); + } + + private static List integrationPropertyNames() { + List propertiesToMap = new ArrayList<>(); + ReflectionUtils.doWithFields(IntegrationProperties.class, (field) -> { + String value = (String) ReflectionUtils.getField(field, null); + if (value.startsWith(IntegrationProperties.INTEGRATION_PROPERTIES_PREFIX) + && value.length() > IntegrationProperties.INTEGRATION_PROPERTIES_PREFIX.length()) { + propertiesToMap.add(value); + } + }, (field) -> Modifier.isStatic(field.getModifiers()) && field.getType().equals(String.class)); + propertiesToMap.remove(IntegrationProperties.TASK_SCHEDULER_POOL_SIZE); + return propertiesToMap; + } + + @MethodSource("mappedConfigurationProperties") + @ParameterizedTest + void mappedPropertiesExistOnBootsIntegrationProperties(String mapping) { + Bindable bindable = Bindable + .of(org.springframework.boot.autoconfigure.integration.IntegrationProperties.class); + MockEnvironment environment = new MockEnvironment().withProperty(mapping, + (mapping.contains("max") || mapping.contains("timeout")) ? "1" : "true"); + BindResult result = Binder + .get(environment) + .bind("spring.integration", bindable); + assertThat(result.isBound()).isTrue(); + } + + @SuppressWarnings("unchecked") + private static Collection mappedConfigurationProperties() { + try { + Class propertySource = ClassUtils.forName("%s.IntegrationPropertiesPropertySource" + .formatted(IntegrationPropertiesEnvironmentPostProcessor.class.getName()), null); + Map mappings = (Map) ReflectionTestUtils.getField(propertySource, + "KEYS_MAPPING"); + return mappings.keySet(); + } + catch (Exception ex) { + throw new RuntimeException(ex); + } + } + private Consumer textOrigin(Resource resource, int line, int column) { return (origin) -> { assertThat(origin).isInstanceOf(TextResourceOrigin.class); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java index cdb19e69fe06..3e8dcf01c8f3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java @@ -45,6 +45,8 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.cfg.ConstructorDetector; import com.fasterxml.jackson.databind.cfg.ConstructorDetector.SingleArgConstructor; +import com.fasterxml.jackson.databind.cfg.EnumFeature; +import com.fasterxml.jackson.databind.cfg.JsonNodeFeature; import com.fasterxml.jackson.databind.exc.InvalidFormatException; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.util.StdDateFormat; @@ -88,6 +90,7 @@ * @author Johannes Edmeier * @author Grzegorz Poznachowski * @author Ralf Ueberfuhr + * @author Eddú Meléndez */ class JacksonAutoConfigurationTests { @@ -289,6 +292,27 @@ void defaultObjectMapperBuilder() { }); } + @Test + void enableEnumFeature() { + this.contextRunner.withPropertyValues("spring.jackson.datatype.enum.write-enums-to-lowercase=true") + .run((context) -> { + ObjectMapper mapper = context.getBean(ObjectMapper.class); + assertThat(EnumFeature.WRITE_ENUMS_TO_LOWERCASE.enabledByDefault()).isFalse(); + assertThat(mapper.getSerializationConfig().isEnabled(EnumFeature.WRITE_ENUMS_TO_LOWERCASE)).isTrue(); + }); + } + + @Test + void disableJsonNodeFeature() { + this.contextRunner.withPropertyValues("spring.jackson.datatype.json-node.write-null-properties:false") + .run((context) -> { + ObjectMapper mapper = context.getBean(ObjectMapper.class); + assertThat(JsonNodeFeature.WRITE_NULL_PROPERTIES.enabledByDefault()).isTrue(); + assertThat(mapper.getDeserializationConfig().isEnabled(JsonNodeFeature.WRITE_NULL_PROPERTIES)) + .isFalse(); + }); + } + @Test void moduleBeansAndWellKnownModulesAreRegisteredWithTheObjectMapperBuilder() { this.contextRunner.withUserConfiguration(ModuleConfig.class).run((context) -> { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java index 75cf07536762..2c1c29897228 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -152,10 +152,10 @@ void oracleUcpIsFallback() { } @Test - void oracleUcpValidatesConnectionByDefault() { + void oracleUcpDoesNotValidateConnectionByDefault() { assertDataSource(PoolDataSourceImpl.class, Arrays.asList("com.zaxxer.hikari", "org.apache.tomcat", "org.apache.commons.dbcp2"), (dataSource) -> { - assertThat(dataSource.getValidateConnectionOnBorrow()).isTrue(); + assertThat(dataSource.getValidateConnectionOnBorrow()).isFalse(); // Use an internal ping when using an Oracle JDBC driver assertThat(dataSource.getSQLForValidateConnection()).isNull(); }); @@ -267,8 +267,8 @@ void dbcp2UsesCustomConnectionDetailsWhenDefined() { DataSource dataSource = context.getBean(DataSource.class); assertThat(dataSource).asInstanceOf(InstanceOfAssertFactories.type(BasicDataSource.class)) .satisfies((dbcp2) -> { - assertThat(dbcp2.getUsername()).isEqualTo("user-1"); - assertThat(dbcp2.getPassword()).isEqualTo("password-1"); + assertThat(dbcp2.getUserName()).isEqualTo("user-1"); + assertThat(dbcp2).extracting("password").isEqualTo("password-1"); assertThat(dbcp2.getDriverClassName()).isEqualTo(DatabaseDriver.POSTGRESQL.getDriverClassName()); assertThat(dbcp2.getUrl()).isEqualTo("jdbc:customdb://customdb.example.com:12345/database-1"); }); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfigurationTests.java index 90044440432a..82228d4cc98f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,8 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; +import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizationAutoConfiguration; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.jdbc.support.JdbcTransactionManager; @@ -39,11 +41,13 @@ * @author Stephane Nicoll * @author Kazuki Shimizu * @author Davin Byeon + * @author Moritz Halbritter */ class DataSourceTransactionManagerAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(TransactionAutoConfiguration.class, + TransactionManagerCustomizationAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class)) .withPropertyValues("spring.datasource.url:jdbc:hsqldb:mem:test-" + UUID.randomUUID()); @@ -122,4 +126,10 @@ void transactionWithMultipleDataSourcesAndPrimaryCandidateIsConfigured() { }); } + @Test + void shouldNotUseDataSourcePropertiesIfDataSourceIsNotOnTheClasspath() { + this.contextRunner.withClassLoader(new FilteredClassLoader(DataSource.class)) + .run((context) -> assertThat(context).doesNotHaveBean(DataSourceProperties.class)); + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/Dbcp2JdbcConnectionDetailsBeanPostProcessorTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/Dbcp2JdbcConnectionDetailsBeanPostProcessorTests.java index fd3aef26ad38..666ea530fbca 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/Dbcp2JdbcConnectionDetailsBeanPostProcessorTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/Dbcp2JdbcConnectionDetailsBeanPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,8 +42,8 @@ void setUsernamePasswordUrlAndDriverClassName() { new Dbcp2JdbcConnectionDetailsBeanPostProcessor(null).processDataSource(dataSource, new TestJdbcConnectionDetails()); assertThat(dataSource.getUrl()).isEqualTo("jdbc:customdb://customdb.example.com:12345/database-1"); - assertThat(dataSource.getUsername()).isEqualTo("user-1"); - assertThat(dataSource.getPassword()).isEqualTo("password-1"); + assertThat(dataSource.getUserName()).isEqualTo("user-1"); + assertThat(dataSource).extracting("password").isEqualTo("password-1"); assertThat(dataSource.getDriverClassName()).isEqualTo(DatabaseDriver.POSTGRESQL.getDriverClassName()); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfigurationTests.java index 653d59cb8f48..3019be543757 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfigurationTests.java @@ -22,10 +22,16 @@ import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.boot.jdbc.HikariCheckpointRestoreLifecycle; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.testsupport.classpath.ClassPathOverrides; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.DelegatingDataSource; import static org.assertj.core.api.Assertions.assertThat; @@ -37,6 +43,7 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Olga Maciaszek-Sharma */ class HikariDataSourceConfigurationTests { @@ -122,6 +129,33 @@ void usesCustomConnectionDetailsWhenDefined() { }); } + @Test + @ClassPathOverrides("org.crac:crac:1.3.0") + void whenCheckpointRestoreIsAvailableHikariAutoConfigRegistersLifecycleBean() { + this.contextRunner.withPropertyValues("spring.datasource.type=" + HikariDataSource.class.getName()) + .run((context) -> assertThat(context).hasSingleBean(HikariCheckpointRestoreLifecycle.class)); + } + + @Test + @ClassPathOverrides("org.crac:crac:1.3.0") + void whenCheckpointRestoreIsAvailableAndDataSourceHasBeenWrappedHikariAutoConfigRegistersLifecycleBean() { + this.contextRunner.withUserConfiguration(DataSourceWrapperConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(HikariCheckpointRestoreLifecycle.class)); + } + + @Test + void whenCheckpointRestoreIsNotAvailableHikariAutoConfigDoesNotRegisterLifecycleBean() { + this.contextRunner + .run((context) -> assertThat(context).doesNotHaveBean(HikariCheckpointRestoreLifecycle.class)); + } + + @Test + @ClassPathOverrides("org.crac:crac:1.3.0") + void whenCheckpointRestoreIsAvailableAndDataSourceIsFromUserConfigurationHikariAutoConfigRegistersLifecycleBean() { + this.contextRunner.withUserConfiguration(UserDataSourceConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(HikariCheckpointRestoreLifecycle.class)); + } + @Configuration(proxyBeanMethods = false) static class ConnectionDetailsConfiguration { @@ -132,4 +166,39 @@ JdbcConnectionDetails sqlConnectionDetails() { } + @Configuration(proxyBeanMethods = false) + static class DataSourceWrapperConfiguration { + + @Bean + static BeanPostProcessor dataSourceWrapper() { + return new BeanPostProcessor() { + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof DataSource dataSource) { + return new DelegatingDataSource(dataSource); + } + return bean; + } + + }; + } + + } + + @Configuration(proxyBeanMethods = false) + static class UserDataSourceConfiguration { + + @Bean + DataSource dataSource() { + return DataSourceBuilder.create() + .driverClassName("org.postgresql.Driver") + .url("jdbc:postgresql://localhost:5432/database") + .username("user") + .password("password") + .build(); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/JdbcClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/JdbcClientAutoConfigurationTests.java new file mode 100644 index 000000000000..6c4035dc79e8 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/JdbcClientAutoConfigurationTests.java @@ -0,0 +1,102 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jdbc; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.simple.JdbcClient; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link JdbcClientAutoConfiguration}. + * + * @author Stephane Nicoll + */ +class JdbcClientAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.datasource.generate-unique-name=true") + .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, JdbcTemplateAutoConfiguration.class, + JdbcClientAutoConfiguration.class)); + + @Test + void jdbcClientWhenNoAvailableJdbcTemplateIsNotCreated() { + new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of(DataSourceAutoConfiguration.class, JdbcClientAutoConfiguration.class)) + .run((context) -> assertThat(context).doesNotHaveBean(JdbcClient.class)); + } + + @Test + void jdbcClientWhenExistingJdbcTemplateIsCreated() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(JdbcClient.class); + NamedParameterJdbcTemplate namedParameterJdbcTemplate = context.getBean(NamedParameterJdbcTemplate.class); + assertThat(namedParameterJdbcTemplate.getJdbcOperations()).isEqualTo(context.getBean(JdbcOperations.class)); + }); + } + + @Test + void jdbcClientWithCustomJdbcClientIsNotCreated() { + this.contextRunner.withBean("customJdbcClient", JdbcClient.class, () -> mock(JdbcClient.class)) + .run((context) -> { + assertThat(context).hasSingleBean(JdbcClient.class); + assertThat(context.getBean(JdbcClient.class)).isEqualTo(context.getBean("customJdbcClient")); + }); + } + + @Test + void jdbcClientIsOrderedAfterFlywayMigration() { + this.contextRunner.withUserConfiguration(JdbcClientDataSourceMigrationValidator.class) + .withPropertyValues("spring.flyway.locations:classpath:db/city") + .withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class)) + .run((context) -> { + assertThat(context).hasNotFailed().hasSingleBean(JdbcClient.class); + assertThat(context.getBean(JdbcClientDataSourceMigrationValidator.class).count).isZero(); + }); + } + + @Test + void jdbcClientIsOrderedAfterLiquibaseMigration() { + this.contextRunner.withUserConfiguration(JdbcClientDataSourceMigrationValidator.class) + .withPropertyValues("spring.liquibase.change-log:classpath:db/changelog/db.changelog-city.yaml") + .withConfiguration(AutoConfigurations.of(LiquibaseAutoConfiguration.class)) + .run((context) -> { + assertThat(context).hasNotFailed().hasSingleBean(JdbcClient.class); + assertThat(context.getBean(JdbcClientDataSourceMigrationValidator.class).count).isZero(); + }); + } + + static class JdbcClientDataSourceMigrationValidator { + + private final Long count; + + JdbcClientDataSourceMigrationValidator(JdbcClient jdbcClient) { + this.count = jdbcClient.sql("SELECT COUNT(*) from CITY").query(Long.class).single(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/JdbcTemplateAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/JdbcTemplateAutoConfigurationTests.java index e9424bd2bd36..7154f618dd49 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/JdbcTemplateAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/JdbcTemplateAutoConfigurationTests.java @@ -184,7 +184,7 @@ void testDependencyToFlywayWithJdbcTemplateMixed() { @Test void testDependencyToLiquibase() { this.contextRunner.withUserConfiguration(DataSourceMigrationValidator.class) - .withPropertyValues("spring.liquibase.changeLog:classpath:db/changelog/db.changelog-city.yaml") + .withPropertyValues("spring.liquibase.change-log:classpath:db/changelog/db.changelog-city.yaml") .withConfiguration(AutoConfigurations.of(LiquibaseAutoConfiguration.class)) .run((context) -> { assertThat(context).hasNotFailed(); @@ -195,7 +195,7 @@ void testDependencyToLiquibase() { @Test void testDependencyToLiquibaseWithJdbcTemplateMixed() { this.contextRunner.withUserConfiguration(NamedParameterDataSourceMigrationValidator.class) - .withPropertyValues("spring.liquibase.changeLog:classpath:db/changelog/db.changelog-city.yaml") + .withPropertyValues("spring.liquibase.change-log:classpath:db/changelog/db.changelog-city.yaml") .withConfiguration(AutoConfigurations.of(LiquibaseAutoConfiguration.class)) .run((context) -> { assertThat(context).hasNotFailed(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/OracleUcpDataSourceConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/OracleUcpDataSourceConfigurationTests.java index 234374540943..8448c9b2f83e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/OracleUcpDataSourceConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/OracleUcpDataSourceConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.jdbc; import java.sql.Connection; +import java.time.Duration; import javax.sql.DataSource; @@ -82,10 +83,10 @@ void testDataSourceDefaultsPreserved() { this.contextRunner.run((context) -> { PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class); assertThat(ds.getInitialPoolSize()).isZero(); - assertThat(ds.getMinPoolSize()).isZero(); + assertThat(ds.getMinPoolSize()).isOne(); assertThat(ds.getMaxPoolSize()).isEqualTo(Integer.MAX_VALUE); assertThat(ds.getInactiveConnectionTimeout()).isZero(); - assertThat(ds.getConnectionWaitTimeout()).isEqualTo(3); + assertThat(ds.getConnectionWaitDuration()).isEqualTo(Duration.ofSeconds(3)); assertThat(ds.getTimeToLiveConnectionTimeout()).isZero(); assertThat(ds.getAbandonedConnectionTimeout()).isZero(); assertThat(ds.getTimeoutCheckInterval()).isEqualTo(30); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationCustomObjectMapperProviderTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationCustomObjectMapperProviderTests.java index 7cddc14bfe79..fe823f095482 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationCustomObjectMapperProviderTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationCustomObjectMapperProviderTests.java @@ -60,8 +60,8 @@ class JerseyAutoConfigurationCustomObjectMapperProviderTests { @Test void contextLoads() { ResponseEntity response = this.restTemplate.getForEntity("/rest/message", String.class); - assertThat(HttpStatus.OK).isEqualTo(response.getStatusCode()); - assertThat(response.getBody()).isEqualTo("{\"subject\":\"Jersey\"}"); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat("{\"subject\":\"Jersey\"}").isEqualTo(response.getBody()); } @MinimalWebConfiguration diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationObjectMapperProviderTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationObjectMapperProviderTests.java index e1336263463c..59cc5167b36e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationObjectMapperProviderTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationObjectMapperProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,7 +62,7 @@ class JerseyAutoConfigurationObjectMapperProviderTests { @Test void responseIsSerializedUsingAutoConfiguredObjectMapper() { ResponseEntity response = this.restTemplate.getForEntity("/rest/message", String.class); - assertThat(HttpStatus.OK).isEqualTo(response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).isEqualTo("{\"subject\":\"Jersey\"}"); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/AcknowledgeModeTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/AcknowledgeModeTests.java new file mode 100644 index 000000000000..77957f5a9674 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/AcknowledgeModeTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jms; + +import jakarta.jms.Session; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link AcknowledgeMode}. + * + * @author Andy Wilkinson + */ +class AcknowledgeModeTests { + + @ParameterizedTest + @EnumSource(Mapping.class) + void stringIsMappedToInt(Mapping mapping) { + assertThat(AcknowledgeMode.of(mapping.actual)).extracting(AcknowledgeMode::getMode).isEqualTo(mapping.expected); + } + + @Test + void mapShouldThrowWhenMapIsCalledWithUnknownNonIntegerString() { + assertThatIllegalArgumentException().isThrownBy(() -> AcknowledgeMode.of("some-string")) + .withMessage( + "'some-string' is neither a known acknowledge mode (auto, client, or dups_ok) nor an integer value"); + } + + private enum Mapping { + + AUTO_LOWER_CASE("auto", Session.AUTO_ACKNOWLEDGE), + + CLIENT_LOWER_CASE("client", Session.CLIENT_ACKNOWLEDGE), + + DUPS_OK_LOWER_CASE("dups_ok", Session.DUPS_OK_ACKNOWLEDGE), + + AUTO_UPPER_CASE("AUTO", Session.AUTO_ACKNOWLEDGE), + + CLIENT_UPPER_CASE("CLIENT", Session.CLIENT_ACKNOWLEDGE), + + DUPS_OK_UPPER_CASE("DUPS_OK", Session.DUPS_OK_ACKNOWLEDGE), + + AUTO_MIXED_CASE("AuTo", Session.AUTO_ACKNOWLEDGE), + + CLIENT_MIXED_CASE("CliEnT", Session.CLIENT_ACKNOWLEDGE), + + DUPS_OK_MIXED_CASE("dUPs_Ok", Session.DUPS_OK_ACKNOWLEDGE), + + DUPS_OK_KEBAB_CASE("DUPS-OK", Session.DUPS_OK_ACKNOWLEDGE), + + DUPS_OK_NO_SEPARATOR_UPPER_CASE("DUPSOK", Session.DUPS_OK_ACKNOWLEDGE), + + DUPS_OK_NO_SEPARATOR_LOWER_CASE("dupsok", Session.DUPS_OK_ACKNOWLEDGE), + + DUPS_OK_NO_SEPARATOR_MIXED_CASE("duPSok", Session.DUPS_OK_ACKNOWLEDGE), + + INTEGER("36", 36); + + private final String actual; + + private final int expected; + + Mapping(String actual, int expected) { + this.actual = actual; + this.expected = expected; + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java index 17692a1b7c62..906b9f390ffd 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,20 +18,25 @@ import java.io.IOException; +import io.micrometer.observation.ObservationRegistry; import jakarta.jms.ConnectionFactory; import jakarta.jms.ExceptionListener; import jakarta.jms.Session; import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.aot.test.generate.TestGenerationContext; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import org.springframework.context.aot.ApplicationContextAotGenerator; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.jms.annotation.EnableJms; import org.springframework.jms.config.DefaultJmsListenerContainerFactory; @@ -39,6 +44,7 @@ import org.springframework.jms.config.JmsListenerContainerFactory; import org.springframework.jms.config.JmsListenerEndpoint; import org.springframework.jms.config.SimpleJmsListenerContainerFactory; +import org.springframework.jms.config.SimpleJmsListenerEndpoint; import org.springframework.jms.connection.CachingConnectionFactory; import org.springframework.jms.core.JmsMessagingTemplate; import org.springframework.jms.core.JmsTemplate; @@ -57,6 +63,8 @@ * @author Stephane Nicoll * @author Aurélien Leboulanger * @author Eddú Meléndez + * @author Vedran Pavic + * @author Lasse Wulff */ class JmsAutoConfigurationTests { @@ -65,20 +73,18 @@ class JmsAutoConfigurationTests { @Test void testDefaultJmsConfiguration() { - this.contextRunner.withUserConfiguration(TestConfiguration.class).run(this::testDefaultJmsConfiguration); - } - - private void testDefaultJmsConfiguration(AssertableApplicationContext loaded) { - assertThat(loaded).hasSingleBean(ConnectionFactory.class); - assertThat(loaded).hasSingleBean(CachingConnectionFactory.class); - CachingConnectionFactory factory = loaded.getBean(CachingConnectionFactory.class); - assertThat(factory.getTargetConnectionFactory()).isInstanceOf(ActiveMQConnectionFactory.class); - JmsTemplate jmsTemplate = loaded.getBean(JmsTemplate.class); - JmsMessagingTemplate messagingTemplate = loaded.getBean(JmsMessagingTemplate.class); - assertThat(factory).isEqualTo(jmsTemplate.getConnectionFactory()); - assertThat(messagingTemplate.getJmsTemplate()).isEqualTo(jmsTemplate); - assertThat(getBrokerUrl(factory)).startsWith("vm://"); - assertThat(loaded.containsBean("jmsListenerContainerFactory")).isTrue(); + this.contextRunner.withUserConfiguration(TestConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(ConnectionFactory.class); + assertThat(context).hasSingleBean(CachingConnectionFactory.class); + CachingConnectionFactory factory = context.getBean(CachingConnectionFactory.class); + assertThat(factory.getTargetConnectionFactory()).isInstanceOf(ActiveMQConnectionFactory.class); + JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class); + JmsMessagingTemplate messagingTemplate = context.getBean(JmsMessagingTemplate.class); + assertThat(factory).isEqualTo(jmsTemplate.getConnectionFactory()); + assertThat(messagingTemplate.getJmsTemplate()).isEqualTo(jmsTemplate); + assertThat(getBrokerUrl(factory)).startsWith("vm://"); + assertThat(context.containsBean("jmsListenerContainerFactory")).isTrue(); + }); } @Test @@ -117,6 +123,30 @@ private void testJmsTemplateBackOffEverything(AssertableApplicationContext loade assertThat(messagingTemplate.getJmsTemplate()).isEqualTo(jmsTemplate); } + @Test + void testDefaultJmsListenerConfiguration() { + this.contextRunner.withUserConfiguration(TestConfiguration.class).run((loaded) -> { + assertThat(loaded).hasSingleBean(CachingConnectionFactory.class); + CachingConnectionFactory connectionFactory = loaded.getBean(CachingConnectionFactory.class); + assertThat(loaded).hasSingleBean(DefaultJmsListenerContainerFactory.class); + DefaultJmsListenerContainerFactory containerFactory = loaded + .getBean(DefaultJmsListenerContainerFactory.class); + SimpleJmsListenerEndpoint jmsListenerEndpoint = new SimpleJmsListenerEndpoint(); + jmsListenerEndpoint.setMessageListener((message) -> { + }); + DefaultMessageListenerContainer container = containerFactory.createListenerContainer(jmsListenerEndpoint); + assertThat(container.getClientId()).isNull(); + assertThat(container.getConcurrentConsumers()).isEqualTo(1); + assertThat(container.getConnectionFactory()).isSameAs(connectionFactory.getTargetConnectionFactory()); + assertThat(container.getMaxConcurrentConsumers()).isEqualTo(1); + assertThat(container.getSessionAcknowledgeMode()).isEqualTo(Session.AUTO_ACKNOWLEDGE); + assertThat(container.isAutoStartup()).isTrue(); + assertThat(container.isPubSubDomain()).isFalse(); + assertThat(container.isSubscriptionDurable()).isFalse(); + assertThat(container).hasFieldOrPropertyWithValue("receiveTimeout", 1000L); + }); + } + @Test void testEnableJmsCreateDefaultContainerFactory() { this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class) @@ -142,9 +172,11 @@ void jmsListenerContainerFactoryWhenMultipleConnectionFactoryBeansShouldBackOff( @Test void testJmsListenerContainerFactoryWithCustomSettings() { this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class) - .withPropertyValues("spring.jms.listener.autoStartup=false", "spring.jms.listener.acknowledgeMode=client", - "spring.jms.listener.concurrency=2", "spring.jms.listener.receiveTimeout=2s", - "spring.jms.listener.maxConcurrency=10") + .withPropertyValues("spring.jms.listener.autoStartup=false", + "spring.jms.listener.session.acknowledgeMode=client", + "spring.jms.listener.session.transacted=false", "spring.jms.listener.minConcurrency=2", + "spring.jms.listener.receiveTimeout=2s", "spring.jms.listener.maxConcurrency=10", + "spring.jms.subscription-durable=true", "spring.jms.client-id=exampleId") .run(this::testJmsListenerContainerFactoryWithCustomSettings); } @@ -152,9 +184,22 @@ private void testJmsListenerContainerFactoryWithCustomSettings(AssertableApplica DefaultMessageListenerContainer container = getContainer(loaded, "jmsListenerContainerFactory"); assertThat(container.isAutoStartup()).isFalse(); assertThat(container.getSessionAcknowledgeMode()).isEqualTo(Session.CLIENT_ACKNOWLEDGE); + assertThat(container.isSessionTransacted()).isFalse(); assertThat(container.getConcurrentConsumers()).isEqualTo(2); assertThat(container.getMaxConcurrentConsumers()).isEqualTo(10); assertThat(container).hasFieldOrPropertyWithValue("receiveTimeout", 2000L); + assertThat(container.isSubscriptionDurable()).isTrue(); + assertThat(container.getClientId()).isEqualTo("exampleId"); + } + + @Test + void testJmsListenerContainerFactoryWithNonStandardAcknowledgeMode() { + this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class) + .withPropertyValues("spring.jms.listener.session.acknowledge-mode=9") + .run((context) -> { + DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory"); + assertThat(container.getSessionAcknowledgeMode()).isEqualTo(9); + }); } @Test @@ -179,6 +224,18 @@ void testDefaultContainerFactoryWithJtaTransactionManager() { }); } + @Test + void testDefaultContainerFactoryWithJtaTransactionManagerAndSessionTransactedEnabled() { + this.contextRunner.withUserConfiguration(TestConfiguration7.class, EnableJmsConfiguration.class) + .withPropertyValues("spring.jms.listener.session.transacted=true") + .run((context) -> { + DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory"); + assertThat(container.isSessionTransacted()).isTrue(); + assertThat(container).hasFieldOrPropertyWithValue("transactionManager", + context.getBean(JtaTransactionManager.class)); + }); + } + @Test void testDefaultContainerFactoryNonJtaTransactionManager() { this.contextRunner.withUserConfiguration(TestConfiguration8.class, EnableJmsConfiguration.class) @@ -198,6 +255,17 @@ void testDefaultContainerFactoryNoTransactionManager() { }); } + @Test + void testDefaultContainerFactoryNoTransactionManagerAndSessionTransactedDisabled() { + this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class) + .withPropertyValues("spring.jms.listener.session.transacted=false") + .run((context) -> { + DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory"); + assertThat(container.isSessionTransacted()).isFalse(); + assertThat(container).hasFieldOrPropertyWithValue("transactionManager", null); + }); + } + @Test void testDefaultContainerFactoryWithMessageConverters() { this.contextRunner.withUserConfiguration(MessageConvertersConfiguration.class, EnableJmsConfiguration.class) @@ -218,6 +286,17 @@ void testDefaultContainerFactoryWithExceptionListener() { }); } + @Test + void testDefaultContainerFactoryWithObservationRegistry() { + ObservationRegistry observationRegistry = mock(ObservationRegistry.class); + this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class) + .withBean(ObservationRegistry.class, () -> observationRegistry) + .run((context) -> { + DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory"); + assertThat(container.getObservationRegistry()).isSameAs(observationRegistry); + }); + } + @Test void testCustomContainerFactoryWithConfigurer() { this.contextRunner.withUserConfiguration(TestConfiguration9.class, EnableJmsConfiguration.class) @@ -250,10 +329,22 @@ void testJmsTemplateWithDestinationResolver() { .isSameAs(context.getBean("myDestinationResolver"))); } + @Test + void testJmsTemplateWithObservationRegistry() { + ObservationRegistry observationRegistry = mock(ObservationRegistry.class); + this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class) + .withBean(ObservationRegistry.class, () -> observationRegistry) + .run((context) -> { + JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class); + assertThat(jmsTemplate).extracting("observationRegistry").isSameAs(observationRegistry); + }); + } + @Test void testJmsTemplateFullCustomization() { this.contextRunner.withUserConfiguration(MessageConvertersConfiguration.class) - .withPropertyValues("spring.jms.template.default-destination=testQueue", + .withPropertyValues("spring.jms.template.session.acknowledge-mode=client", + "spring.jms.template.session.transacted=true", "spring.jms.template.default-destination=testQueue", "spring.jms.template.delivery-delay=500", "spring.jms.template.delivery-mode=non-persistent", "spring.jms.template.priority=6", "spring.jms.template.time-to-live=6000", "spring.jms.template.receive-timeout=2000") @@ -261,6 +352,8 @@ void testJmsTemplateFullCustomization() { JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class); assertThat(jmsTemplate.getMessageConverter()).isSameAs(context.getBean("myMessageConverter")); assertThat(jmsTemplate.isPubSubDomain()).isFalse(); + assertThat(jmsTemplate.getSessionAcknowledgeMode()).isEqualTo(Session.CLIENT_ACKNOWLEDGE); + assertThat(jmsTemplate.isSessionTransacted()).isTrue(); assertThat(jmsTemplate.getDefaultDestinationName()).isEqualTo("testQueue"); assertThat(jmsTemplate.getDeliveryDelay()).isEqualTo(500); assertThat(jmsTemplate.getDeliveryMode()).isOne(); @@ -271,6 +364,16 @@ void testJmsTemplateFullCustomization() { }); } + @Test + void testJmsTemplateWithNonStandardAcknowledgeMode() { + this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class) + .withPropertyValues("spring.jms.template.session.acknowledge-mode=7") + .run((context) -> { + JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class); + assertThat(jmsTemplate.getSessionAcknowledgeMode()).isEqualTo(7); + }); + } + @Test void testJmsMessagingTemplateUseConfiguredDefaultDestination() { this.contextRunner.withPropertyValues("spring.jms.template.default-destination=testQueue").run((context) -> { @@ -338,6 +441,17 @@ void enableJmsAutomatically() { .hasBean(JmsListenerConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME)); } + @Test + void runtimeHintsAreRegisteredForBindingOfAcknowledgeMode() { + try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) { + context.register(ArtemisAutoConfiguration.class, JmsAutoConfiguration.class); + TestGenerationContext generationContext = new TestGenerationContext(); + new ApplicationContextAotGenerator().processAheadOfTime(context, generationContext); + assertThat(RuntimeHintsPredicates.reflection().onMethod(AcknowledgeMode.class, "of").invoke()) + .accepts(generationContext.getRuntimeHints()); + } + } + @Configuration(proxyBeanMethods = false) static class TestConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsPropertiesTests.java index 8e42211c0661..32b93708dcae 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsPropertiesTests.java @@ -40,7 +40,7 @@ void formatConcurrencyNull() { @Test void formatConcurrencyOnlyLowerBound() { JmsProperties properties = new JmsProperties(); - properties.getListener().setConcurrency(2); + properties.getListener().setMinConcurrency(2); assertThat(properties.getListener().formatConcurrency()).isEqualTo("2-2"); } @@ -54,7 +54,7 @@ void formatConcurrencyOnlyHigherBound() { @Test void formatConcurrencyBothBounds() { JmsProperties properties = new JmsProperties(); - properties.getListener().setConcurrency(2); + properties.getListener().setMinConcurrency(2); properties.getListener().setMaxConcurrency(10); assertThat(properties.getListener().formatConcurrency()).isEqualTo("2-10"); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java index 0edd4270c7ee..2815e3e2ff50 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java @@ -40,6 +40,7 @@ * @author Andy Wilkinson * @author Aurélien Leboulanger * @author Stephane Nicoll + * @author Eddú Meléndez */ class ActiveMQAutoConfigurationTests { @@ -233,6 +234,27 @@ void cachingConnectionFactoryNotOnTheClasspathAndCacheEnabledThenSimpleConnectio .doesNotHaveBean("jmsConnectionFactory")); } + @Test + void definesPropertiesBasedConnectionDetailsByDefault() { + this.contextRunner.run((context) -> assertThat(context) + .hasSingleBean(ActiveMQAutoConfiguration.PropertiesActiveMQConnectionDetails.class)); + } + + @Test + void testConnectionFactoryWithOverridesWhenUsingCustomConnectionDetails() { + this.contextRunner.withClassLoader(new FilteredClassLoader(CachingConnectionFactory.class)) + .withPropertyValues("spring.activemq.pool.enabled=false", "spring.jms.cache.enabled=false") + .withUserConfiguration(TestConnectionDetailsConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(ActiveMQConnectionDetails.class) + .doesNotHaveBean(ActiveMQAutoConfiguration.PropertiesActiveMQConnectionDetails.class); + ActiveMQConnectionFactory connectionFactory = context.getBean(ActiveMQConnectionFactory.class); + assertThat(connectionFactory.getBrokerURL()).isEqualTo("tcp://localhost:12345"); + assertThat(connectionFactory.getUserName()).isEqualTo("springuser"); + assertThat(connectionFactory.getPassword()).isEqualTo("spring"); + }); + } + @Configuration(proxyBeanMethods = false) static class EmptyConfiguration { @@ -261,4 +283,31 @@ ActiveMQConnectionFactoryCustomizer activeMQConnectionFactoryCustomizer() { } + @Configuration(proxyBeanMethods = false) + static class TestConnectionDetailsConfiguration { + + @Bean + ActiveMQConnectionDetails activemqConnectionDetails() { + return new ActiveMQConnectionDetails() { + + @Override + public String getBrokerUrl() { + return "tcp://localhost:12345"; + } + + @Override + public String getUser() { + return "springuser"; + } + + @Override + public String getPassword() { + return "spring"; + } + + }; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java index d6b66bc12390..3c4ee27987c1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +16,18 @@ package org.springframework.boot.autoconfigure.jms.activemq; -import java.util.Collections; - import org.apache.activemq.ActiveMQConnectionFactory; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link ActiveMQProperties} and {@link ActiveMQConnectionFactoryFactory}. + * Tests for {@link ActiveMQProperties} and {@link ActiveMQConnectionFactoryConfigurer}. * * @author Stephane Nicoll * @author Aurélien Leboulanger * @author Venil Noronha + * @author Eddú Meléndez */ class ActiveMQPropertiesTests { @@ -38,35 +37,32 @@ class ActiveMQPropertiesTests { @Test void getBrokerUrlIsLocalhostByDefault() { - assertThat(createFactory(this.properties).determineBrokerUrl()).isEqualTo(DEFAULT_NETWORK_BROKER_URL); + assertThat(this.properties.determineBrokerUrl()).isEqualTo(DEFAULT_NETWORK_BROKER_URL); } @Test void getBrokerUrlUseExplicitBrokerUrl() { this.properties.setBrokerUrl("tcp://activemq.example.com:71717"); - assertThat(createFactory(this.properties).determineBrokerUrl()).isEqualTo("tcp://activemq.example.com:71717"); + assertThat(this.properties.determineBrokerUrl()).isEqualTo("tcp://activemq.example.com:71717"); } @Test void setTrustAllPackages() { + ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(); this.properties.getPackages().setTrustAll(true); - assertThat(createFactory(this.properties).createConnectionFactory(ActiveMQConnectionFactory.class) - .isTrustAllPackages()).isTrue(); + new ActiveMQConnectionFactoryConfigurer(this.properties, null).configure(factory); + assertThat(factory.isTrustAllPackages()).isTrue(); } @Test void setTrustedPackages() { + ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(); this.properties.getPackages().setTrustAll(false); this.properties.getPackages().getTrusted().add("trusted.package"); - ActiveMQConnectionFactory factory = createFactory(this.properties) - .createConnectionFactory(ActiveMQConnectionFactory.class); + new ActiveMQConnectionFactoryConfigurer(this.properties, null).configure(factory); assertThat(factory.isTrustAllPackages()).isFalse(); - assertThat(factory.getTrustedPackages().size()).isEqualTo(1); + assertThat(factory.getTrustedPackages()).hasSize(1); assertThat(factory.getTrustedPackages().get(0)).isEqualTo("trusted.package"); } - private ActiveMQConnectionFactoryFactory createFactory(ActiveMQProperties properties) { - return new ActiveMQConnectionFactoryFactory(properties, Collections.emptyList()); - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfigurationTests.java index ec0c32031e73..9f23146475cf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,6 +46,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration; +import org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration.PropertiesArtemisConnectionDetails; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -371,6 +372,27 @@ void cachingConnectionFactoryNotOnTheClasspathAndCacheEnabledThenSimpleConnectio .run((context) -> assertThat(context).doesNotHaveBean(ActiveMQConnectionFactory.class)); } + @Test + void definesPropertiesBasedConnectionDetailsByDefault() { + this.contextRunner + .run((context) -> assertThat(context).hasSingleBean(PropertiesArtemisConnectionDetails.class)); + } + + @Test + void testConnectionFactoryWithOverridesWhenUsingCustomConnectionDetails() { + this.contextRunner.withClassLoader(new FilteredClassLoader(CachingConnectionFactory.class)) + .withPropertyValues("spring.artemis.pool.enabled=false", "spring.jms.cache.enabled=false") + .withUserConfiguration(TestConnectionDetailsConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(ArtemisConnectionDetails.class) + .doesNotHaveBean(PropertiesArtemisConnectionDetails.class); + ActiveMQConnectionFactory connectionFactory = context.getBean(ActiveMQConnectionFactory.class); + assertThat(connectionFactory.toURI().toString()).startsWith("tcp://localhost:12345"); + assertThat(connectionFactory.getUser()).isEqualTo("springuser"); + assertThat(connectionFactory.getPassword()).isEqualTo("spring"); + }); + } + private ConnectionFactory getConnectionFactory(AssertableApplicationContext context) { assertThat(context).hasSingleBean(ConnectionFactory.class).hasBean("jmsConnectionFactory"); ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class); @@ -427,7 +449,7 @@ void checkTopic(String name, boolean shouldExist) { void checkDestination(String name, RoutingType routingType, boolean shouldExist) { try { - BindingQueryResult result = this.server.bindingQuery(new SimpleString(name)); + BindingQueryResult result = this.server.bindingQuery(SimpleString.of(name)); assertThat(result.isExists()).isEqualTo(shouldExist); if (shouldExist) { assertThat(result.getAddressInfo().getRoutingType()).isEqualTo(routingType); @@ -496,4 +518,36 @@ ArtemisConfigurationCustomizer myArtemisCustomize() { } + @Configuration(proxyBeanMethods = false) + static class TestConnectionDetailsConfiguration { + + @Bean + ArtemisConnectionDetails activemqConnectionDetails() { + return new ArtemisConnectionDetails() { + + @Override + public ArtemisMode getMode() { + return ArtemisMode.NATIVE; + } + + @Override + public String getBrokerUrl() { + return "tcp://localhost:12345"; + } + + @Override + public String getUser() { + return "springuser"; + } + + @Override + public String getPassword() { + return "spring"; + } + + }; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedConfigurationFactoryTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedConfigurationFactoryTests.java index 06e2d552c3d5..6c247455309c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedConfigurationFactoryTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedConfigurationFactoryTests.java @@ -75,10 +75,8 @@ void hasDlqExpiryQueueAddressSettingsConfigured() { ArtemisProperties properties = new ArtemisProperties(); Configuration configuration = new ArtemisEmbeddedConfigurationFactory(properties).createConfiguration(); Map addressSettings = configuration.getAddressSettings(); - assertThat((Object) addressSettings.get("#").getDeadLetterAddress()) - .isEqualTo(SimpleString.toSimpleString("DLQ")); - assertThat((Object) addressSettings.get("#").getExpiryAddress()) - .isEqualTo(SimpleString.toSimpleString("ExpiryQueue")); + assertThat((Object) addressSettings.get("#").getDeadLetterAddress()).isEqualTo(SimpleString.of("DLQ")); + assertThat((Object) addressSettings.get("#").getExpiryAddress()).isEqualTo(SimpleString.of("ExpiryQueue")); } @Test diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/DefaultExceptionTranslatorExecuteListenerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/DefaultExceptionTranslatorExecuteListenerTests.java new file mode 100644 index 000000000000..ae1278889d1b --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/DefaultExceptionTranslatorExecuteListenerTests.java @@ -0,0 +1,112 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jooq; + +import java.sql.SQLException; +import java.util.function.Function; + +import org.jooq.Configuration; +import org.jooq.ExecuteContext; +import org.jooq.SQLDialect; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import org.springframework.jdbc.BadSqlGrammarException; +import org.springframework.jdbc.support.SQLExceptionTranslator; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.assertArg; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; + +/** + * Tests for {@link DefaultExceptionTranslatorExecuteListener}. + * + * @author Andy Wilkinson + * @author Phillip Webb + */ +class DefaultExceptionTranslatorExecuteListenerTests { + + private final ExceptionTranslatorExecuteListener listener = new DefaultExceptionTranslatorExecuteListener(); + + @Test + void createWhenTranslatorFactoryIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new DefaultExceptionTranslatorExecuteListener( + (Function) null)) + .withMessage("TranslatorFactory must not be null"); + } + + @ParameterizedTest(name = "{0}") + @MethodSource + void exceptionTranslatesSqlExceptions(SQLDialect dialect, SQLException sqlException) { + ExecuteContext context = mockContext(dialect, sqlException); + this.listener.exception(context); + then(context).should().exception(assertArg((ex) -> assertThat(ex).isInstanceOf(BadSqlGrammarException.class))); + } + + @Test + void exceptionWhenExceptionCannotBeTranslatedDoesNotCallExecuteContextException() { + ExecuteContext context = mockContext(SQLDialect.POSTGRES, new SQLException(null, null, 123456789)); + this.listener.exception(context); + then(context).should(never()).exception(any()); + } + + @Test + void exceptionWhenHasCustomTranslatorFactory() { + SQLExceptionTranslator translator = BadSqlGrammarException::new; + ExceptionTranslatorExecuteListener listener = new DefaultExceptionTranslatorExecuteListener( + (context) -> translator); + SQLException sqlException = sqlException(123); + ExecuteContext context = mockContext(SQLDialect.DUCKDB, sqlException); + listener.exception(context); + then(context).should().exception(assertArg((ex) -> assertThat(ex).isInstanceOf(BadSqlGrammarException.class))); + } + + private ExecuteContext mockContext(SQLDialect dialect, SQLException sqlException) { + ExecuteContext context = mock(ExecuteContext.class); + Configuration configuration = mock(Configuration.class); + given(context.configuration()).willReturn(configuration); + given(configuration.dialect()).willReturn(dialect); + given(context.sqlException()).willReturn(sqlException); + return context; + } + + static Object[] exceptionTranslatesSqlExceptions() { + return new Object[] { new Object[] { SQLDialect.DERBY, sqlException("42802") }, + new Object[] { SQLDialect.H2, sqlException(42000) }, + new Object[] { SQLDialect.HSQLDB, sqlException(-22) }, + new Object[] { SQLDialect.MARIADB, sqlException(1054) }, + new Object[] { SQLDialect.MYSQL, sqlException(1054) }, + new Object[] { SQLDialect.POSTGRES, sqlException("03000") }, + new Object[] { SQLDialect.SQLITE, sqlException("21000") } }; + } + + private static SQLException sqlException(String sqlState) { + return new SQLException(null, sqlState); + } + + private static SQLException sqlException(int vendorCode) { + return new SQLException(null, null, vendorCode); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfigurationTests.java index 1e78fee1d9ad..a26b63349159 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,6 +55,7 @@ * @author Andy Wilkinson * @author Stephane Nicoll * @author Dmytro Nosan + * @author Dennis Melzer */ class JooqAutoConfigurationTests { @@ -180,6 +181,26 @@ void transactionProviderBacksOffOnExistingTransactionProvider() { }); } + @Test + void jooqExceptionTranslatorProviderFromConfigurationCustomizerOverridesJooqExceptionTranslatorBean() { + this.contextRunner + .withUserConfiguration(JooqDataSourceConfiguration.class, CustomJooqExceptionTranslatorConfiguration.class) + .run((context) -> { + assertThat(context.getBean(ExceptionTranslatorExecuteListener.class)) + .isInstanceOf(CustomJooqExceptionTranslator.class); + assertThat(context.getBean(DefaultExecuteListenerProvider.class).provide()) + .isInstanceOf(CustomJooqExceptionTranslator.class); + }); + } + + @Test + void jooqWithDefaultJooqExceptionTranslator() { + this.contextRunner.withUserConfiguration(JooqDataSourceConfiguration.class).run((context) -> { + ExceptionTranslatorExecuteListener translator = context.getBean(ExceptionTranslatorExecuteListener.class); + assertThat(translator).isInstanceOf(DefaultExceptionTranslatorExecuteListener.class); + }); + } + @Test void transactionProviderFromConfigurationCustomizerOverridesTransactionProviderBean() { this.contextRunner @@ -254,6 +275,16 @@ TransactionProvider transactionProvider() { } + @Configuration(proxyBeanMethods = false) + static class CustomJooqExceptionTranslatorConfiguration { + + @Bean + ExceptionTranslatorExecuteListener jooqExceptionTranslator() { + return new CustomJooqExceptionTranslator(); + } + + } + @Configuration(proxyBeanMethods = false) static class CustomTransactionProviderFromCustomizerConfiguration { @@ -303,4 +334,8 @@ public void rollback(TransactionContext ctx) { } + static class CustomJooqExceptionTranslator implements ExceptionTranslatorExecuteListener { + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqExceptionTranslatorTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqExceptionTranslatorTests.java index 5a9f6685f144..f53b34880b38 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqExceptionTranslatorTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqExceptionTranslatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,6 +40,8 @@ * * @author Andy Wilkinson */ +@Deprecated(since = "3.3.0") +@SuppressWarnings("removal") class JooqExceptionTranslatorTests { private final JooqExceptionTranslator exceptionTranslator = new JooqExceptionTranslator(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/ConcurrentKafkaListenerContainerFactoryConfigurerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/ConcurrentKafkaListenerContainerFactoryConfigurerTests.java new file mode 100644 index 000000000000..779d9974d3f1 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/ConcurrentKafkaListenerContainerFactoryConfigurerTests.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.kafka; + +import java.util.function.Function; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.core.ConsumerFactory; +import org.springframework.kafka.listener.MessageListenerContainer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; + +/** + * Tests for {@link ConcurrentKafkaListenerContainerFactoryConfigurer}. + * + * @author Moritz Halbritter + */ +class ConcurrentKafkaListenerContainerFactoryConfigurerTests { + + private ConcurrentKafkaListenerContainerFactoryConfigurer configurer; + + private ConcurrentKafkaListenerContainerFactory factory; + + private ConsumerFactory consumerFactory; + + private KafkaProperties properties; + + @BeforeEach + @SuppressWarnings("unchecked") + void setUp() { + this.configurer = new ConcurrentKafkaListenerContainerFactoryConfigurer(); + this.properties = new KafkaProperties(); + this.configurer.setKafkaProperties(this.properties); + this.factory = spy(new ConcurrentKafkaListenerContainerFactory<>()); + this.consumerFactory = mock(ConsumerFactory.class); + + } + + @Test + void shouldApplyThreadNameSupplier() { + Function function = (container) -> "thread-1"; + this.configurer.setThreadNameSupplier(function); + this.configurer.configure(this.factory, this.consumerFactory); + then(this.factory).should().setThreadNameSupplier(function); + } + + @Test + void shouldApplyChangeConsumerThreadName() { + this.properties.getListener().setChangeConsumerThreadName(true); + this.configurer.configure(this.factory, this.consumerFactory); + then(this.factory).should().setChangeConsumerThreadName(true); + } + + @Test + void shouldApplyListenerTaskExecutor() { + SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(); + this.configurer.setListenerTaskExecutor(executor); + this.configurer.configure(this.factory, this.consumerFactory); + assertThat(this.factory.getContainerProperties().getListenerTaskExecutor()).isEqualTo(executor); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaAutoConfigurationIntegrationTests.java index 2b6d6e849a17..02ff53618300 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaAutoConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,11 +28,13 @@ import org.apache.kafka.streams.kstream.KStream; import org.apache.kafka.streams.kstream.KTable; import org.apache.kafka.streams.kstream.Materialized; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.OS; +import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; @@ -58,6 +60,7 @@ * @author Gary Russell * @author Stephane Nicoll * @author Tomaz Fernandes + * @author Andy Wilkinson */ @DisabledOnOs(OS.WINDOWS) @EmbeddedKafka(topics = KafkaAutoConfigurationIntegrationTests.TEST_TOPIC) @@ -113,7 +116,7 @@ void testEndToEndWithRetryTopics() throws Exception { assertThat(listener).extracting(RetryListener::getKey, RetryListener::getReceived) .containsExactly("foo", "bar"); assertThat(listener).extracting(RetryListener::getTopics) - .asList() + .asInstanceOf(InstanceOfAssertFactories.LIST) .hasSize(5) .containsSequence("testRetryTopic", "testRetryTopic-retry-0", "testRetryTopic-retry-1", "testRetryTopic-retry-2"); @@ -133,6 +136,7 @@ private void load(Class config, String... environment) { private AnnotationConfigApplicationContext doLoad(Class[] configs, String... environment) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.register(configs); + applicationContext.register(SslAutoConfiguration.class); applicationContext.register(KafkaAutoConfiguration.class); TestPropertyValues.of(environment).applyTo(applicationContext); applicationContext.refresh(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaAutoConfigurationTests.java index f274a3b51779..8e34ee8da56f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,15 +40,21 @@ import org.apache.kafka.streams.StreamsConfig; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ContextConsumer; +import org.springframework.boot.testsupport.assertj.SimpleAsyncTaskExecutorAssert; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.kafka.annotation.EnableKafkaStreams; import org.springframework.kafka.annotation.KafkaStreamsDefaultConfiguration; import org.springframework.kafka.config.AbstractKafkaListenerContainerFactory; @@ -102,11 +108,12 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick */ class KafkaAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(KafkaAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(KafkaAutoConfiguration.class, SslAutoConfiguration.class)); @Test void consumerProperties() { @@ -389,20 +396,6 @@ void connectionDetailsAreAppliedToStreams() { }); } - @SuppressWarnings("deprecation") - @Deprecated(since = "3.1.0", forRemoval = true) - void streamsCacheMaxSizeBuffering() { - this.contextRunner.withUserConfiguration(EnableKafkaStreamsConfiguration.class) - .withPropertyValues("spring.kafka.streams.cache-max-size-buffering=1KB") - .run((context) -> { - Properties configs = context - .getBean(KafkaStreamsDefaultConfiguration.DEFAULT_STREAMS_CONFIG_BEAN_NAME, - KafkaStreamsConfiguration.class) - .asProperties(); - assertThat(configs).containsEntry(StreamsConfig.CACHE_MAX_BYTES_BUFFERING_CONFIG, 1024); - }); - } - @SuppressWarnings("unchecked") @Test void streamsApplicationIdUsesMainApplicationNameByDefault() { @@ -449,6 +442,22 @@ void retryTopicConfigurationIsNotEnabledByDefault() { @Test void retryTopicConfigurationWithExponentialBackOff() { + this.contextRunner.withPropertyValues("spring.application.name=my-test-app", + "spring.kafka.bootstrap-servers=localhost:9092,localhost:9093", "spring.kafka.retry.topic.enabled=true", + "spring.kafka.retry.topic.attempts=5", "spring.kafka.retry.topic.backoff.delay=100ms", + "spring.kafka.retry.topic.backoff.multiplier=2", "spring.kafka.retry.topic.backoff.max-delay=300ms") + .run((context) -> { + RetryTopicConfiguration configuration = context.getBean(RetryTopicConfiguration.class); + assertThat(configuration.getDestinationTopicProperties()).hasSize(5) + .extracting(DestinationTopic.Properties::delay, DestinationTopic.Properties::suffix) + .containsExactly(tuple(0L, ""), tuple(100L, "-retry-0"), tuple(200L, "-retry-1"), + tuple(300L, "-retry-2"), tuple(0L, "-dlt")); + }); + } + + @Test + @Deprecated(since = "3.4.0", forRemoval = true) + void retryTopicConfigurationWithExponentialBackOffUsingDeprecatedProperties() { this.contextRunner.withPropertyValues("spring.application.name=my-test-app", "spring.kafka.bootstrap-servers=localhost:9092,localhost:9093", "spring.kafka.retry.topic.enabled=true", "spring.kafka.retry.topic.attempts=5", "spring.kafka.retry.topic.delay=100ms", @@ -478,6 +487,18 @@ void retryTopicConfigurationWithDefaultProperties() { @Test void retryTopicConfigurationWithFixedBackOff() { + this.contextRunner.withPropertyValues("spring.application.name=my-test-app", + "spring.kafka.bootstrap-servers=localhost:9092,localhost:9093", "spring.kafka.retry.topic.enabled=true", + "spring.kafka.retry.topic.attempts=4", "spring.kafka.retry.topic.backoff.delay=2s") + .run(assertRetryTopicConfiguration( + (configuration) -> assertThat(configuration.getDestinationTopicProperties()).hasSize(3) + .extracting(DestinationTopic.Properties::delay) + .containsExactly(0L, 2000L, 0L))); + } + + @Test + @Deprecated(since = "3.4.0", forRemoval = true) + void retryTopicConfigurationWithFixedBackOffUsingDeprecatedProperties() { this.contextRunner.withPropertyValues("spring.application.name=my-test-app", "spring.kafka.bootstrap-servers=localhost:9092,localhost:9093", "spring.kafka.retry.topic.enabled=true", "spring.kafka.retry.topic.attempts=4", "spring.kafka.retry.topic.delay=2s") @@ -489,6 +510,18 @@ void retryTopicConfigurationWithFixedBackOff() { @Test void retryTopicConfigurationWithNoBackOff() { + this.contextRunner.withPropertyValues("spring.application.name=my-test-app", + "spring.kafka.bootstrap-servers=localhost:9092,localhost:9093", "spring.kafka.retry.topic.enabled=true", + "spring.kafka.retry.topic.attempts=4", "spring.kafka.retry.topic.backoff.delay=0") + .run(assertRetryTopicConfiguration( + (configuration) -> assertThat(configuration.getDestinationTopicProperties()).hasSize(3) + .extracting(DestinationTopic.Properties::delay) + .containsExactly(0L, 0L, 0L))); + } + + @Test + @Deprecated(since = "3.4.0", forRemoval = true) + void retryTopicConfigurationWithNoBackOffUsingDeprecatedProperties() { this.contextRunner.withPropertyValues("spring.application.name=my-test-app", "spring.kafka.bootstrap-servers=localhost:9092,localhost:9093", "spring.kafka.retry.topic.enabled=true", "spring.kafka.retry.topic.attempts=4", "spring.kafka.retry.topic.delay=0") @@ -570,6 +603,31 @@ void streamsApplicationIdIsNotMandatoryIfEnableKafkaStreamsIsNotSet() { }); } + @Test + void shouldUsePlatformThreadsByDefault() { + this.contextRunner.run((context) -> { + ConcurrentKafkaListenerContainerFactory factory = context + .getBean(ConcurrentKafkaListenerContainerFactory.class); + assertThat(factory).isNotNull(); + AsyncTaskExecutor listenerTaskExecutor = factory.getContainerProperties().getListenerTaskExecutor(); + assertThat(listenerTaskExecutor).isNull(); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void shouldUseVirtualThreadsIfEnabled() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> { + ConcurrentKafkaListenerContainerFactory factory = context + .getBean(ConcurrentKafkaListenerContainerFactory.class); + assertThat(factory).isNotNull(); + AsyncTaskExecutor listenerTaskExecutor = factory.getContainerProperties().getListenerTaskExecutor(); + assertThat(listenerTaskExecutor).isInstanceOf(SimpleAsyncTaskExecutor.class); + SimpleAsyncTaskExecutorAssert.assertThat((SimpleAsyncTaskExecutor) listenerTaskExecutor) + .usesVirtualThreads(); + }); + } + @SuppressWarnings("unchecked") @Test void listenerProperties() { @@ -586,7 +644,8 @@ void listenerProperties() { "spring.kafka.listener.missing-topics-fatal=true", "spring.kafka.jaas.enabled=true", "spring.kafka.listener.immediate-stop=true", "spring.kafka.producer.transaction-id-prefix=foo", "spring.kafka.jaas.login-module=foo", "spring.kafka.jaas.control-flag=REQUISITE", - "spring.kafka.jaas.options.useKeyTab=true", "spring.kafka.listener.async-acks=true") + "spring.kafka.jaas.options.useKeyTab=true", "spring.kafka.listener.async-acks=true", + "spring.kafka.template.observation-enabled=true", "spring.kafka.listener.observation-enabled=true") .run((context) -> { DefaultKafkaProducerFactory producerFactory = context.getBean(DefaultKafkaProducerFactory.class); DefaultKafkaConsumerFactory consumerFactory = context.getBean(DefaultKafkaConsumerFactory.class); @@ -597,6 +656,7 @@ void listenerProperties() { assertThat(kafkaTemplate).hasFieldOrPropertyWithValue("producerFactory", producerFactory); assertThat(kafkaTemplate.getDefaultTopic()).isEqualTo("testTopic"); assertThat(kafkaTemplate).hasFieldOrPropertyWithValue("transactionIdPrefix", "txOverride"); + assertThat(kafkaTemplate).hasFieldOrPropertyWithValue("observationEnabled", true); assertThat(kafkaListenerContainerFactory.getConsumerFactory()).isEqualTo(consumerFactory); ContainerProperties containerProperties = kafkaListenerContainerFactory.getContainerProperties(); assertThat(containerProperties.getAckMode()).isEqualTo(AckMode.MANUAL); @@ -613,6 +673,7 @@ void listenerProperties() { assertThat(containerProperties.isLogContainerConfig()).isTrue(); assertThat(containerProperties.isMissingTopicsFatal()).isTrue(); assertThat(containerProperties.isStopImmediate()).isTrue(); + assertThat(containerProperties.isObservationEnabled()).isTrue(); assertThat(kafkaListenerContainerFactory).extracting("concurrency").isEqualTo(3); assertThat(kafkaListenerContainerFactory.isBatchListener()).isTrue(); assertThat(kafkaListenerContainerFactory).hasFieldOrPropertyWithValue("autoStartup", true); @@ -722,7 +783,7 @@ void testConcurrentKafkaListenerContainerFactoryWithDefaultTransactionManager() assertThat(context).hasSingleBean(KafkaAwareTransactionManager.class); ConcurrentKafkaListenerContainerFactory factory = context .getBean(ConcurrentKafkaListenerContainerFactory.class); - assertThat(factory.getContainerProperties().getTransactionManager()) + assertThat(factory.getContainerProperties().getKafkaAwareTransactionManager()) .isSameAs(context.getBean(KafkaAwareTransactionManager.class)); }); } @@ -737,7 +798,7 @@ void testConcurrentKafkaListenerContainerFactoryWithCustomTransactionManager() { .run((context) -> { ConcurrentKafkaListenerContainerFactory factory = context .getBean(ConcurrentKafkaListenerContainerFactory.class); - assertThat(factory.getContainerProperties().getTransactionManager()) + assertThat(factory.getContainerProperties().getKafkaAwareTransactionManager()) .isSameAs(context.getBean("customTransactionManager")); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaPropertiesTests.java index 8ee0486d857b..dbd53fd59d81 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaPropertiesTests.java @@ -27,6 +27,8 @@ import org.springframework.boot.autoconfigure.kafka.KafkaProperties.IsolationLevel; import org.springframework.boot.autoconfigure.kafka.KafkaProperties.Listener; import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException; +import org.springframework.boot.ssl.DefaultSslBundleRegistry; +import org.springframework.boot.ssl.SslBundle; import org.springframework.core.io.ClassPathResource; import org.springframework.kafka.core.CleanupConfig; import org.springframework.kafka.core.KafkaAdmin; @@ -34,16 +36,19 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.mock; /** * Tests for {@link KafkaProperties}. * * @author Stephane Nicoll * @author Madhura Bhave + * @author Scott Frederick */ class KafkaPropertiesTests { - @SuppressWarnings("rawtypes") + private final SslBundle sslBundle = mock(SslBundle.class); + @Test void isolationLevelEnumConsistentWithKafkaVersion() { org.apache.kafka.common.IsolationLevel[] original = org.apache.kafka.common.IsolationLevel.values(); @@ -75,20 +80,30 @@ void sslPemConfiguration() { properties.getSsl().setKeyStoreKey("-----BEGINkey"); properties.getSsl().setTrustStoreCertificates("-----BEGINtrust"); properties.getSsl().setKeyStoreCertificateChain("-----BEGINchain"); - Map consumerProperties = properties.buildConsumerProperties(); + Map consumerProperties = properties.buildConsumerProperties(null); assertThat(consumerProperties).containsEntry(SslConfigs.SSL_KEYSTORE_KEY_CONFIG, "-----BEGINkey"); assertThat(consumerProperties).containsEntry(SslConfigs.SSL_TRUSTSTORE_CERTIFICATES_CONFIG, "-----BEGINtrust"); assertThat(consumerProperties).containsEntry(SslConfigs.SSL_KEYSTORE_CERTIFICATE_CHAIN_CONFIG, "-----BEGINchain"); } + @Test + void sslBundleConfiguration() { + KafkaProperties properties = new KafkaProperties(); + properties.getSsl().setBundle("myBundle"); + Map consumerProperties = properties + .buildConsumerProperties(new DefaultSslBundleRegistry("myBundle", this.sslBundle)); + assertThat(consumerProperties).containsEntry(SslConfigs.SSL_ENGINE_FACTORY_CLASS_CONFIG, + SslBundleSslEngineFactory.class.getName()); + } + @Test void sslPropertiesWhenKeyStoreLocationAndKeySetShouldThrowException() { KafkaProperties properties = new KafkaProperties(); properties.getSsl().setKeyStoreKey("-----BEGIN"); properties.getSsl().setKeyStoreLocation(new ClassPathResource("ksLoc")); assertThatExceptionOfType(MutuallyExclusiveConfigurationPropertiesException.class) - .isThrownBy(properties::buildConsumerProperties); + .isThrownBy(() -> properties.buildConsumerProperties(null)); } @Test @@ -97,7 +112,43 @@ void sslPropertiesWhenTrustStoreLocationAndCertificatesSetShouldThrowException() properties.getSsl().setTrustStoreLocation(new ClassPathResource("tsLoc")); properties.getSsl().setTrustStoreCertificates("-----BEGIN"); assertThatExceptionOfType(MutuallyExclusiveConfigurationPropertiesException.class) - .isThrownBy(properties::buildConsumerProperties); + .isThrownBy(() -> properties.buildConsumerProperties(null)); + } + + @Test + void sslPropertiesWhenKeyStoreLocationAndBundleSetShouldThrowException() { + KafkaProperties properties = new KafkaProperties(); + properties.getSsl().setBundle("myBundle"); + properties.getSsl().setKeyStoreLocation(new ClassPathResource("ksLoc")); + assertThatExceptionOfType(MutuallyExclusiveConfigurationPropertiesException.class).isThrownBy( + () -> properties.buildConsumerProperties(new DefaultSslBundleRegistry("myBundle", this.sslBundle))); + } + + @Test + void sslPropertiesWhenKeyStoreKeyAndBundleSetShouldThrowException() { + KafkaProperties properties = new KafkaProperties(); + properties.getSsl().setBundle("myBundle"); + properties.getSsl().setKeyStoreKey("-----BEGIN"); + assertThatExceptionOfType(MutuallyExclusiveConfigurationPropertiesException.class).isThrownBy( + () -> properties.buildConsumerProperties(new DefaultSslBundleRegistry("myBundle", this.sslBundle))); + } + + @Test + void sslPropertiesWhenTrustStoreLocationAndBundleSetShouldThrowException() { + KafkaProperties properties = new KafkaProperties(); + properties.getSsl().setBundle("myBundle"); + properties.getSsl().setTrustStoreLocation(new ClassPathResource("tsLoc")); + assertThatExceptionOfType(MutuallyExclusiveConfigurationPropertiesException.class).isThrownBy( + () -> properties.buildConsumerProperties(new DefaultSslBundleRegistry("myBundle", this.sslBundle))); + } + + @Test + void sslPropertiesWhenTrustStoreCertificatesAndBundleSetShouldThrowException() { + KafkaProperties properties = new KafkaProperties(); + properties.getSsl().setBundle("myBundle"); + properties.getSsl().setTrustStoreCertificates("-----BEGIN"); + assertThatExceptionOfType(MutuallyExclusiveConfigurationPropertiesException.class).isThrownBy( + () -> properties.buildConsumerProperties(new DefaultSslBundleRegistry("myBundle", this.sslBundle))); } @Test diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ldap/LdapAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ldap/LdapAutoConfigurationTests.java index bde394cc67a1..2d51f84a9b7b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ldap/LdapAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ldap/LdapAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import org.springframework.ldap.core.support.SimpleDirContextAuthenticationStrategy; import org.springframework.ldap.pool2.factory.PoolConfig; import org.springframework.ldap.pool2.factory.PooledContextSource; +import org.springframework.ldap.support.LdapUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -112,6 +113,25 @@ void contextSourceWithNoCustomization() { }); } + @Test + void definesPropertiesBasedConnectionDetailsByDefault() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesLdapConnectionDetails.class)); + } + + @Test + void usesCustomConnectionDetailsWhenDefined() { + this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(LdapContextSource.class) + .hasSingleBean(LdapConnectionDetails.class) + .doesNotHaveBean(PropertiesLdapConnectionDetails.class); + LdapContextSource contextSource = context.getBean(LdapContextSource.class); + assertThat(contextSource.getUrls()).isEqualTo(new String[] { "ldaps://ldap.example.com" }); + assertThat(contextSource.getBaseLdapName()).isEqualTo(LdapUtils.newLdapName("dc=base")); + assertThat(contextSource.getUserDn()).isEqualTo("ldap-user"); + assertThat(contextSource.getPassword()).isEqualTo("ldap-password"); + }); + } + @Test void templateExists() { this.contextRunner.withPropertyValues("spring.ldap.urls:ldap://localhost:389").run((context) -> { @@ -174,6 +194,37 @@ void contextSourceWithCustomNonUniqueDirContextAuthenticationStrategy() { }); } + @Configuration(proxyBeanMethods = false) + static class ConnectionDetailsConfiguration { + + @Bean + LdapConnectionDetails ldapConnectionDetails() { + return new LdapConnectionDetails() { + + @Override + public String[] getUrls() { + return new String[] { "ldaps://ldap.example.com" }; + } + + @Override + public String getBase() { + return "dc=base"; + } + + @Override + public String getUsername() { + return "ldap-user"; + } + + @Override + public String getPassword() { + return "ldap-password"; + } + }; + } + + } + @Configuration(proxyBeanMethods = false) static class PooledContextSourceConfig { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/Liquibase423AutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/Liquibase423AutoConfigurationTests.java new file mode 100644 index 000000000000..850a1e66124d --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/Liquibase423AutoConfigurationTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.liquibase; + +import java.util.function.Consumer; + +import liquibase.integration.spring.SpringLiquibase; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ContextConsumer; +import org.springframework.boot.testsupport.classpath.ClassPathOverrides; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link LiquibaseAutoConfiguration} with Liquibase 4.23. + * + * @author Andy Wilkinson + */ +@ClassPathOverrides("org.liquibase:liquibase-core:4.23.1") +class Liquibase423AutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(LiquibaseAutoConfiguration.class)) + .withPropertyValues("spring.datasource.generate-unique-name=true"); + + @Test + void defaultSpringLiquibase() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .run(assertLiquibase((liquibase) -> { + assertThat(liquibase.getChangeLog()).isEqualTo("classpath:/db/changelog/db.changelog-master.yaml"); + assertThat(liquibase.getContexts()).isNull(); + assertThat(liquibase.getDefaultSchema()).isNull(); + assertThat(liquibase.isDropFirst()).isFalse(); + assertThat(liquibase.isClearCheckSums()).isFalse(); + })); + } + + private ContextConsumer assertLiquibase(Consumer consumer) { + return (context) -> { + assertThat(context).hasSingleBean(SpringLiquibase.class); + SpringLiquibase liquibase = context.getBean(SpringLiquibase.class); + consumer.accept(liquibase); + }; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java index b05e9b0f010d..cc546cc1e9ae 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,13 @@ import javax.sql.DataSource; import com.zaxxer.hikari.HikariDataSource; +import liquibase.Liquibase; +import liquibase.UpdateSummaryEnum; +import liquibase.UpdateSummaryOutputEnum; +import liquibase.command.core.helpers.ShowSummaryArgument; +import liquibase.integration.spring.Customizer; import liquibase.integration.spring.SpringLiquibase; +import liquibase.ui.UIServiceEnum; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; @@ -79,6 +85,7 @@ * @author Evgeniy Cheban * @author Moritz Halbritter * @author Phillip Webb + * @author Ahmed Ashour */ @ExtendWith(OutputCaptureExtension.class) class LiquibaseAutoConfigurationTests { @@ -220,6 +227,10 @@ void defaultValues() { assertThat(liquibase.isDropFirst()).isEqualTo(properties.isDropFirst()); assertThat(liquibase.isClearCheckSums()).isEqualTo(properties.isClearChecksums()); assertThat(liquibase.isTestRollbackOnUpdate()).isEqualTo(properties.isTestRollbackOnUpdate()); + assertThat(liquibase).extracting("showSummary").isNull(); + assertThat(ShowSummaryArgument.SHOW_SUMMARY.getDefaultValue()).isEqualTo(UpdateSummaryEnum.SUMMARY); + assertThat(liquibase).extracting("showSummaryOutput").isEqualTo(UpdateSummaryOutputEnum.LOG); + assertThat(liquibase).extracting("uiService").isEqualTo(UIServiceEnum.LOGGER); })); } @@ -266,8 +277,12 @@ void overrideDropFirst() { @Test void overrideClearChecksums() { + String jdbcUrl = "jdbc:hsqldb:mem:liquibase" + UUID.randomUUID(); + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.liquibase.url:" + jdbcUrl) + .run((context) -> assertThat(context).hasNotFailed()); this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.liquibase.clear-checksums:true") + .withPropertyValues("spring.liquibase.clear-checksums:true", "spring.liquibase.url:" + jdbcUrl) .run(assertLiquibase((liquibase) -> assertThat(liquibase.isClearCheckSums()).isTrue())); } @@ -380,11 +395,33 @@ void overrideLabelFilter() { } @Test - @Deprecated(since = "3.0.0", forRemoval = true) - void overrideLabelFilterWithDeprecatedLabelsProperty() { + void overrideShowSummary() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.liquibase.labels:test, production") - .run(assertLiquibase((liquibase) -> assertThat(liquibase.getLabelFilter()).isEqualTo("test, production"))); + .withPropertyValues("spring.liquibase.show-summary=off") + .run(assertLiquibase((liquibase) -> { + UpdateSummaryEnum showSummary = (UpdateSummaryEnum) ReflectionTestUtils.getField(liquibase, + "showSummary"); + assertThat(showSummary).isEqualTo(UpdateSummaryEnum.OFF); + })); + } + + @Test + void overrideShowSummaryOutput() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.liquibase.show-summary-output=all") + .run(assertLiquibase((liquibase) -> { + UpdateSummaryOutputEnum showSummaryOutput = (UpdateSummaryOutputEnum) ReflectionTestUtils + .getField(liquibase, "showSummaryOutput"); + assertThat(showSummaryOutput).isEqualTo(UpdateSummaryOutputEnum.ALL); + })); + } + + @Test + void overrideUiService() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.liquibase.ui-service=console") + .run(assertLiquibase( + (liquibase) -> assertThat(liquibase).extracting("uiService").isEqualTo(UIServiceEnum.CONSOLE))); } @Test @@ -404,7 +441,7 @@ void testOverrideParameters() { void rollbackFile(@TempDir Path temp) throws IOException { File file = Files.createTempFile(temp, "rollback-file", "sql").toFile(); this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.liquibase.rollbackFile:" + file.getAbsolutePath()) + .withPropertyValues("spring.liquibase.rollback-file:" + file.getAbsolutePath()) .run((context) -> { SpringLiquibase liquibase = context.getBean(SpringLiquibase.class); File actualFile = (File) ReflectionTestUtils.getField(liquibase, "rollbackFile"); @@ -498,6 +535,12 @@ void shouldRegisterHints() { assertThat(RuntimeHintsPredicates.resource().forResource("db/changelog/tables/init.sql")).accepts(hints); } + @Test + void whenCustomizerBeanIsDefinedThenItIsConfiguredOnSpringLiquibase() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, CustomizerConfiguration.class) + .run(assertLiquibase((liquibase) -> assertThat(liquibase.getCustomizer()).isNotNull())); + } + private ContextConsumer assertLiquibase(Consumer consumer) { return (context) -> { assertThat(context).hasSingleBean(SpringLiquibase.class); @@ -634,6 +677,16 @@ public String getPassword() { } + @Configuration(proxyBeanMethods = false) + static class CustomizerConfiguration { + + @Bean + Customizer customizer() { + return (liquibase) -> liquibase.setChangeLogParameter("some key", "some value"); + } + + } + static class CustomH2Driver extends org.h2.Driver { } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibasePropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibasePropertiesTests.java new file mode 100644 index 000000000000..f6533a5a9bf7 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibasePropertiesTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.liquibase; + +import java.util.List; +import java.util.stream.Stream; + +import liquibase.UpdateSummaryEnum; +import liquibase.UpdateSummaryOutputEnum; +import liquibase.ui.UIServiceEnum; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties.ShowSummary; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties.ShowSummaryOutput; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties.UiService; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link LiquibaseProperties}. + * + * @author Andy Wilkinson + */ +class LiquibasePropertiesTests { + + @Test + void valuesOfShowSummaryMatchValuesOfUpdateSummaryEnum() { + assertThat(namesOf(ShowSummary.values())).isEqualTo(namesOf(UpdateSummaryEnum.values())); + } + + @Test + void valuesOfShowSummaryOutputMatchValuesOfUpdateSummaryOutputEnum() { + assertThat(namesOf(ShowSummaryOutput.values())).isEqualTo(namesOf(UpdateSummaryOutputEnum.values())); + } + + @Test + void valuesOfUiServiceMatchValuesOfUiServiceEnum() { + assertThat(namesOf(UiService.values())).isEqualTo(namesOf(UIServiceEnum.values())); + } + + private List namesOf(Enum[] input) { + return Stream.of(input).map(Enum::name).toList(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportLoggingListenerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportLoggingListenerTests.java index ebeba985c06a..0c2d48f743fb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportLoggingListenerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportLoggingListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,7 +58,7 @@ void logsDebugOnContextRefresh(CapturedOutput output) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); this.initializer.initialize(context); context.register(Config.class); - withDebugLogging(() -> context.refresh()); + withDebugLogging(context::refresh); assertThat(output).contains("CONDITIONS EVALUATION REPORT"); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mail/MailSenderAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mail/MailSenderAutoConfigurationTests.java index 6fd720cbbaa0..8bf0ba25c598 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mail/MailSenderAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mail/MailSenderAutoConfigurationTests.java @@ -19,6 +19,7 @@ import java.util.Properties; import javax.naming.Context; +import javax.net.ssl.SSLSocketFactory; import jakarta.mail.Session; import org.junit.jupiter.api.AfterEach; @@ -29,6 +30,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jndi.JndiPropertiesHidingClassLoader; import org.springframework.boot.autoconfigure.jndi.TestableInitialContextFactory; +import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -49,8 +51,9 @@ */ class MailSenderAutoConfigurationTests { - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration( - AutoConfigurations.of(MailSenderAutoConfiguration.class, MailSenderValidatorAutoConfiguration.class)); + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(MailSenderAutoConfiguration.class, + MailSenderValidatorAutoConfiguration.class, SslAutoConfiguration.class)); private ClassLoader threadContextClassLoader; @@ -240,6 +243,61 @@ void connectionOnStartupNotCalled() { }); } + @Test + void smtpSslEnabled() { + this.contextRunner.withPropertyValues("spring.mail.host:localhost", "spring.mail.ssl.enabled:true") + .run((context) -> { + assertThat(context).hasSingleBean(JavaMailSenderImpl.class); + JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class); + assertThat(mailSender.getJavaMailProperties()).containsEntry("mail.smtp.ssl.enable", "true"); + }); + } + + @Test + void smtpSslBundle() { + this.contextRunner + .withPropertyValues("spring.mail.host:localhost", "spring.mail.ssl.bundle:test-bundle", + "spring.ssl.bundle.jks.test-bundle.keystore.location:classpath:test.jks", + "spring.ssl.bundle.jks.test-bundle.keystore.password:secret", + "spring.ssl.bundle.jks.test-bundle.key.password:password") + .run((context) -> { + assertThat(context).hasSingleBean(JavaMailSenderImpl.class); + JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class); + assertThat(mailSender.getJavaMailProperties()).doesNotContainKey("mail.smtp.ssl.enable"); + Object property = mailSender.getJavaMailProperties().get("mail.smtp.ssl.socketFactory"); + assertThat(property).isInstanceOf(SSLSocketFactory.class); + }); + } + + @Test + void smtpsSslEnabled() { + this.contextRunner + .withPropertyValues("spring.mail.host:localhost", "spring.mail.protocol:smtps", + "spring.mail.ssl.enabled:true") + .run((context) -> { + assertThat(context).hasSingleBean(JavaMailSenderImpl.class); + JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class); + assertThat(mailSender.getJavaMailProperties()).containsEntry("mail.smtps.ssl.enable", "true"); + }); + } + + @Test + void smtpsSslBundle() { + this.contextRunner + .withPropertyValues("spring.mail.host:localhost", "spring.mail.protocol:smtps", + "spring.mail.ssl.bundle:test-bundle", + "spring.ssl.bundle.jks.test-bundle.keystore.location:classpath:test.jks", + "spring.ssl.bundle.jks.test-bundle.keystore.password:secret", + "spring.ssl.bundle.jks.test-bundle.key.password:password") + .run((context) -> { + assertThat(context).hasSingleBean(JavaMailSenderImpl.class); + JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class); + assertThat(mailSender.getJavaMailProperties()).doesNotContainKey("mail.smtps.ssl.enable"); + Object property = mailSender.getJavaMailProperties().get("mail.smtps.ssl.socketFactory"); + assertThat(property).isInstanceOf(SSLSocketFactory.class); + }); + } + private Session configureJndiSession(String name) { Properties properties = new Properties(); Session session = Session.getDefaultInstance(properties); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfigurationTests.java index c71d6f481b3b..b2122ebecc2d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesClientSettingsBuilderCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesClientSettingsBuilderCustomizerTests.java deleted file mode 100644 index 021d298523c0..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesClientSettingsBuilderCustomizerTests.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.mongo; - -import java.util.Arrays; -import java.util.List; - -import com.mongodb.MongoClientSettings; -import com.mongodb.MongoCredential; -import com.mongodb.ServerAddress; -import org.bson.UuidRepresentation; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link MongoPropertiesClientSettingsBuilderCustomizer}. - * - * @author Scott Frederick - */ -@Deprecated(since = "3.1.0", forRemoval = true) -class MongoPropertiesClientSettingsBuilderCustomizerTests { - - private final MongoProperties properties = new MongoProperties(); - - @Test - void portCanBeCustomized() { - this.properties.setPort(12345); - MongoClientSettings settings = customizeSettings(); - List allAddresses = getAllAddresses(settings); - assertThat(allAddresses).hasSize(1); - assertServerAddress(allAddresses.get(0), "localhost", 12345); - } - - @Test - void hostCanBeCustomized() { - this.properties.setHost("mongo.example.com"); - MongoClientSettings settings = customizeSettings(); - List allAddresses = getAllAddresses(settings); - assertThat(allAddresses).hasSize(1); - assertServerAddress(allAddresses.get(0), "mongo.example.com", 27017); - } - - @Test - void additionalHostCanBeAdded() { - this.properties.setHost("mongo.example.com"); - this.properties.setAdditionalHosts(Arrays.asList("mongo.example.com:33", "mongo.example2.com")); - MongoClientSettings settings = customizeSettings(); - List allAddresses = getAllAddresses(settings); - assertThat(allAddresses).hasSize(3); - assertServerAddress(allAddresses.get(0), "mongo.example.com", 27017); - assertServerAddress(allAddresses.get(1), "mongo.example.com", 33); - assertServerAddress(allAddresses.get(2), "mongo.example2.com", 27017); - } - - @Test - void credentialsCanBeCustomized() { - this.properties.setUsername("user"); - this.properties.setPassword("secret".toCharArray()); - MongoClientSettings settings = customizeSettings(); - assertMongoCredential(settings.getCredential(), "user", "secret", "test"); - } - - @Test - void replicaSetCanBeCustomized() { - this.properties.setReplicaSetName("test"); - MongoClientSettings settings = customizeSettings(); - assertThat(settings.getClusterSettings().getRequiredReplicaSetName()).isEqualTo("test"); - } - - @Test - void databaseCanBeCustomized() { - this.properties.setDatabase("foo"); - this.properties.setUsername("user"); - this.properties.setPassword("secret".toCharArray()); - MongoClientSettings settings = customizeSettings(); - assertMongoCredential(settings.getCredential(), "user", "secret", "foo"); - } - - @Test - void uuidRepresentationDefaultToJavaLegacy() { - MongoClientSettings settings = customizeSettings(); - assertThat(settings.getUuidRepresentation()).isEqualTo(UuidRepresentation.JAVA_LEGACY); - } - - @Test - void uuidRepresentationCanBeCustomized() { - this.properties.setUuidRepresentation(UuidRepresentation.STANDARD); - MongoClientSettings settings = customizeSettings(); - assertThat(settings.getUuidRepresentation()).isEqualTo(UuidRepresentation.STANDARD); - } - - @Test - void authenticationDatabaseCanBeCustomized() { - this.properties.setAuthenticationDatabase("foo"); - this.properties.setUsername("user"); - this.properties.setPassword("secret".toCharArray()); - MongoClientSettings settings = customizeSettings(); - assertMongoCredential(settings.getCredential(), "user", "secret", "foo"); - } - - @Test - void onlyHostAndPortSetShouldUseThat() { - this.properties.setHost("localhost"); - this.properties.setPort(27017); - MongoClientSettings settings = customizeSettings(); - List allAddresses = getAllAddresses(settings); - assertThat(allAddresses).hasSize(1); - assertServerAddress(allAddresses.get(0), "localhost", 27017); - } - - @Test - void onlyUriSetShouldUseThat() { - this.properties.setUri("mongodb://mongo1.example.com:12345"); - MongoClientSettings settings = customizeSettings(); - List allAddresses = getAllAddresses(settings); - assertThat(allAddresses).hasSize(1); - assertServerAddress(allAddresses.get(0), "mongo1.example.com", 12345); - } - - @Test - void noCustomAddressAndNoUriUsesDefaultUri() { - MongoClientSettings settings = customizeSettings(); - List allAddresses = getAllAddresses(settings); - assertThat(allAddresses).hasSize(1); - assertServerAddress(allAddresses.get(0), "localhost", 27017); - } - - @Test - void uriCanBeCustomized() { - this.properties.setUri("mongodb://user:secret@mongo1.example.com:12345,mongo2.example.com:23456/test"); - MongoClientSettings settings = customizeSettings(); - List allAddresses = getAllAddresses(settings); - assertThat(allAddresses).hasSize(2); - assertServerAddress(allAddresses.get(0), "mongo1.example.com", 12345); - assertServerAddress(allAddresses.get(1), "mongo2.example.com", 23456); - assertMongoCredential(settings.getCredential(), "user", "secret", "test"); - } - - @Test - void uriOverridesUsernameAndPassword() { - this.properties.setUri("mongodb://127.0.0.1:1234/mydb"); - this.properties.setUsername("user"); - this.properties.setPassword("secret".toCharArray()); - MongoClientSettings settings = customizeSettings(); - assertThat(settings.getCredential()).isNull(); - } - - @Test - void uriOverridesDatabase() { - this.properties.setUri("mongodb://secret:password@127.0.0.1:1234/mydb"); - this.properties.setDatabase("test"); - MongoClientSettings settings = customizeSettings(); - List allAddresses = getAllAddresses(settings); - assertThat(allAddresses).hasSize(1); - assertServerAddress(allAddresses.get(0), "127.0.0.1", 1234); - assertThat(settings.getCredential().getSource()).isEqualTo("mydb"); - } - - @Test - void uriOverridesHostAndPort() { - this.properties.setUri("mongodb://127.0.0.1:1234/mydb"); - this.properties.setHost("localhost"); - this.properties.setPort(4567); - MongoClientSettings settings = customizeSettings(); - List addresses = getAllAddresses(settings); - assertThat(addresses.get(0).getHost()).isEqualTo("127.0.0.1"); - assertThat(addresses.get(0).getPort()).isEqualTo(1234); - } - - @Test - void retryWritesIsPropagatedFromUri() { - this.properties.setUri("mongodb://localhost/test?retryWrites=false"); - MongoClientSettings settings = customizeSettings(); - assertThat(settings.getRetryWrites()).isFalse(); - } - - @SuppressWarnings("removal") - private MongoClientSettings customizeSettings() { - MongoClientSettings.Builder settings = MongoClientSettings.builder(); - new MongoPropertiesClientSettingsBuilderCustomizer(this.properties).customize(settings); - return settings.build(); - } - - private List getAllAddresses(MongoClientSettings settings) { - return settings.getClusterSettings().getHosts(); - } - - protected void assertServerAddress(ServerAddress serverAddress, String expectedHost, int expectedPort) { - assertThat(serverAddress.getHost()).isEqualTo(expectedHost); - assertThat(serverAddress.getPort()).isEqualTo(expectedPort); - } - - protected void assertMongoCredential(MongoCredential credentials, String expectedUsername, String expectedPassword, - String expectedSource) { - assertThat(credentials.getUserName()).isEqualTo(expectedUsername); - assertThat(credentials.getPassword()).isEqualTo(expectedPassword.toCharArray()); - assertThat(credentials.getSource()).isEqualTo(expectedSource); - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfigurationTests.java index cc30d13a73ae..a3225f4fea0f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,11 +23,9 @@ import com.mongodb.MongoClientSettings; import com.mongodb.MongoCredential; import com.mongodb.ReadPreference; -import com.mongodb.connection.AsynchronousSocketChannelStreamFactoryFactory; +import com.mongodb.connection.NettyTransportSettings; import com.mongodb.connection.SslSettings; -import com.mongodb.connection.StreamFactory; -import com.mongodb.connection.StreamFactoryFactory; -import com.mongodb.connection.netty.NettyStreamFactoryFactory; +import com.mongodb.connection.TransportSettings; import com.mongodb.reactivestreams.client.MongoClient; import com.mongodb.reactivestreams.client.internal.MongoClientImpl; import io.netty.channel.EventLoopGroup; @@ -39,12 +37,8 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; /** * Tests for {@link MongoReactiveAutoConfiguration}. @@ -85,7 +79,7 @@ void settingsSslConfig() { assertThat(context).hasSingleBean(MongoClient.class); MongoClientSettings settings = getSettings(context); assertThat(settings.getApplicationName()).isEqualTo("test-config"); - assertThat(settings.getStreamFactoryFactory()).isSameAs(context.getBean("myStreamFactoryFactory")); + assertThat(settings.getTransportSettings()).isSameAs(context.getBean("myTransportSettings")); }); } @@ -212,13 +206,13 @@ void configuresCredentialsFromUriPropertyWithAuthDatabase() { } @Test - void nettyStreamFactoryFactoryIsConfiguredAutomatically() { + void nettyTransportSettingsAreConfiguredAutomatically() { AtomicReference eventLoopGroupReference = new AtomicReference<>(); this.contextRunner.run((context) -> { assertThat(context).hasSingleBean(MongoClient.class); - StreamFactoryFactory factory = getSettings(context).getStreamFactoryFactory(); - assertThat(factory).isInstanceOf(NettyStreamFactoryFactory.class); - EventLoopGroup eventLoopGroup = (EventLoopGroup) ReflectionTestUtils.getField(factory, "eventLoopGroup"); + TransportSettings transportSettings = getSettings(context).getTransportSettings(); + assertThat(transportSettings).isInstanceOf(NettyTransportSettings.class); + EventLoopGroup eventLoopGroup = ((NettyTransportSettings) transportSettings).getEventLoopGroup(); assertThat(eventLoopGroup.isShutdown()).isFalse(); eventLoopGroupReference.set(eventLoopGroup); }); @@ -226,14 +220,16 @@ void nettyStreamFactoryFactoryIsConfiguredAutomatically() { } @Test - void customizerOverridesAutoConfig() { + @SuppressWarnings("deprecation") + void customizerWithTransportSettingsOverridesAutoConfig() { this.contextRunner.withPropertyValues("spring.data.mongodb.uri:mongodb://localhost/test?appname=auto-config") - .withUserConfiguration(SimpleCustomizerConfig.class) + .withUserConfiguration(SimpleTransportSettingsCustomizerConfig.class) .run((context) -> { assertThat(context).hasSingleBean(MongoClient.class); MongoClientSettings settings = getSettings(context); - assertThat(settings.getApplicationName()).isEqualTo("overridden-name"); - assertThat(settings.getStreamFactoryFactory()).isEqualTo(SimpleCustomizerConfig.streamFactoryFactory); + assertThat(settings.getApplicationName()).isEqualTo("custom-transport-settings"); + assertThat(settings.getTransportSettings()) + .isSameAs(SimpleTransportSettingsCustomizerConfig.transportSettings); }); } @@ -284,32 +280,29 @@ MongoClientSettings mongoClientSettings() { static class SslSettingsConfig { @Bean - MongoClientSettings mongoClientSettings(StreamFactoryFactory streamFactoryFactory) { + MongoClientSettings mongoClientSettings(TransportSettings transportSettings) { return MongoClientSettings.builder() .applicationName("test-config") - .streamFactoryFactory(streamFactoryFactory) + .transportSettings(transportSettings) .build(); } @Bean - StreamFactoryFactory myStreamFactoryFactory() { - StreamFactoryFactory streamFactoryFactory = mock(StreamFactoryFactory.class); - given(streamFactoryFactory.create(any(), any())).willReturn(mock(StreamFactory.class)); - return streamFactoryFactory; + TransportSettings myTransportSettings() { + return TransportSettings.nettyBuilder().build(); } } @Configuration(proxyBeanMethods = false) - static class SimpleCustomizerConfig { + static class SimpleTransportSettingsCustomizerConfig { - private static final StreamFactoryFactory streamFactoryFactory = new AsynchronousSocketChannelStreamFactoryFactory.Builder() - .build(); + private static final TransportSettings transportSettings = TransportSettings.nettyBuilder().build(); @Bean MongoClientSettingsBuilderCustomizer customizer() { - return (clientSettingsBuilder) -> clientSettingsBuilder.applicationName("overridden-name") - .streamFactoryFactory(streamFactoryFactory); + return (clientSettingsBuilder) -> clientSettingsBuilder.applicationName("custom-transport-settings") + .transportSettings(transportSettings); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationIntegrationTests.java deleted file mode 100644 index c8febbbe6056..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationIntegrationTests.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.neo4j; - -import java.time.Duration; - -import org.junit.jupiter.api.Test; -import org.neo4j.driver.Driver; -import org.neo4j.driver.Result; -import org.neo4j.driver.Session; -import org.neo4j.driver.Transaction; -import org.testcontainers.containers.Neo4jContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; -import org.springframework.context.annotation.Configuration; -import org.springframework.test.context.DynamicPropertyRegistry; -import org.springframework.test.context.DynamicPropertySource; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link Neo4jAutoConfiguration}. - * - * @author Michael J. Simons - * @author Stephane Nicoll - */ -@SpringBootTest -@Testcontainers(disabledWithoutDocker = true) -class Neo4jAutoConfigurationIntegrationTests { - - @Container - private static final Neo4jContainer neo4jServer = new Neo4jContainer<>(DockerImageNames.neo4j()) - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); - - @DynamicPropertySource - static void neo4jProperties(DynamicPropertyRegistry registry) { - registry.add("spring.neo4j.uri", neo4jServer::getBoltUrl); - registry.add("spring.neo4j.authentication.username", () -> "neo4j"); - registry.add("spring.neo4j.authentication.password", neo4jServer::getAdminPassword); - } - - @Autowired - private Driver driver; - - @Test - void driverCanHandleRequest() { - try (Session session = this.driver.session(); Transaction tx = session.beginTransaction()) { - Result statementResult = tx.run("MATCH (n:Thing) RETURN n LIMIT 1"); - assertThat(statementResult.hasNext()).isFalse(); - tx.commit(); - } - } - - @Configuration(proxyBeanMethods = false) - @ImportAutoConfiguration(Neo4jAutoConfiguration.class) - static class TestConfiguration { - - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java index df6821f2c5ef..1a05936d3c65 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.neo4j.driver.AuthTokenManagers; import org.neo4j.driver.AuthTokens; import org.neo4j.driver.Config; import org.neo4j.driver.Config.ConfigBuilder; @@ -143,7 +144,7 @@ void maxTransactionRetryTime() { @Test void uriShouldDefaultToLocalhost() { - assertThat(new PropertiesNeo4jConnectionDetails(new Neo4jProperties()).getUri()) + assertThat(new PropertiesNeo4jConnectionDetails(new Neo4jProperties(), null).getUri()) .isEqualTo(URI.create("bolt://localhost:7687")); } @@ -152,12 +153,12 @@ void determineServerUriWithCustomUriShouldOverrideDefault() { URI customUri = URI.create("bolt://localhost:4242"); Neo4jProperties properties = new Neo4jProperties(); properties.setUri(customUri); - assertThat(new PropertiesNeo4jConnectionDetails(properties).getUri()).isEqualTo(customUri); + assertThat(new PropertiesNeo4jConnectionDetails(properties, null).getUri()).isEqualTo(customUri); } @Test void authenticationShouldDefaultToNone() { - assertThat(new PropertiesNeo4jConnectionDetails(new Neo4jProperties()).getAuthToken()) + assertThat(new PropertiesNeo4jConnectionDetails(new Neo4jProperties(), null).getAuthToken()) .isEqualTo(AuthTokens.none()); } @@ -166,8 +167,9 @@ void authenticationWithUsernameShouldEnableBasicAuth() { Neo4jProperties properties = new Neo4jProperties(); properties.getAuthentication().setUsername("Farin"); properties.getAuthentication().setPassword("Urlaub"); - assertThat(new PropertiesNeo4jConnectionDetails(properties).getAuthToken()) - .isEqualTo(AuthTokens.basic("Farin", "Urlaub")); + PropertiesNeo4jConnectionDetails connectionDetails = new PropertiesNeo4jConnectionDetails(properties, null); + assertThat(connectionDetails.getAuthToken()).isEqualTo(AuthTokens.basic("Farin", "Urlaub")); + assertThat(connectionDetails.getAuthTokenManager()).isNull(); } @Test @@ -177,8 +179,22 @@ void authenticationWithUsernameAndRealmShouldEnableBasicAuth() { authentication.setUsername("Farin"); authentication.setPassword("Urlaub"); authentication.setRealm("Test Realm"); - assertThat(new PropertiesNeo4jConnectionDetails(properties).getAuthToken()) - .isEqualTo(AuthTokens.basic("Farin", "Urlaub", "Test Realm")); + PropertiesNeo4jConnectionDetails connectionDetails = new PropertiesNeo4jConnectionDetails(properties, null); + assertThat(connectionDetails.getAuthToken()).isEqualTo(AuthTokens.basic("Farin", "Urlaub", "Test Realm")); + assertThat(connectionDetails.getAuthTokenManager()).isNull(); + } + + @Test + void authenticationWithAuthTokenManagerAndUsernameShouldProvideAuthTokenManger() { + Neo4jProperties properties = new Neo4jProperties(); + Authentication authentication = properties.getAuthentication(); + authentication.setUsername("Farin"); + authentication.setPassword("Urlaub"); + authentication.setRealm("Test Realm"); + assertThat(new PropertiesNeo4jConnectionDetails(properties, + AuthTokenManagers.bearer( + () -> AuthTokens.basic("username", "password").expiringAt(System.currentTimeMillis() + 5000))) + .getAuthTokenManager()).isNotNull(); } @Test @@ -186,7 +202,7 @@ void authenticationWithKerberosTicketShouldEnableKerberos() { Neo4jProperties properties = new Neo4jProperties(); Authentication authentication = properties.getAuthentication(); authentication.setKerberosTicket("AABBCCDDEE"); - assertThat(new PropertiesNeo4jConnectionDetails(properties).getAuthToken()) + assertThat(new PropertiesNeo4jConnectionDetails(properties, null).getAuthToken()) .isEqualTo(AuthTokens.kerberos("AABBCCDDEE")); } @@ -197,7 +213,7 @@ void authenticationWithBothUsernameAndKerberosShouldNotBeAllowed() { authentication.setUsername("Farin"); authentication.setKerberosTicket("AABBCCDDEE"); assertThatIllegalStateException() - .isThrownBy(() -> new PropertiesNeo4jConnectionDetails(properties).getAuthToken()) + .isThrownBy(() -> new PropertiesNeo4jConnectionDetails(properties, null).getAuthToken()) .withMessage("Cannot specify both username ('Farin') and kerberos ticket ('AABBCCDDEE')"); } @@ -313,7 +329,7 @@ void driverConfigShouldBeConfiguredToUseUseSpringJclLogging() { private Config mapDriverConfig(Neo4jProperties properties, ConfigBuilderCustomizer... customizers) { return new Neo4jAutoConfiguration().mapDriverConfig(properties, - new PropertiesNeo4jConnectionDetails(properties), Arrays.asList(customizers)); + new PropertiesNeo4jConnectionDetails(properties, null), Arrays.asList(customizers)); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java index 38909c284b3a..d5efec995d6f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,7 @@ import org.springframework.boot.autoconfigure.orm.jpa.test.City; import org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration; import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; +import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizationAutoConfiguration; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -53,6 +54,7 @@ import org.springframework.orm.jpa.JpaVendorAdapter; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager; +import org.springframework.orm.jpa.persistenceunit.ManagedClassNameFilter; import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes; import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager; import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter; @@ -68,6 +70,7 @@ * @author Phillip Webb * @author Dave Syer * @author Stephane Nicoll + * @author Yanming Zhou */ abstract class AbstractJpaAutoConfigurationTests { @@ -82,7 +85,8 @@ protected AbstractJpaAutoConfigurationTests(Class autoConfiguredClass) { "spring.jta.log-dir=" + new File(new BuildOutput(getClass()).getRootLocation(), "transaction-logs")) .withUserConfiguration(TestConfiguration.class) .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, - TransactionAutoConfiguration.class, SqlInitializationAutoConfiguration.class, autoConfiguredClass)); + TransactionAutoConfiguration.class, TransactionManagerCustomizationAutoConfiguration.class, + SqlInitializationAutoConfiguration.class, autoConfiguredClass)); } protected ApplicationContextRunner contextRunner() { @@ -277,6 +281,16 @@ void customPersistenceUnitPostProcessors() { }); } + @Test + void customManagedClassNameFilter() { + this.contextRunner.withBean(ManagedClassNameFilter.class, () -> (s) -> !s.endsWith("City")) + .withUserConfiguration(AutoConfigurePackageForCountry.class) + .run((context) -> { + EntityManager entityManager = context.getBean(EntityManagerFactory.class).createEntityManager(); + assertThat(getManagedJavaTypes(entityManager)).contains(Country.class).doesNotContain(City.class); + }); + } + private Class[] getManagedJavaTypes(EntityManager entityManager) { Set> managedTypes = entityManager.getMetamodel().getManagedTypes(); return managedTypes.stream().map(ManagedType::getJavaType).toArray(Class[]::new); @@ -421,6 +435,12 @@ TransactionManager testTransactionManager() { } + @Configuration(proxyBeanMethods = false) + @TestAutoConfigurationPackage(Country.class) + static class AutoConfigurePackageForCountry { + + } + @Configuration(proxyBeanMethods = false) @TestAutoConfigurationPackage(AbstractJpaAutoConfigurationTests.class) static class TestConfigurationWithCustomPersistenceUnitManager { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java index f320c683f482..3d0d625c33b5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,8 @@ import org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy; import org.hibernate.boot.model.naming.ImplicitNamingStrategy; import org.hibernate.boot.model.naming.PhysicalNamingStrategy; -import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.ManagedBeanSettings; +import org.hibernate.cfg.SchemaToolingSettings; import org.hibernate.dialect.H2Dialect; import org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; @@ -131,7 +132,8 @@ void testDmlScript() { void testDmlScriptRunsEarly() { contextRunner().withUserConfiguration(TestInitializedJpaConfiguration.class) .withClassLoader(new HideDataScriptClassLoader()) - .withPropertyValues("spring.jpa.show-sql=true", "spring.jpa.hibernate.ddl-auto:create-drop", + .withPropertyValues("spring.jpa.show-sql=true", "spring.jpa.properties.hibernate.format_sql=true", + "spring.jpa.properties.hibernate.highlight_sql=true", "spring.jpa.hibernate.ddl-auto:create-drop", "spring.sql.init.data-locations:/city.sql", "spring.jpa.defer-datasource-initialization=true") .run((context) -> assertThat(context.getBean(TestInitializedJpaConfiguration.class).called).isTrue()); } @@ -155,7 +157,7 @@ void testFlywayPlusValidation() { @Test void testLiquibasePlusValidation() { contextRunner() - .withPropertyValues("spring.liquibase.changeLog:classpath:db/changelog/db.changelog-city.yaml", + .withPropertyValues("spring.liquibase.change-log:classpath:db/changelog/db.changelog-city.yaml", "spring.jpa.hibernate.ddl-auto:validate") .withConfiguration(AutoConfigurations.of(LiquibaseAutoConfiguration.class)) .run((context) -> assertThat(context).hasNotFailed()); @@ -388,8 +390,8 @@ void hibernatePropertiesCustomizerCanDisableBeanContainer() { @Test void vendorPropertiesWithEmbeddedDatabaseAndNoDdlProperty() { contextRunner().run(vendorProperties((vendorProperties) -> { - assertThat(vendorProperties).doesNotContainKeys(AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION); - assertThat(vendorProperties).containsEntry(AvailableSettings.HBM2DDL_AUTO, "create-drop"); + assertThat(vendorProperties).doesNotContainKeys(SchemaToolingSettings.JAKARTA_HBM2DDL_DATABASE_ACTION); + assertThat(vendorProperties).containsEntry(SchemaToolingSettings.HBM2DDL_AUTO, "create-drop"); })); } @@ -397,8 +399,8 @@ void vendorPropertiesWithEmbeddedDatabaseAndNoDdlProperty() { void vendorPropertiesWhenDdlAutoPropertyIsSet() { contextRunner().withPropertyValues("spring.jpa.hibernate.ddl-auto=update") .run(vendorProperties((vendorProperties) -> { - assertThat(vendorProperties).doesNotContainKeys(AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION); - assertThat(vendorProperties).containsEntry(AvailableSettings.HBM2DDL_AUTO, "update"); + assertThat(vendorProperties).doesNotContainKeys(SchemaToolingSettings.JAKARTA_HBM2DDL_DATABASE_ACTION); + assertThat(vendorProperties).containsEntry(SchemaToolingSettings.HBM2DDL_AUTO, "update"); })); } @@ -408,8 +410,8 @@ void vendorPropertiesWhenDdlAutoPropertyAndHibernatePropertiesAreSet() { .withPropertyValues("spring.jpa.hibernate.ddl-auto=update", "spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop") .run(vendorProperties((vendorProperties) -> { - assertThat(vendorProperties).doesNotContainKeys(AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION); - assertThat(vendorProperties).containsEntry(AvailableSettings.HBM2DDL_AUTO, "create-drop"); + assertThat(vendorProperties).doesNotContainKeys(SchemaToolingSettings.JAKARTA_HBM2DDL_DATABASE_ACTION); + assertThat(vendorProperties).containsEntry(SchemaToolingSettings.HBM2DDL_AUTO, "create-drop"); })); } @@ -417,7 +419,7 @@ void vendorPropertiesWhenDdlAutoPropertyAndHibernatePropertiesAreSet() { void vendorPropertiesWhenDdlAutoPropertyIsSetToNone() { contextRunner().withPropertyValues("spring.jpa.hibernate.ddl-auto=none") .run(vendorProperties((vendorProperties) -> assertThat(vendorProperties).doesNotContainKeys( - AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION, AvailableSettings.HBM2DDL_AUTO))); + SchemaToolingSettings.JAKARTA_HBM2DDL_DATABASE_ACTION, SchemaToolingSettings.HBM2DDL_AUTO))); } @Test @@ -425,8 +427,9 @@ void vendorPropertiesWhenJpaDdlActionIsSet() { contextRunner() .withPropertyValues("spring.jpa.properties.jakarta.persistence.schema-generation.database.action=create") .run(vendorProperties((vendorProperties) -> { - assertThat(vendorProperties).containsEntry(AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION, "create"); - assertThat(vendorProperties).doesNotContainKeys(AvailableSettings.HBM2DDL_AUTO); + assertThat(vendorProperties).containsEntry(SchemaToolingSettings.JAKARTA_HBM2DDL_DATABASE_ACTION, + "create"); + assertThat(vendorProperties).doesNotContainKeys(SchemaToolingSettings.HBM2DDL_AUTO); })); } @@ -436,8 +439,9 @@ void vendorPropertiesWhenBothDdlAutoPropertiesAreSet() { .withPropertyValues("spring.jpa.properties.jakarta.persistence.schema-generation.database.action=create", "spring.jpa.hibernate.ddl-auto=create-only") .run(vendorProperties((vendorProperties) -> { - assertThat(vendorProperties).containsEntry(AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION, "create"); - assertThat(vendorProperties).containsEntry(AvailableSettings.HBM2DDL_AUTO, "create-only"); + assertThat(vendorProperties).containsEntry(SchemaToolingSettings.JAKARTA_HBM2DDL_DATABASE_ACTION, + "create"); + assertThat(vendorProperties).containsEntry(SchemaToolingSettings.HBM2DDL_AUTO, "create-only"); })); } @@ -650,7 +654,7 @@ static class DisableBeanContainerConfiguration { @Bean HibernatePropertiesCustomizer disableBeanContainerHibernatePropertiesCustomizer() { - return (hibernateProperties) -> hibernateProperties.remove(AvailableSettings.BEAN_CONTAINER); + return (hibernateProperties) -> hibernateProperties.remove(ManagedBeanSettings.BEAN_CONTAINER); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/Customizers.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/Customizers.java new file mode 100644 index 000000000000..7f8de3556175 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/Customizers.java @@ -0,0 +1,106 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import java.util.List; +import java.util.function.BiConsumer; + +import org.assertj.core.api.AssertDelegateTarget; +import org.mockito.InOrder; + +import org.springframework.test.util.ReflectionTestUtils; + +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; + +/** + * Test utility used to check customizers are called correctly. + * + * @param the customizer type + * @param the target class that is customized + * @author Phillip Webb + * @author Chris Bono + */ +final class Customizers { + + private final BiConsumer customizeAction; + + private final Class targetClass; + + @SuppressWarnings("unchecked") + private Customizers(Class targetClass, BiConsumer customizeAction) { + this.customizeAction = customizeAction; + this.targetClass = (Class) targetClass; + } + + /** + * Create an instance by getting the value from a field. + * @param source the source to extract the customizers from + * @param fieldName the field name + * @return a new {@link CustomizersAssert} instance + */ + @SuppressWarnings("unchecked") + CustomizersAssert fromField(Object source, String fieldName) { + return new CustomizersAssert(ReflectionTestUtils.getField(source, fieldName)); + } + + /** + * Create a new {@link Customizers} instance. + * @param the customizer class + * @param the target class that is customized + * @param targetClass the target class that is customized + * @param customizeAction the customizer action to take + * @return a new {@link Customizers} instance + */ + static Customizers of(Class targetClass, BiConsumer customizeAction) { + return new Customizers<>(targetClass, customizeAction); + } + + /** + * Assertions that can be applied to customizers. + */ + final class CustomizersAssert implements AssertDelegateTarget { + + private final List customizers; + + @SuppressWarnings("unchecked") + private CustomizersAssert(Object customizers) { + this.customizers = (customizers instanceof List) ? (List) customizers : List.of((C) customizers); + } + + /** + * Assert that the customize method is called in a specified order. It is expected + * that each customizer has set a unique value so the expected values can be used + * as a verify step. + * @param the value type + * @param call the call the customizer makes + * @param expectedValues the expected values + */ + @SuppressWarnings("unchecked") + void callsInOrder(BiConsumer call, V... expectedValues) { + T target = mock(Customizers.this.targetClass); + BiConsumer customizeAction = Customizers.this.customizeAction; + this.customizers.forEach((customizer) -> customizeAction.accept(customizer, target)); + InOrder ordered = inOrder(target); + for (V expectedValue : expectedValues) { + call.accept(ordered.verify(target), expectedValue); + } + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/DeadLetterPolicyMapperTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/DeadLetterPolicyMapperTests.java new file mode 100644 index 000000000000..afc18050f64b --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/DeadLetterPolicyMapperTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import org.apache.pulsar.client.api.DeadLetterPolicy; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link DeadLetterPolicyMapper}. + * + * @author Chris Bono + * @author Phillip Webb + */ +class DeadLetterPolicyMapperTests { + + @Test + void map() { + PulsarProperties.Consumer.DeadLetterPolicy properties = new PulsarProperties.Consumer.DeadLetterPolicy(); + properties.setMaxRedeliverCount(100); + properties.setRetryLetterTopic("my-retry-topic"); + properties.setDeadLetterTopic("my-dlt-topic"); + properties.setInitialSubscriptionName("my-initial-subscription"); + DeadLetterPolicy policy = DeadLetterPolicyMapper.map(properties); + assertThat(policy.getMaxRedeliverCount()).isEqualTo(100); + assertThat(policy.getRetryLetterTopic()).isEqualTo("my-retry-topic"); + assertThat(policy.getDeadLetterTopic()).isEqualTo("my-dlt-topic"); + assertThat(policy.getInitialSubscriptionName()).isEqualTo("my-initial-subscription"); + } + + @Test + void mapWhenMaxRedeliverCountIsNotPositiveThrowsException() { + PulsarProperties.Consumer.DeadLetterPolicy properties = new PulsarProperties.Consumer.DeadLetterPolicy(); + properties.setMaxRedeliverCount(0); + assertThatIllegalStateException().isThrownBy(() -> DeadLetterPolicyMapper.map(properties)) + .withMessage("Pulsar DeadLetterPolicy must have a positive 'max-redelivery-count' property value"); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/MockAuthentication.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/MockAuthentication.java new file mode 100644 index 000000000000..33216b086bb7 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/MockAuthentication.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.apache.pulsar.client.api.Authentication; +import org.apache.pulsar.client.api.AuthenticationDataProvider; +import org.apache.pulsar.client.api.PulsarClientException; + +/** + * Test plugin-class-name for Authentication + * + * @author Swamy Mavuri + */ +@SuppressWarnings("deprecation") +public class MockAuthentication implements Authentication { + + public Map authParamsMap = new HashMap<>(); + + @Override + public String getAuthMethodName() { + return null; + } + + @Override + public AuthenticationDataProvider getAuthData() { + return null; + } + + @Override + public void configure(Map authParams) { + this.authParamsMap = authParams; + } + + @Override + public void start() throws PulsarClientException { + + } + + @Override + public void close() throws IOException { + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PropertiesPulsarConnectionDetailsTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PropertiesPulsarConnectionDetailsTests.java new file mode 100644 index 000000000000..3abff9be7346 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PropertiesPulsarConnectionDetailsTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link PropertiesPulsarConnectionDetails}. + * + * @author Chris Bono + */ +class PropertiesPulsarConnectionDetailsTests { + + @Test + void getClientServiceUrlReturnsValueFromProperties() { + PulsarProperties properties = new PulsarProperties(); + properties.getClient().setServiceUrl("foo"); + PulsarConnectionDetails connectionDetails = new PropertiesPulsarConnectionDetails(properties); + assertThat(connectionDetails.getBrokerUrl()).isEqualTo("foo"); + } + + @Test + void getAdminServiceHttpUrlReturnsValueFromProperties() { + PulsarProperties properties = new PulsarProperties(); + properties.getAdmin().setServiceUrl("foo"); + PulsarConnectionDetails connectionDetails = new PropertiesPulsarConnectionDetails(properties); + assertThat(connectionDetails.getAdminUrl()).isEqualTo("foo"); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarAutoConfigurationTests.java new file mode 100644 index 000000000000..1b5e3fed0e4d --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarAutoConfigurationTests.java @@ -0,0 +1,670 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import com.github.benmanes.caffeine.cache.Caffeine; +import org.apache.pulsar.client.api.ConsumerBuilder; +import org.apache.pulsar.client.api.ProducerBuilder; +import org.apache.pulsar.client.api.PulsarClient; +import org.apache.pulsar.client.api.ReaderBuilder; +import org.apache.pulsar.client.api.interceptor.ProducerInterceptor; +import org.apache.pulsar.common.schema.SchemaType; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.core.task.VirtualThreadTaskExecutor; +import org.springframework.pulsar.annotation.PulsarBootstrapConfiguration; +import org.springframework.pulsar.annotation.PulsarListenerAnnotationBeanPostProcessor; +import org.springframework.pulsar.annotation.PulsarReaderAnnotationBeanPostProcessor; +import org.springframework.pulsar.cache.provider.caffeine.CaffeineCacheProvider; +import org.springframework.pulsar.config.ConcurrentPulsarListenerContainerFactory; +import org.springframework.pulsar.config.DefaultPulsarReaderContainerFactory; +import org.springframework.pulsar.config.PulsarListenerContainerFactory; +import org.springframework.pulsar.config.PulsarListenerEndpointRegistry; +import org.springframework.pulsar.config.PulsarReaderEndpointRegistry; +import org.springframework.pulsar.core.CachingPulsarProducerFactory; +import org.springframework.pulsar.core.ConsumerBuilderCustomizer; +import org.springframework.pulsar.core.DefaultPulsarClientFactory; +import org.springframework.pulsar.core.DefaultPulsarConsumerFactory; +import org.springframework.pulsar.core.DefaultPulsarProducerFactory; +import org.springframework.pulsar.core.DefaultPulsarReaderFactory; +import org.springframework.pulsar.core.DefaultSchemaResolver; +import org.springframework.pulsar.core.DefaultTopicResolver; +import org.springframework.pulsar.core.ProducerBuilderCustomizer; +import org.springframework.pulsar.core.PulsarAdministration; +import org.springframework.pulsar.core.PulsarConsumerFactory; +import org.springframework.pulsar.core.PulsarProducerFactory; +import org.springframework.pulsar.core.PulsarReaderFactory; +import org.springframework.pulsar.core.PulsarTemplate; +import org.springframework.pulsar.core.ReaderBuilderCustomizer; +import org.springframework.pulsar.core.SchemaResolver; +import org.springframework.pulsar.core.TopicResolver; +import org.springframework.pulsar.listener.PulsarContainerProperties.TransactionSettings; +import org.springframework.pulsar.transaction.PulsarAwareTransactionManager; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link PulsarAutoConfiguration}. + * + * @author Chris Bono + * @author Alexander Preuß + * @author Soby Chacko + * @author Phillip Webb + */ +class PulsarAutoConfigurationTests { + + private static final String INTERNAL_PULSAR_LISTENER_ANNOTATION_PROCESSOR = "org.springframework.pulsar.config.internalPulsarListenerAnnotationProcessor"; + + private static final String INTERNAL_PULSAR_READER_ANNOTATION_PROCESSOR = "org.springframework.pulsar.config.internalPulsarReaderAnnotationProcessor"; + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(PulsarAutoConfiguration.class)) + .withBean(PulsarClient.class, () -> mock(PulsarClient.class)); + + @Test + void whenPulsarNotOnClasspathAutoConfigurationIsSkipped() { + new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(PulsarAutoConfiguration.class)) + .withClassLoader(new FilteredClassLoader(PulsarClient.class)) + .run((context) -> assertThat(context).doesNotHaveBean(PulsarAutoConfiguration.class)); + } + + @Test + void whenSpringPulsarNotOnClasspathAutoConfigurationIsSkipped() { + this.contextRunner.withClassLoader(new FilteredClassLoader(PulsarTemplate.class)) + .run((context) -> assertThat(context).doesNotHaveBean(PulsarAutoConfiguration.class)); + } + + @Test + void whenCustomPulsarListenerAnnotationProcessorDefinedAutoConfigurationIsSkipped() { + this.contextRunner.withBean(INTERNAL_PULSAR_LISTENER_ANNOTATION_PROCESSOR, String.class, () -> "bean") + .run((context) -> assertThat(context).doesNotHaveBean(PulsarBootstrapConfiguration.class)); + } + + @Test + void whenCustomPulsarReaderAnnotationProcessorDefinedAutoConfigurationIsSkipped() { + this.contextRunner.withBean(INTERNAL_PULSAR_READER_ANNOTATION_PROCESSOR, String.class, () -> "bean") + .run((context) -> assertThat(context).doesNotHaveBean(PulsarBootstrapConfiguration.class)); + } + + @Test + void autoConfiguresBeans() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PulsarConfiguration.class) + .hasSingleBean(PulsarConnectionDetails.class) + .hasSingleBean(DefaultPulsarClientFactory.class) + .hasSingleBean(PulsarClient.class) + .hasSingleBean(PulsarAdministration.class) + .hasSingleBean(DefaultSchemaResolver.class) + .hasSingleBean(DefaultTopicResolver.class) + .hasSingleBean(CachingPulsarProducerFactory.class) + .hasSingleBean(PulsarTemplate.class) + .hasSingleBean(DefaultPulsarConsumerFactory.class) + .hasSingleBean(ConcurrentPulsarListenerContainerFactory.class) + .hasSingleBean(DefaultPulsarReaderFactory.class) + .hasSingleBean(DefaultPulsarReaderContainerFactory.class) + .hasSingleBean(PulsarListenerAnnotationBeanPostProcessor.class) + .hasSingleBean(PulsarListenerEndpointRegistry.class) + .hasSingleBean(PulsarReaderAnnotationBeanPostProcessor.class) + .hasSingleBean(PulsarReaderEndpointRegistry.class)); + } + + @Nested + class ProducerFactoryTests { + + private final ApplicationContextRunner contextRunner = PulsarAutoConfigurationTests.this.contextRunner; + + @Test + @SuppressWarnings("unchecked") + void whenHasUserDefinedBeanDoesNotAutoConfigureBean() { + PulsarProducerFactory producerFactory = mock(PulsarProducerFactory.class); + this.contextRunner + .withBean("customPulsarProducerFactory", PulsarProducerFactory.class, () -> producerFactory) + .run((context) -> assertThat(context).getBean(PulsarProducerFactory.class).isSameAs(producerFactory)); + } + + @Test + void whenNoPropertiesUsesCachingPulsarProducerFactory() { + this.contextRunner.run((context) -> assertThat(context).getBean(PulsarProducerFactory.class) + .isExactlyInstanceOf(CachingPulsarProducerFactory.class)); + } + + @Test + void whenCachingDisabledUsesDefaultPulsarProducerFactory() { + this.contextRunner.withPropertyValues("spring.pulsar.producer.cache.enabled=false") + .run((context) -> assertThat(context).getBean(PulsarProducerFactory.class) + .isExactlyInstanceOf(DefaultPulsarProducerFactory.class)); + } + + @Test + void whenCachingEnabledUsesCachingPulsarProducerFactory() { + this.contextRunner.withPropertyValues("spring.pulsar.producer.cache.enabled=true") + .run((context) -> assertThat(context).getBean(PulsarProducerFactory.class) + .isExactlyInstanceOf(CachingPulsarProducerFactory.class)); + } + + @Test + void whenCachingEnabledAndCaffeineNotOnClasspathStillUsesCaffeine() { + this.contextRunner.withClassLoader(new FilteredClassLoader(Caffeine.class)) + .withPropertyValues("spring.pulsar.producer.cache.enabled=true") + .run((context) -> { + assertThat(context).getBean(CachingPulsarProducerFactory.class) + .extracting("producerCache") + .extracting(Object::getClass) + .isEqualTo(CaffeineCacheProvider.class); + assertThat(context).getBean(CachingPulsarProducerFactory.class) + .extracting("producerCache.cache") + .extracting(Object::getClass) + .extracting(Class::getName) + .asString() + .startsWith("org.springframework.pulsar.shade.com.github.benmanes.caffeine.cache."); + }); + } + + @Test + void whenCustomCachingPropertiesCreatesConfiguredBean() { + this.contextRunner + .withPropertyValues("spring.pulsar.producer.cache.expire-after-access=100s", + "spring.pulsar.producer.cache.maximum-size=5150", + "spring.pulsar.producer.cache.initial-capacity=200") + .run((context) -> assertThat(context).getBean(CachingPulsarProducerFactory.class) + .extracting("producerCache.cache.cache") + .hasFieldOrPropertyWithValue("maximum", 5150L) + .hasFieldOrPropertyWithValue("expiresAfterAccessNanos", TimeUnit.SECONDS.toNanos(100))); + } + + @Test + void whenHasTopicNamePropertyCreatesConfiguredBean() { + this.contextRunner.withPropertyValues("spring.pulsar.producer.topic-name=my-topic") + .run((context) -> assertThat(context).getBean(DefaultPulsarProducerFactory.class) + .hasFieldOrPropertyWithValue("defaultTopic", "my-topic")); + } + + @Test + void injectsExpectedBeans() { + this.contextRunner + .withPropertyValues("spring.pulsar.producer.topic-name=my-topic", + "spring.pulsar.producer.cache.enabled=false") + .run((context) -> assertThat(context).getBean(DefaultPulsarProducerFactory.class) + .hasFieldOrPropertyWithValue("pulsarClient", context.getBean(PulsarClient.class)) + .hasFieldOrPropertyWithValue("topicResolver", context.getBean(TopicResolver.class))); + } + + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void whenHasUserDefinedCustomizersAppliesInCorrectOrder(boolean cachingEnabled) { + this.contextRunner + .withPropertyValues("spring.pulsar.producer.cache.enabled=" + cachingEnabled, + "spring.pulsar.producer.name=fromPropsCustomizer") + .withUserConfiguration(ProducerBuilderCustomizersConfig.class) + .run((context) -> { + DefaultPulsarProducerFactory producerFactory = context + .getBean(DefaultPulsarProducerFactory.class); + Customizers, ProducerBuilder> customizers = Customizers + .of(ProducerBuilder.class, ProducerBuilderCustomizer::customize); + assertThat(customizers.fromField(producerFactory, "defaultConfigCustomizers")).callsInOrder( + ProducerBuilder::producerName, "fromPropsCustomizer", "fromCustomizer1", "fromCustomizer2"); + }); + } + + @TestConfiguration(proxyBeanMethods = false) + static class ProducerBuilderCustomizersConfig { + + @Bean + @Order(200) + ProducerBuilderCustomizer customizerFoo() { + return (builder) -> builder.producerName("fromCustomizer2"); + } + + @Bean + @Order(100) + ProducerBuilderCustomizer customizerBar() { + return (builder) -> builder.producerName("fromCustomizer1"); + } + + } + + } + + @Nested + class TemplateTests { + + private final ApplicationContextRunner contextRunner = PulsarAutoConfigurationTests.this.contextRunner; + + @Test + @SuppressWarnings("unchecked") + void whenHasUserDefinedBeanDoesNotAutoConfigureBean() { + PulsarTemplate template = mock(PulsarTemplate.class); + this.contextRunner.withBean("customPulsarTemplate", PulsarTemplate.class, () -> template) + .run((context) -> assertThat(context).getBean(PulsarTemplate.class).isSameAs(template)); + } + + @Test + void injectsExpectedBeans() { + PulsarProducerFactory producerFactory = mock(PulsarProducerFactory.class); + SchemaResolver schemaResolver = mock(SchemaResolver.class); + TopicResolver topicResolver = mock(TopicResolver.class); + this.contextRunner + .withBean("customPulsarProducerFactory", PulsarProducerFactory.class, () -> producerFactory) + .withBean("schemaResolver", SchemaResolver.class, () -> schemaResolver) + .withBean("topicResolver", TopicResolver.class, () -> topicResolver) + .run((context) -> assertThat(context).getBean(PulsarTemplate.class) + .hasFieldOrPropertyWithValue("producerFactory", producerFactory) + .hasFieldOrPropertyWithValue("schemaResolver", schemaResolver) + .hasFieldOrPropertyWithValue("topicResolver", topicResolver)); + } + + @Test + void whenHasUseDefinedProducerInterceptorInjectsBean() { + ProducerInterceptor interceptor = mock(ProducerInterceptor.class); + this.contextRunner.withBean("customProducerInterceptor", ProducerInterceptor.class, () -> interceptor) + .run((context) -> { + PulsarTemplate pulsarTemplate = context.getBean(PulsarTemplate.class); + Customizers, ProducerBuilder> customizers = Customizers + .of(ProducerBuilder.class, ProducerBuilderCustomizer::customize); + assertThat(customizers.fromField(pulsarTemplate, "interceptorsCustomizers")) + .callsInOrder(ProducerBuilder::intercept, interceptor); + }); + } + + @Test + void whenHasUseDefinedProducerInterceptorsInjectsBeansInCorrectOrder() { + this.contextRunner.withUserConfiguration(InterceptorTestConfiguration.class).run((context) -> { + ProducerInterceptor interceptorFoo = context.getBean("interceptorFoo", ProducerInterceptor.class); + ProducerInterceptor interceptorBar = context.getBean("interceptorBar", ProducerInterceptor.class); + PulsarTemplate pulsarTemplate = context.getBean(PulsarTemplate.class); + Customizers, ProducerBuilder> customizers = Customizers + .of(ProducerBuilder.class, ProducerBuilderCustomizer::customize); + assertThat(customizers.fromField(pulsarTemplate, "interceptorsCustomizers")) + .callsInOrder(ProducerBuilder::intercept, interceptorBar, interceptorFoo); + }); + } + + @Test + void whenNoPropertiesEnablesObservation() { + this.contextRunner.run((context) -> assertThat(context).getBean(PulsarTemplate.class) + .hasFieldOrPropertyWithValue("observationEnabled", false)); + } + + @Test + void whenObservationsEnabledEnablesObservation() { + this.contextRunner.withPropertyValues("spring.pulsar.template.observations-enabled=true") + .run((context) -> assertThat(context).getBean(PulsarTemplate.class) + .hasFieldOrPropertyWithValue("observationEnabled", true)); + } + + @Test + void whenObservationsDisabledDoesNotEnableObservation() { + this.contextRunner.withPropertyValues("spring.pulsar.template.observations-enabled=false") + .run((context) -> assertThat(context).getBean(PulsarTemplate.class) + .hasFieldOrPropertyWithValue("observationEnabled", false)); + } + + @Test + void whenTransactionEnabledTrueEnablesTransactions() { + this.contextRunner.withPropertyValues("spring.pulsar.transaction.enabled=true") + .run((context) -> assertThat(context.getBean(PulsarTemplate.class).transactions().isEnabled()) + .isTrue()); + } + + @Configuration(proxyBeanMethods = false) + static class InterceptorTestConfiguration { + + @Bean + @Order(200) + ProducerInterceptor interceptorFoo() { + return mock(ProducerInterceptor.class); + } + + @Bean + @Order(100) + ProducerInterceptor interceptorBar() { + return mock(ProducerInterceptor.class); + } + + } + + } + + @Nested + class ConsumerFactoryTests { + + private final ApplicationContextRunner contextRunner = PulsarAutoConfigurationTests.this.contextRunner; + + @Test + @SuppressWarnings("unchecked") + void whenHasUserDefinedBeanDoesNotAutoConfigureBean() { + PulsarConsumerFactory consumerFactory = mock(PulsarConsumerFactory.class); + this.contextRunner + .withBean("customPulsarConsumerFactory", PulsarConsumerFactory.class, () -> consumerFactory) + .run((context) -> assertThat(context).getBean(PulsarConsumerFactory.class).isSameAs(consumerFactory)); + } + + @Test + void injectsExpectedBeans() { + this.contextRunner.run((context) -> assertThat(context).getBean(DefaultPulsarConsumerFactory.class) + .hasFieldOrPropertyWithValue("pulsarClient", context.getBean(PulsarClient.class))); + } + + @Test + void whenHasUserDefinedCustomizersAppliesInCorrectOrder() { + this.contextRunner.withPropertyValues("spring.pulsar.consumer.name=fromPropsCustomizer") + .withUserConfiguration(ConsumerBuilderCustomizersConfig.class) + .run((context) -> { + DefaultPulsarConsumerFactory consumerFactory = context + .getBean(DefaultPulsarConsumerFactory.class); + Customizers, ConsumerBuilder> customizers = Customizers + .of(ConsumerBuilder.class, ConsumerBuilderCustomizer::customize); + assertThat(customizers.fromField(consumerFactory, "defaultConfigCustomizers")).callsInOrder( + ConsumerBuilder::consumerName, "fromPropsCustomizer", "fromCustomizer1", "fromCustomizer2"); + }); + } + + @Test + void injectsExpectedBeanWithExplicitGenericType() { + this.contextRunner.withBean(ExplicitGenericTypeConfig.class) + .run((context) -> assertThat(context).getBean(ExplicitGenericTypeConfig.class) + .hasFieldOrPropertyWithValue("consumerFactory", context.getBean(PulsarConsumerFactory.class)) + .hasFieldOrPropertyWithValue("containerFactory", + context.getBean(ConcurrentPulsarListenerContainerFactory.class))); + } + + @TestConfiguration(proxyBeanMethods = false) + static class ConsumerBuilderCustomizersConfig { + + @Bean + @Order(200) + ConsumerBuilderCustomizer customizerFoo() { + return (builder) -> builder.consumerName("fromCustomizer2"); + } + + @Bean + @Order(100) + ConsumerBuilderCustomizer customizerBar() { + return (builder) -> builder.consumerName("fromCustomizer1"); + } + + } + + static class ExplicitGenericTypeConfig { + + @Autowired + PulsarConsumerFactory consumerFactory; + + @Autowired + ConcurrentPulsarListenerContainerFactory containerFactory; + + static class TestType { + + } + + } + + } + + @Nested + class ListenerTests { + + private final ApplicationContextRunner contextRunner = PulsarAutoConfigurationTests.this.contextRunner; + + @Test + void whenHasUserDefinedListenerContainerFactoryBeanDoesNotAutoConfigureBean() { + PulsarListenerContainerFactory listenerContainerFactory = mock(PulsarListenerContainerFactory.class); + this.contextRunner + .withBean("pulsarListenerContainerFactory", PulsarListenerContainerFactory.class, + () -> listenerContainerFactory) + .run((context) -> assertThat(context).getBean(PulsarListenerContainerFactory.class) + .isSameAs(listenerContainerFactory)); + } + + @Test + @SuppressWarnings("rawtypes") + void injectsExpectedBeans() { + PulsarConsumerFactory consumerFactory = mock(PulsarConsumerFactory.class); + SchemaResolver schemaResolver = mock(SchemaResolver.class); + TopicResolver topicResolver = mock(TopicResolver.class); + this.contextRunner.withBean("pulsarConsumerFactory", PulsarConsumerFactory.class, () -> consumerFactory) + .withBean("schemaResolver", SchemaResolver.class, () -> schemaResolver) + .withBean("topicResolver", TopicResolver.class, () -> topicResolver) + .run((context) -> assertThat(context).getBean(ConcurrentPulsarListenerContainerFactory.class) + .hasFieldOrPropertyWithValue("consumerFactory", consumerFactory) + .extracting(ConcurrentPulsarListenerContainerFactory::getContainerProperties) + .hasFieldOrPropertyWithValue("schemaResolver", schemaResolver) + .hasFieldOrPropertyWithValue("topicResolver", topicResolver)); + } + + @Test + @SuppressWarnings("unchecked") + void whenHasUserDefinedListenerAnnotationBeanPostProcessorBeanDoesNotAutoConfigureBean() { + PulsarListenerAnnotationBeanPostProcessor listenerAnnotationBeanPostProcessor = mock( + PulsarListenerAnnotationBeanPostProcessor.class); + this.contextRunner + .withBean("org.springframework.pulsar.config.internalPulsarListenerAnnotationProcessor", + PulsarListenerAnnotationBeanPostProcessor.class, () -> listenerAnnotationBeanPostProcessor) + .run((context) -> assertThat(context).getBean(PulsarListenerAnnotationBeanPostProcessor.class) + .isSameAs(listenerAnnotationBeanPostProcessor)); + } + + @Test + void whenHasCustomProperties() { + List properties = new ArrayList<>(); + properties.add("spring.pulsar.listener.schema-type=avro"); + this.contextRunner.withPropertyValues(properties.toArray(String[]::new)).run((context) -> { + ConcurrentPulsarListenerContainerFactory factory = context + .getBean(ConcurrentPulsarListenerContainerFactory.class); + assertThat(factory.getContainerProperties().getSchemaType()).isEqualTo(SchemaType.AVRO); + }); + } + + @Test + void whenNoPropertiesEnablesObservation() { + this.contextRunner + .run((context) -> assertThat(context).getBean(ConcurrentPulsarListenerContainerFactory.class) + .hasFieldOrPropertyWithValue("containerProperties.observationEnabled", false)); + } + + @Test + void whenObservationsEnabledEnablesObservation() { + this.contextRunner.withPropertyValues("spring.pulsar.listener.observation-enabled=true") + .run((context) -> assertThat(context).getBean(ConcurrentPulsarListenerContainerFactory.class) + .hasFieldOrPropertyWithValue("containerProperties.observationEnabled", true)); + } + + @Test + void whenObservationsDisabledDoesNotEnableObservation() { + this.contextRunner.withPropertyValues("spring.pulsar.listener.observation-enabled=false") + .run((context) -> assertThat(context).getBean(ConcurrentPulsarListenerContainerFactory.class) + .hasFieldOrPropertyWithValue("containerProperties.observationEnabled", false)); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void whenVirtualThreadsAreEnabledOnJava21AndLaterListenerContainerShouldUseVirtualThreads() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> { + ConcurrentPulsarListenerContainerFactory factory = context + .getBean(ConcurrentPulsarListenerContainerFactory.class); + assertThat(factory.getContainerProperties().getConsumerTaskExecutor()) + .isInstanceOf(VirtualThreadTaskExecutor.class); + Object taskExecutor = factory.getContainerProperties().getConsumerTaskExecutor(); + Object virtualThread = ReflectionTestUtils.getField(taskExecutor, "virtualThreadFactory"); + Thread threadCreated = ((ThreadFactory) virtualThread).newThread(mock(Runnable.class)); + assertThat(threadCreated.getName()).containsPattern("pulsar-consumer-[0-9]+"); + }); + } + + @Test + @EnabledForJreRange(max = JRE.JAVA_20) + void whenVirtualThreadsAreEnabledOnJava20AndEarlierListenerContainerShouldNotUseVirtualThreads() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> { + ConcurrentPulsarListenerContainerFactory factory = context + .getBean(ConcurrentPulsarListenerContainerFactory.class); + assertThat(factory.getContainerProperties().getConsumerTaskExecutor()).isNull(); + }); + } + + @Test + void whenTransactionEnabledTrueListenerContainerShouldUseTransactions() { + this.contextRunner.withPropertyValues("spring.pulsar.transaction.enabled=true").run((context) -> { + ConcurrentPulsarListenerContainerFactory factory = context + .getBean(ConcurrentPulsarListenerContainerFactory.class); + TransactionSettings transactions = factory.getContainerProperties().transactions(); + assertThat(transactions.isEnabled()).isTrue(); + assertThat(transactions.getTransactionManager()).isNotNull(); + }); + } + + @Test + void whenTransactionEnabledFalseListenerContainerShouldNotUseTransactions() { + this.contextRunner.withPropertyValues("spring.pulsar.transaction.enabled=false").run((context) -> { + ConcurrentPulsarListenerContainerFactory factory = context + .getBean(ConcurrentPulsarListenerContainerFactory.class); + TransactionSettings transactions = factory.getContainerProperties().transactions(); + assertThat(transactions.isEnabled()).isFalse(); + assertThat(transactions.getTransactionManager()).isNull(); + }); + } + + } + + @Nested + class ReaderFactoryTests { + + private final ApplicationContextRunner contextRunner = PulsarAutoConfigurationTests.this.contextRunner; + + @Test + @SuppressWarnings("unchecked") + void whenHasUserDefinedBeanDoesNotAutoConfigureBean() { + PulsarReaderFactory readerFactory = mock(PulsarReaderFactory.class); + this.contextRunner.withBean("customPulsarReaderFactory", PulsarReaderFactory.class, () -> readerFactory) + .run((context) -> assertThat(context).getBean(PulsarReaderFactory.class).isSameAs(readerFactory)); + } + + @Test + void injectsExpectedBeans() { + this.contextRunner.run((context) -> assertThat(context).getBean(DefaultPulsarReaderFactory.class) + .hasFieldOrPropertyWithValue("pulsarClient", context.getBean(PulsarClient.class))); + } + + @Test + void whenHasUserDefinedCustomizersAppliesInCorrectOrder() { + this.contextRunner.withPropertyValues("spring.pulsar.reader.name=fromPropsCustomizer") + .withUserConfiguration(ReaderBuilderCustomizersConfig.class) + .run((context) -> { + DefaultPulsarReaderFactory readerFactory = context.getBean(DefaultPulsarReaderFactory.class); + Customizers, ReaderBuilder> customizers = Customizers + .of(ReaderBuilder.class, ReaderBuilderCustomizer::customize); + assertThat(customizers.fromField(readerFactory, "defaultConfigCustomizers")).callsInOrder( + ReaderBuilder::readerName, "fromPropsCustomizer", "fromCustomizer1", "fromCustomizer2"); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void whenVirtualThreadsAreEnabledOnJava21AndLaterReaderShouldUseVirtualThreads() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> { + DefaultPulsarReaderContainerFactory factory = context + .getBean(DefaultPulsarReaderContainerFactory.class); + assertThat(factory.getContainerProperties().getReaderTaskExecutor()) + .isInstanceOf(VirtualThreadTaskExecutor.class); + Object taskExecutor = factory.getContainerProperties().getReaderTaskExecutor(); + Object virtualThread = ReflectionTestUtils.getField(taskExecutor, "virtualThreadFactory"); + Thread threadCreated = ((ThreadFactory) virtualThread).newThread(mock(Runnable.class)); + assertThat(threadCreated.getName()).containsPattern("pulsar-reader-[0-9]+"); + }); + } + + @Test + @EnabledForJreRange(max = JRE.JAVA_20) + void whenVirtualThreadsAreEnabledOnJava20AndEarlierReaderShouldNotUseVirtualThreads() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> { + DefaultPulsarReaderContainerFactory factory = context + .getBean(DefaultPulsarReaderContainerFactory.class); + assertThat(factory.getContainerProperties().getReaderTaskExecutor()).isNull(); + }); + } + + @TestConfiguration(proxyBeanMethods = false) + static class ReaderBuilderCustomizersConfig { + + @Bean + @Order(200) + ReaderBuilderCustomizer customizerFoo() { + return (builder) -> builder.readerName("fromCustomizer2"); + } + + @Bean + @Order(100) + ReaderBuilderCustomizer customizerBar() { + return (builder) -> builder.readerName("fromCustomizer1"); + } + + } + + } + + @Nested + class TransactionManagerTests { + + private final ApplicationContextRunner contextRunner = PulsarAutoConfigurationTests.this.contextRunner; + + @Test + @SuppressWarnings("unchecked") + void whenUserHasDefinedATransactionManagerTheAutoConfigurationBacksOff() { + PulsarAwareTransactionManager txnMgr = mock(PulsarAwareTransactionManager.class); + this.contextRunner.withBean("customTransactionManager", PulsarAwareTransactionManager.class, () -> txnMgr) + .run((context) -> assertThat(context).getBean(PulsarAwareTransactionManager.class).isSameAs(txnMgr)); + } + + @Test + void whenNoPropertiesAreSetTransactionManagerShouldNotBeDefined() { + this.contextRunner + .run((context) -> assertThat(context).doesNotHaveBean(PulsarAwareTransactionManager.class)); + } + + @Test + void whenTransactionEnabledFalseTransactionManagerIsNotAutoConfigured() { + this.contextRunner.withPropertyValues("spring.pulsar.transaction.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(PulsarAwareTransactionManager.class)); + } + + @Test + void whenTransactionEnabledTrueTransactionManagerIsAutoConfigured() { + this.contextRunner.withPropertyValues("spring.pulsar.transaction.enabled=true") + .run((context) -> assertThat(context).hasSingleBean(PulsarAwareTransactionManager.class)); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarConfigurationTests.java new file mode 100644 index 000000000000..ef775cab6336 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarConfigurationTests.java @@ -0,0 +1,374 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import org.apache.pulsar.client.admin.PulsarAdminBuilder; +import org.apache.pulsar.client.api.ClientBuilder; +import org.apache.pulsar.client.api.PulsarClient; +import org.apache.pulsar.client.api.Schema; +import org.apache.pulsar.client.impl.AutoClusterFailover; +import org.apache.pulsar.common.schema.KeyValueEncodingType; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.assertj.core.api.InstanceOfAssertFactory; +import org.assertj.core.api.MapAssert; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.core.annotation.Order; +import org.springframework.pulsar.core.DefaultPulsarClientFactory; +import org.springframework.pulsar.core.DefaultSchemaResolver; +import org.springframework.pulsar.core.DefaultTopicResolver; +import org.springframework.pulsar.core.PulsarAdminBuilderCustomizer; +import org.springframework.pulsar.core.PulsarAdministration; +import org.springframework.pulsar.core.PulsarClientBuilderCustomizer; +import org.springframework.pulsar.core.PulsarClientFactory; +import org.springframework.pulsar.core.SchemaResolver; +import org.springframework.pulsar.core.SchemaResolver.SchemaResolverCustomizer; +import org.springframework.pulsar.core.TopicResolver; +import org.springframework.pulsar.function.PulsarFunctionAdministration; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link PulsarConfiguration}. + * + * @author Chris Bono + * @author Alexander Preuß + * @author Soby Chacko + * @author Phillip Webb + * @author Swamy Mavuri + */ +class PulsarConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(PulsarConfiguration.class)) + .withBean(PulsarClient.class, () -> mock(PulsarClient.class)); + + @Test + void whenHasUserDefinedConnectionDetailsBeanDoesNotAutoConfigureBean() { + PulsarConnectionDetails customConnectionDetails = mock(PulsarConnectionDetails.class); + this.contextRunner + .withBean("customPulsarConnectionDetails", PulsarConnectionDetails.class, () -> customConnectionDetails) + .run((context) -> assertThat(context).getBean(PulsarConnectionDetails.class) + .isSameAs(customConnectionDetails)); + } + + @Nested + class ClientTests { + + @Test + void whenHasUserDefinedClientFactoryBeanDoesNotAutoConfigureBean() { + PulsarClientFactory customFactory = mock(PulsarClientFactory.class); + new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(PulsarConfiguration.class)) + .withBean("customPulsarClientFactory", PulsarClientFactory.class, () -> customFactory) + .run((context) -> assertThat(context).getBean(PulsarClientFactory.class).isSameAs(customFactory)); + } + + @Test + void whenHasUserDefinedClientBeanDoesNotAutoConfigureBean() { + PulsarClient customClient = mock(PulsarClient.class); + new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(PulsarConfiguration.class)) + .withBean("customPulsarClient", PulsarClient.class, () -> customClient) + .run((context) -> assertThat(context).getBean(PulsarClient.class).isSameAs(customClient)); + } + + @Test + void whenHasUserDefinedCustomizersAppliesInCorrectOrder() { + PulsarConnectionDetails connectionDetails = mock(PulsarConnectionDetails.class); + given(connectionDetails.getBrokerUrl()).willReturn("connectiondetails"); + PulsarConfigurationTests.this.contextRunner + .withUserConfiguration(PulsarClientBuilderCustomizersConfig.class) + .withBean(PulsarConnectionDetails.class, () -> connectionDetails) + .withPropertyValues("spring.pulsar.client.service-url=properties") + .run((context) -> { + DefaultPulsarClientFactory clientFactory = context.getBean(DefaultPulsarClientFactory.class); + Customizers customizers = Customizers + .of(ClientBuilder.class, PulsarClientBuilderCustomizer::customize); + assertThat(customizers.fromField(clientFactory, "customizer")).callsInOrder( + ClientBuilder::serviceUrl, "connectiondetails", "fromCustomizer1", "fromCustomizer2"); + }); + } + + @Test + void whenHasUserDefinedFailoverPropertiesAddsToClient() { + PulsarConnectionDetails connectionDetails = mock(PulsarConnectionDetails.class); + given(connectionDetails.getBrokerUrl()).willReturn("connectiondetails"); + PulsarConfigurationTests.this.contextRunner.withBean(PulsarConnectionDetails.class, () -> connectionDetails) + .withPropertyValues("spring.pulsar.client.service-url=properties", + "spring.pulsar.client.failover.backup-clusters[0].service-url=backup-cluster-1", + "spring.pulsar.client.failover.delay=15s", + "spring.pulsar.client.failover.switch-back-delay=30s", + "spring.pulsar.client.failover.check-interval=5s", + "spring.pulsar.client.failover.backup-clusters[1].service-url=backup-cluster-2", + "spring.pulsar.client.failover.backup-clusters[1].authentication.plugin-class-name=org.springframework.boot.autoconfigure.pulsar.MockAuthentication", + "spring.pulsar.client.failover.backup-clusters[1].authentication.param.token=1234") + .run((context) -> { + DefaultPulsarClientFactory clientFactory = context.getBean(DefaultPulsarClientFactory.class); + PulsarProperties pulsarProperties = context.getBean(PulsarProperties.class); + ClientBuilder target = mock(ClientBuilder.class); + BiConsumer customizeAction = PulsarClientBuilderCustomizer::customize; + PulsarClientBuilderCustomizer pulsarClientBuilderCustomizer = (PulsarClientBuilderCustomizer) ReflectionTestUtils + .getField(clientFactory, "customizer"); + customizeAction.accept(pulsarClientBuilderCustomizer, target); + InOrder ordered = inOrder(target); + ordered.verify(target).serviceUrlProvider(Mockito.any(AutoClusterFailover.class)); + assertThat(pulsarProperties.getClient().getFailover().getDelay()).isEqualTo(Duration.ofSeconds(15)); + assertThat(pulsarProperties.getClient().getFailover().getSwitchBackDelay()) + .isEqualTo(Duration.ofSeconds(30)); + assertThat(pulsarProperties.getClient().getFailover().getCheckInterval()) + .isEqualTo(Duration.ofSeconds(5)); + assertThat(pulsarProperties.getClient().getFailover().getBackupClusters().size()).isEqualTo(2); + }); + } + + @TestConfiguration(proxyBeanMethods = false) + static class PulsarClientBuilderCustomizersConfig { + + @Bean + @Order(200) + PulsarClientBuilderCustomizer customizerFoo() { + return (builder) -> builder.serviceUrl("fromCustomizer2"); + } + + @Bean + @Order(100) + PulsarClientBuilderCustomizer customizerBar() { + return (builder) -> builder.serviceUrl("fromCustomizer1"); + } + + } + + } + + @Nested + class AdministrationTests { + + private final ApplicationContextRunner contextRunner = PulsarConfigurationTests.this.contextRunner; + + @Test + void whenHasUserDefinedBeanDoesNotAutoConfigureBean() { + PulsarAdministration pulsarAdministration = mock(PulsarAdministration.class); + this.contextRunner + .withBean("customPulsarAdministration", PulsarAdministration.class, () -> pulsarAdministration) + .run((context) -> assertThat(context).getBean(PulsarAdministration.class) + .isSameAs(pulsarAdministration)); + } + + @Test + void whenHasUserDefinedCustomizersAppliesInCorrectOrder() { + PulsarConnectionDetails connectionDetails = mock(PulsarConnectionDetails.class); + given(connectionDetails.getAdminUrl()).willReturn("connectiondetails"); + this.contextRunner.withUserConfiguration(PulsarAdminBuilderCustomizersConfig.class) + .withBean(PulsarConnectionDetails.class, () -> connectionDetails) + .withPropertyValues("spring.pulsar.admin.service-url=property") + .run((context) -> { + PulsarAdministration pulsarAdmin = context.getBean(PulsarAdministration.class); + Customizers customizers = Customizers + .of(PulsarAdminBuilder.class, PulsarAdminBuilderCustomizer::customize); + assertThat(customizers.fromField(pulsarAdmin, "adminCustomizers")).callsInOrder( + PulsarAdminBuilder::serviceHttpUrl, "connectiondetails", "fromCustomizer1", + "fromCustomizer2"); + }); + } + + @TestConfiguration(proxyBeanMethods = false) + static class PulsarAdminBuilderCustomizersConfig { + + @Bean + @Order(200) + PulsarAdminBuilderCustomizer customizerFoo() { + return (builder) -> builder.serviceHttpUrl("fromCustomizer2"); + } + + @Bean + @Order(100) + PulsarAdminBuilderCustomizer customizerBar() { + return (builder) -> builder.serviceHttpUrl("fromCustomizer1"); + } + + } + + } + + @Nested + class SchemaResolverTests { + + @SuppressWarnings("rawtypes") + private static final InstanceOfAssertFactory> CLASS_SCHEMA_MAP = InstanceOfAssertFactories + .map(Class.class, Schema.class); + + private final ApplicationContextRunner contextRunner = PulsarConfigurationTests.this.contextRunner; + + @Test + void whenHasUserDefinedBeanDoesNotAutoConfigureBean() { + SchemaResolver schemaResolver = mock(SchemaResolver.class); + this.contextRunner.withBean("customSchemaResolver", SchemaResolver.class, () -> schemaResolver) + .run((context) -> assertThat(context).getBean(SchemaResolver.class).isSameAs(schemaResolver)); + } + + @Test + void whenHasUserDefinedSchemaResolverCustomizer() { + SchemaResolverCustomizer customizer = (schemaResolver) -> schemaResolver + .addCustomSchemaMapping(TestRecord.class, Schema.STRING); + this.contextRunner.withBean("schemaResolverCustomizer", SchemaResolverCustomizer.class, () -> customizer) + .run((context) -> assertThat(context).getBean(DefaultSchemaResolver.class) + .extracting(DefaultSchemaResolver::getCustomSchemaMappings, InstanceOfAssertFactories.MAP) + .containsEntry(TestRecord.class, Schema.STRING)); + } + + @Test + void whenHasDefaultsTypeMappingForPrimitiveAddsToSchemaResolver() { + List properties = new ArrayList<>(); + properties.add("spring.pulsar.defaults.type-mappings[0].message-type=" + TestRecord.CLASS_NAME); + properties.add("spring.pulsar.defaults.type-mappings[0].schema-info.schema-type=STRING"); + this.contextRunner.withPropertyValues(properties.toArray(String[]::new)) + .run((context) -> assertThat(context).getBean(DefaultSchemaResolver.class) + .extracting(DefaultSchemaResolver::getCustomSchemaMappings, InstanceOfAssertFactories.MAP) + .containsOnly(entry(TestRecord.class, Schema.STRING))); + } + + @Test + void whenHasDefaultsTypeMappingForStructAddsToSchemaResolver() { + List properties = new ArrayList<>(); + properties.add("spring.pulsar.defaults.type-mappings[0].message-type=" + TestRecord.CLASS_NAME); + properties.add("spring.pulsar.defaults.type-mappings[0].schema-info.schema-type=JSON"); + Schema expectedSchema = Schema.JSON(TestRecord.class); + this.contextRunner.withPropertyValues(properties.toArray(String[]::new)) + .run((context) -> assertThat(context).getBean(DefaultSchemaResolver.class) + .extracting(DefaultSchemaResolver::getCustomSchemaMappings, CLASS_SCHEMA_MAP) + .hasEntrySatisfying(TestRecord.class, schemaEqualTo(expectedSchema))); + } + + @Test + void whenHasDefaultsTypeMappingForKeyValueAddsToSchemaResolver() { + List properties = new ArrayList<>(); + properties.add("spring.pulsar.defaults.type-mappings[0].message-type=" + TestRecord.CLASS_NAME); + properties.add("spring.pulsar.defaults.type-mappings[0].schema-info.schema-type=key-value"); + properties.add("spring.pulsar.defaults.type-mappings[0].schema-info.message-key-type=java.lang.String"); + Schema expectedSchema = Schema.KeyValue(Schema.STRING, Schema.JSON(TestRecord.class), + KeyValueEncodingType.INLINE); + this.contextRunner.withPropertyValues(properties.toArray(String[]::new)) + .run((context) -> assertThat(context).getBean(DefaultSchemaResolver.class) + .extracting(DefaultSchemaResolver::getCustomSchemaMappings, CLASS_SCHEMA_MAP) + .hasEntrySatisfying(TestRecord.class, schemaEqualTo(expectedSchema))); + } + + @SuppressWarnings("rawtypes") + private Consumer schemaEqualTo(Schema expected) { + return (actual) -> assertThat(actual.getSchemaInfo()).isEqualTo(expected.getSchemaInfo()); + } + + } + + @Nested + class TopicResolverTests { + + private final ApplicationContextRunner contextRunner = PulsarConfigurationTests.this.contextRunner; + + @Test + void whenHasUserDefinedBeanDoesNotAutoConfigureBean() { + TopicResolver topicResolver = mock(TopicResolver.class); + this.contextRunner.withBean("customTopicResolver", TopicResolver.class, () -> topicResolver) + .run((context) -> assertThat(context).getBean(TopicResolver.class).isSameAs(topicResolver)); + } + + @Test + void whenHasDefaultsTypeMappingAddsToSchemaResolver() { + List properties = new ArrayList<>(); + properties.add("spring.pulsar.defaults.type-mappings[0].message-type=" + TestRecord.CLASS_NAME); + properties.add("spring.pulsar.defaults.type-mappings[0].topic-name=foo-topic"); + properties.add("spring.pulsar.defaults.type-mappings[1].message-type=java.lang.String"); + properties.add("spring.pulsar.defaults.type-mappings[1].topic-name=string-topic"); + this.contextRunner.withPropertyValues(properties.toArray(String[]::new)) + .run((context) -> assertThat(context).getBean(TopicResolver.class) + .asInstanceOf(InstanceOfAssertFactories.type(DefaultTopicResolver.class)) + .extracting(DefaultTopicResolver::getCustomTopicMappings, InstanceOfAssertFactories.MAP) + .containsOnly(entry(TestRecord.class, "foo-topic"), entry(String.class, "string-topic"))); + } + + } + + @Nested + class FunctionAdministrationTests { + + private final ApplicationContextRunner contextRunner = PulsarConfigurationTests.this.contextRunner; + + @Test + void whenNoPropertiesAddsFunctionAdministrationBean() { + this.contextRunner.run((context) -> assertThat(context).getBean(PulsarFunctionAdministration.class) + .hasFieldOrPropertyWithValue("failFast", Boolean.TRUE) + .hasFieldOrPropertyWithValue("propagateFailures", Boolean.TRUE) + .hasFieldOrPropertyWithValue("propagateStopFailures", Boolean.FALSE) + .hasNoNullFieldsOrProperties() // ensures object providers set + .extracting("pulsarAdministration") + .isSameAs(context.getBean(PulsarAdministration.class))); + } + + @Test + void whenHasFunctionPropertiesAppliesPropertiesToBean() { + List properties = new ArrayList<>(); + properties.add("spring.pulsar.function.fail-fast=false"); + properties.add("spring.pulsar.function.propagate-failures=false"); + properties.add("spring.pulsar.function.propagate-stop-failures=true"); + this.contextRunner.withPropertyValues(properties.toArray(String[]::new)) + .run((context) -> assertThat(context).getBean(PulsarFunctionAdministration.class) + .hasFieldOrPropertyWithValue("failFast", Boolean.FALSE) + .hasFieldOrPropertyWithValue("propagateFailures", Boolean.FALSE) + .hasFieldOrPropertyWithValue("propagateStopFailures", Boolean.TRUE)); + } + + @Test + void whenHasFunctionDisabledPropertyDoesNotCreateBean() { + this.contextRunner.withPropertyValues("spring.pulsar.function.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(PulsarFunctionAdministration.class)); + } + + @Test + void whenHasCustomFunctionAdministrationBean() { + PulsarFunctionAdministration functionAdministration = mock(PulsarFunctionAdministration.class); + this.contextRunner.withBean(PulsarFunctionAdministration.class, () -> functionAdministration) + .run((context) -> assertThat(context).getBean(PulsarFunctionAdministration.class) + .isSameAs(functionAdministration)); + } + + } + + record TestRecord() { + + private static final String CLASS_NAME = TestRecord.class.getName(); + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarPropertiesMapperTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarPropertiesMapperTests.java new file mode 100644 index 000000000000..f23584aab0ab --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarPropertiesMapperTests.java @@ -0,0 +1,289 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +import org.apache.pulsar.client.admin.PulsarAdminBuilder; +import org.apache.pulsar.client.api.AutoClusterFailoverBuilder.FailoverPolicy; +import org.apache.pulsar.client.api.ClientBuilder; +import org.apache.pulsar.client.api.CompressionType; +import org.apache.pulsar.client.api.ConsumerBuilder; +import org.apache.pulsar.client.api.DeadLetterPolicy; +import org.apache.pulsar.client.api.HashingScheme; +import org.apache.pulsar.client.api.MessageRoutingMode; +import org.apache.pulsar.client.api.ProducerAccessMode; +import org.apache.pulsar.client.api.ProducerBuilder; +import org.apache.pulsar.client.api.PulsarClientException.UnsupportedAuthenticationException; +import org.apache.pulsar.client.api.ReaderBuilder; +import org.apache.pulsar.client.api.SubscriptionType; +import org.apache.pulsar.client.impl.AutoClusterFailover; +import org.apache.pulsar.common.schema.SchemaType; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.pulsar.PulsarProperties.Consumer; +import org.springframework.boot.autoconfigure.pulsar.PulsarProperties.Failover.BackupCluster; +import org.springframework.pulsar.core.PulsarProducerFactory; +import org.springframework.pulsar.core.PulsarTemplate; +import org.springframework.pulsar.listener.PulsarContainerProperties; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; + +/** + * Tests for {@link PulsarPropertiesMapper}. + * + * @author Chris Bono + * @author Phillip Webb + * @author Swamy Mavuri + */ +class PulsarPropertiesMapperTests { + + @Test + void customizeClientBuilderWhenHasNoAuthentication() { + PulsarProperties properties = new PulsarProperties(); + properties.getClient().setServiceUrl("https://example.com"); + properties.getClient().setConnectionTimeout(Duration.ofSeconds(1)); + properties.getClient().setOperationTimeout(Duration.ofSeconds(2)); + properties.getClient().setLookupTimeout(Duration.ofSeconds(3)); + ClientBuilder builder = mock(ClientBuilder.class); + new PulsarPropertiesMapper(properties).customizeClientBuilder(builder, + new PropertiesPulsarConnectionDetails(properties)); + then(builder).should().serviceUrl("https://example.com"); + then(builder).should().connectionTimeout(1000, TimeUnit.MILLISECONDS); + then(builder).should().operationTimeout(2000, TimeUnit.MILLISECONDS); + then(builder).should().lookupTimeout(3000, TimeUnit.MILLISECONDS); + } + + @Test + void customizeClientBuilderWhenHasAuthentication() throws UnsupportedAuthenticationException { + PulsarProperties properties = new PulsarProperties(); + Map params = Map.of("simpleParam", "foo", "complexParam", + "{\n\t\"k1\" : \"v1\",\n\t\"k2\":\"v2\"\n}"); + String authParamString = "{\"complexParam\":\"{\\n\\t\\\"k1\\\" : \\\"v1\\\",\\n\\t\\\"k2\\\":\\\"v2\\\"\\n}\"" + + ",\"simpleParam\":\"foo\"}"; + properties.getClient().getAuthentication().setPluginClassName("myclass"); + properties.getClient().getAuthentication().setParam(params); + ClientBuilder builder = mock(ClientBuilder.class); + new PulsarPropertiesMapper(properties).customizeClientBuilder(builder, + new PropertiesPulsarConnectionDetails(properties)); + then(builder).should().authentication("myclass", authParamString); + } + + @Test + void customizeClientBuilderWhenTransactionEnabled() { + PulsarProperties properties = new PulsarProperties(); + properties.getTransaction().setEnabled(true); + ClientBuilder builder = mock(ClientBuilder.class); + new PulsarPropertiesMapper(properties).customizeClientBuilder(builder, + new PropertiesPulsarConnectionDetails(properties)); + then(builder).should().enableTransaction(true); + } + + @Test + void customizeClientBuilderWhenTransactionDisabled() { + PulsarProperties properties = new PulsarProperties(); + properties.getTransaction().setEnabled(false); + ClientBuilder builder = mock(ClientBuilder.class); + new PulsarPropertiesMapper(properties).customizeClientBuilder(builder, + new PropertiesPulsarConnectionDetails(properties)); + then(builder).should(never()).enableTransaction(anyBoolean()); + } + + @Test + void customizeClientBuilderWhenHasConnectionDetails() { + PulsarProperties properties = new PulsarProperties(); + properties.getClient().setServiceUrl("https://ignored.example.com"); + ClientBuilder builder = mock(ClientBuilder.class); + PulsarConnectionDetails connectionDetails = mock(PulsarConnectionDetails.class); + given(connectionDetails.getBrokerUrl()).willReturn("https://used.example.com"); + new PulsarPropertiesMapper(properties).customizeClientBuilder(builder, connectionDetails); + then(builder).should().serviceUrl("https://used.example.com"); + } + + @Test + void customizeClientBuilderWhenHasFailover() { + BackupCluster backupCluster1 = new BackupCluster(); + backupCluster1.setServiceUrl("backup-cluster-1"); + Map params = Map.of("param", "name"); + backupCluster1.getAuthentication() + .setPluginClassName("org.springframework.boot.autoconfigure.pulsar.MockAuthentication"); + backupCluster1.getAuthentication().setParam(params); + BackupCluster backupCluster2 = new BackupCluster(); + backupCluster2.setServiceUrl("backup-cluster-2"); + PulsarProperties properties = new PulsarProperties(); + properties.getClient().setServiceUrl("https://used.example.com"); + properties.getClient().getFailover().setPolicy(FailoverPolicy.ORDER); + properties.getClient().getFailover().setCheckInterval(Duration.ofSeconds(5)); + properties.getClient().getFailover().setDelay(Duration.ofSeconds(30)); + properties.getClient().getFailover().setSwitchBackDelay(Duration.ofSeconds(30)); + properties.getClient().getFailover().setBackupClusters(List.of(backupCluster1, backupCluster2)); + PulsarConnectionDetails connectionDetails = mock(PulsarConnectionDetails.class); + given(connectionDetails.getBrokerUrl()).willReturn("https://used.example.com"); + ClientBuilder builder = mock(ClientBuilder.class); + new PulsarPropertiesMapper(properties).customizeClientBuilder(builder, + new PropertiesPulsarConnectionDetails(properties)); + then(builder).should().serviceUrlProvider(any(AutoClusterFailover.class)); + } + + @Test + void customizeAdminBuilderWhenHasNoAuthentication() { + PulsarProperties properties = new PulsarProperties(); + properties.getAdmin().setServiceUrl("https://example.com"); + properties.getAdmin().setConnectionTimeout(Duration.ofSeconds(1)); + properties.getAdmin().setReadTimeout(Duration.ofSeconds(2)); + properties.getAdmin().setRequestTimeout(Duration.ofSeconds(3)); + PulsarAdminBuilder builder = mock(PulsarAdminBuilder.class); + new PulsarPropertiesMapper(properties).customizeAdminBuilder(builder, + new PropertiesPulsarConnectionDetails(properties)); + then(builder).should().serviceHttpUrl("https://example.com"); + then(builder).should().connectionTimeout(1000, TimeUnit.MILLISECONDS); + then(builder).should().readTimeout(2000, TimeUnit.MILLISECONDS); + then(builder).should().requestTimeout(3000, TimeUnit.MILLISECONDS); + } + + @Test + void customizeAdminBuilderWhenHasAuthentication() throws UnsupportedAuthenticationException { + PulsarProperties properties = new PulsarProperties(); + Map params = Map.of("simpleParam", "foo", "complexParam", + "{\n\t\"k1\" : \"v1\",\n\t\"k2\":\"v2\"\n}"); + String authParamString = "{\"complexParam\":\"{\\n\\t\\\"k1\\\" : \\\"v1\\\",\\n\\t\\\"k2\\\":\\\"v2\\\"\\n}\"" + + ",\"simpleParam\":\"foo\"}"; + properties.getAdmin().getAuthentication().setPluginClassName("myclass"); + properties.getAdmin().getAuthentication().setParam(params); + PulsarAdminBuilder builder = mock(PulsarAdminBuilder.class); + new PulsarPropertiesMapper(properties).customizeAdminBuilder(builder, + new PropertiesPulsarConnectionDetails(properties)); + then(builder).should().authentication("myclass", authParamString); + } + + @Test + void customizeAdminBuilderWhenHasConnectionDetails() { + PulsarProperties properties = new PulsarProperties(); + properties.getAdmin().setServiceUrl("https://ignored.example.com"); + PulsarAdminBuilder builder = mock(PulsarAdminBuilder.class); + PulsarConnectionDetails connectionDetails = mock(PulsarConnectionDetails.class); + given(connectionDetails.getAdminUrl()).willReturn("https://used.example.com"); + new PulsarPropertiesMapper(properties).customizeAdminBuilder(builder, connectionDetails); + then(builder).should().serviceHttpUrl("https://used.example.com"); + } + + @Test + @SuppressWarnings("unchecked") + void customizeProducerBuilder() { + PulsarProperties properties = new PulsarProperties(); + properties.getProducer().setName("name"); + properties.getProducer().setTopicName("topicname"); + properties.getProducer().setSendTimeout(Duration.ofSeconds(1)); + properties.getProducer().setMessageRoutingMode(MessageRoutingMode.RoundRobinPartition); + properties.getProducer().setHashingScheme(HashingScheme.JavaStringHash); + properties.getProducer().setBatchingEnabled(false); + properties.getProducer().setChunkingEnabled(true); + properties.getProducer().setCompressionType(CompressionType.SNAPPY); + properties.getProducer().setAccessMode(ProducerAccessMode.Exclusive); + ProducerBuilder builder = mock(ProducerBuilder.class); + new PulsarPropertiesMapper(properties).customizeProducerBuilder(builder); + then(builder).should().producerName("name"); + then(builder).should().topic("topicname"); + then(builder).should().sendTimeout(1000, TimeUnit.MILLISECONDS); + then(builder).should().messageRoutingMode(MessageRoutingMode.RoundRobinPartition); + then(builder).should().hashingScheme(HashingScheme.JavaStringHash); + then(builder).should().enableBatching(false); + then(builder).should().enableChunking(true); + then(builder).should().compressionType(CompressionType.SNAPPY); + then(builder).should().accessMode(ProducerAccessMode.Exclusive); + } + + @Test + @SuppressWarnings("unchecked") + void customizeTemplate() { + PulsarProperties properties = new PulsarProperties(); + properties.getTransaction().setEnabled(true); + PulsarTemplate template = new PulsarTemplate<>(mock(PulsarProducerFactory.class)); + new PulsarPropertiesMapper(properties).customizeTemplate(template); + assertThat(template.transactions().isEnabled()).isTrue(); + } + + @Test + @SuppressWarnings("unchecked") + void customizeConsumerBuilder() { + PulsarProperties properties = new PulsarProperties(); + List topics = List.of("mytopic"); + Pattern topisPattern = Pattern.compile("my-pattern"); + properties.getConsumer().setName("name"); + properties.getConsumer().setTopics(topics); + properties.getConsumer().setTopicsPattern(topisPattern); + properties.getConsumer().setPriorityLevel(123); + properties.getConsumer().setReadCompacted(true); + Consumer.DeadLetterPolicy deadLetterPolicy = new Consumer.DeadLetterPolicy(); + deadLetterPolicy.setDeadLetterTopic("my-dlt"); + deadLetterPolicy.setMaxRedeliverCount(1); + properties.getConsumer().setDeadLetterPolicy(deadLetterPolicy); + ConsumerBuilder builder = mock(ConsumerBuilder.class); + new PulsarPropertiesMapper(properties).customizeConsumerBuilder(builder); + then(builder).should().consumerName("name"); + then(builder).should().topics(topics); + then(builder).should().topicsPattern(topisPattern); + then(builder).should().priorityLevel(123); + then(builder).should().readCompacted(true); + then(builder).should().deadLetterPolicy(new DeadLetterPolicy(1, null, "my-dlt", null)); + } + + @Test + void customizeContainerProperties() { + PulsarProperties properties = new PulsarProperties(); + properties.getConsumer().getSubscription().setType(SubscriptionType.Shared); + properties.getListener().setSchemaType(SchemaType.AVRO); + properties.getListener().setObservationEnabled(true); + properties.getTransaction().setEnabled(true); + PulsarContainerProperties containerProperties = new PulsarContainerProperties("my-topic-pattern"); + new PulsarPropertiesMapper(properties).customizeContainerProperties(containerProperties); + assertThat(containerProperties.getSubscriptionType()).isEqualTo(SubscriptionType.Shared); + assertThat(containerProperties.getSchemaType()).isEqualTo(SchemaType.AVRO); + assertThat(containerProperties.isObservationEnabled()).isTrue(); + assertThat(containerProperties.transactions().isEnabled()).isTrue(); + } + + @Test + @SuppressWarnings("unchecked") + void customizeReaderBuilder() { + PulsarProperties properties = new PulsarProperties(); + List topics = List.of("mytopic"); + properties.getReader().setName("name"); + properties.getReader().setTopics(topics); + properties.getReader().setSubscriptionName("subname"); + properties.getReader().setSubscriptionRolePrefix("subroleprefix"); + properties.getReader().setReadCompacted(true); + ReaderBuilder builder = mock(ReaderBuilder.class); + new PulsarPropertiesMapper(properties).customizeReaderBuilder(builder); + then(builder).should().readerName("name"); + then(builder).should().topics(topics); + then(builder).should().subscriptionName("subname"); + then(builder).should().subscriptionRolePrefix("subroleprefix"); + then(builder).should().readCompacted(true); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarPropertiesTests.java new file mode 100644 index 000000000000..53e90a2b954c --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarPropertiesTests.java @@ -0,0 +1,412 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.pulsar.client.api.CompressionType; +import org.apache.pulsar.client.api.HashingScheme; +import org.apache.pulsar.client.api.MessageRoutingMode; +import org.apache.pulsar.client.api.ProducerAccessMode; +import org.apache.pulsar.client.api.RegexSubscriptionMode; +import org.apache.pulsar.client.api.SubscriptionInitialPosition; +import org.apache.pulsar.client.api.SubscriptionMode; +import org.apache.pulsar.client.api.SubscriptionType; +import org.apache.pulsar.common.schema.SchemaType; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.pulsar.PulsarProperties.Defaults.SchemaInfo; +import org.springframework.boot.autoconfigure.pulsar.PulsarProperties.Defaults.TypeMapping; +import org.springframework.boot.autoconfigure.pulsar.PulsarProperties.Failover; +import org.springframework.boot.autoconfigure.pulsar.PulsarProperties.Failover.BackupCluster; +import org.springframework.boot.context.properties.bind.BindException; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Tests for {@link PulsarProperties}. + * + * @author Chris Bono + * @author Christophe Bornet + * @author Soby Chacko + * @author Phillip Webb + * @author Swamy Mavuri + */ +class PulsarPropertiesTests { + + private PulsarProperties bindProperties(Map map) { + return new Binder(new MapConfigurationPropertySource(map)).bind("spring.pulsar", PulsarProperties.class).get(); + } + + @Nested + class ClientProperties { + + @Test + void bind() { + Map map = new HashMap<>(); + map.put("spring.pulsar.client.service-url", "my-service-url"); + map.put("spring.pulsar.client.operation-timeout", "1s"); + map.put("spring.pulsar.client.lookup-timeout", "2s"); + map.put("spring.pulsar.client.connection-timeout", "12s"); + PulsarProperties.Client properties = bindProperties(map).getClient(); + assertThat(properties.getServiceUrl()).isEqualTo("my-service-url"); + assertThat(properties.getOperationTimeout()).isEqualTo(Duration.ofMillis(1000)); + assertThat(properties.getLookupTimeout()).isEqualTo(Duration.ofMillis(2000)); + assertThat(properties.getConnectionTimeout()).isEqualTo(Duration.ofMillis(12000)); + } + + @Test + void bindAuthentication() { + Map map = new HashMap<>(); + map.put("spring.pulsar.client.authentication.plugin-class-name", "com.example.MyAuth"); + map.put("spring.pulsar.client.authentication.param.token", "1234"); + PulsarProperties.Client properties = bindProperties(map).getClient(); + assertThat(properties.getAuthentication().getPluginClassName()).isEqualTo("com.example.MyAuth"); + assertThat(properties.getAuthentication().getParam()).containsEntry("token", "1234"); + } + + @Test + void bindFailover() { + Map map = new HashMap<>(); + map.put("spring.pulsar.client.service-url", "my-service-url"); + map.put("spring.pulsar.client.failover.delay", "30s"); + map.put("spring.pulsar.client.failover.switch-back-delay", "15s"); + map.put("spring.pulsar.client.failover.check-interval", "1s"); + map.put("spring.pulsar.client.failover.backup-clusters[0].service-url", "backup-service-url-1"); + map.put("spring.pulsar.client.failover.backup-clusters[0].authentication.plugin-class-name", + "com.example.MyAuth1"); + map.put("spring.pulsar.client.failover.backup-clusters[0].authentication.param.token", "1234"); + map.put("spring.pulsar.client.failover.backup-clusters[1].service-url", "backup-service-url-2"); + map.put("spring.pulsar.client.failover.backup-clusters[1].authentication.plugin-class-name", + "com.example.MyAuth2"); + map.put("spring.pulsar.client.failover.backup-clusters[1].authentication.param.token", "5678"); + PulsarProperties.Client properties = bindProperties(map).getClient(); + Failover failoverProperties = properties.getFailover(); + List backupClusters = properties.getFailover().getBackupClusters(); + assertThat(properties.getServiceUrl()).isEqualTo("my-service-url"); + assertThat(failoverProperties.getDelay()).isEqualTo(Duration.ofMillis(30000)); + assertThat(failoverProperties.getSwitchBackDelay()).isEqualTo(Duration.ofMillis(15000)); + assertThat(failoverProperties.getCheckInterval()).isEqualTo(Duration.ofMillis(1000)); + assertThat(backupClusters.get(0).getServiceUrl()).isEqualTo("backup-service-url-1"); + assertThat(backupClusters.get(0).getAuthentication().getPluginClassName()).isEqualTo("com.example.MyAuth1"); + assertThat(backupClusters.get(0).getAuthentication().getParam()).containsEntry("token", "1234"); + assertThat(backupClusters.get(1).getServiceUrl()).isEqualTo("backup-service-url-2"); + assertThat(backupClusters.get(1).getAuthentication().getPluginClassName()).isEqualTo("com.example.MyAuth2"); + assertThat(backupClusters.get(1).getAuthentication().getParam()).containsEntry("token", "5678"); + } + + } + + @Nested + class AdminProperties { + + private final String authPluginClassName = "org.apache.pulsar.client.impl.auth.AuthenticationToken"; + + private final String authToken = "1234"; + + @Test + void bind() { + Map map = new HashMap<>(); + map.put("spring.pulsar.admin.service-url", "my-service-url"); + map.put("spring.pulsar.admin.connection-timeout", "12s"); + map.put("spring.pulsar.admin.read-timeout", "13s"); + map.put("spring.pulsar.admin.request-timeout", "14s"); + PulsarProperties.Admin properties = bindProperties(map).getAdmin(); + assertThat(properties.getServiceUrl()).isEqualTo("my-service-url"); + assertThat(properties.getConnectionTimeout()).isEqualTo(Duration.ofSeconds(12)); + assertThat(properties.getReadTimeout()).isEqualTo(Duration.ofSeconds(13)); + assertThat(properties.getRequestTimeout()).isEqualTo(Duration.ofSeconds(14)); + } + + @Test + void bindAuthentication() { + Map map = new HashMap<>(); + map.put("spring.pulsar.admin.authentication.plugin-class-name", this.authPluginClassName); + map.put("spring.pulsar.admin.authentication.param.token", this.authToken); + PulsarProperties.Admin properties = bindProperties(map).getAdmin(); + assertThat(properties.getAuthentication().getPluginClassName()).isEqualTo(this.authPluginClassName); + assertThat(properties.getAuthentication().getParam()).containsEntry("token", this.authToken); + } + + } + + @Nested + class DefaultsProperties { + + @Test + void bindWhenNoTypeMappings() { + assertThat(new PulsarProperties().getDefaults().getTypeMappings()).isEmpty(); + } + + @Test + void bindWhenTypeMappingsWithTopicsOnly() { + Map map = new HashMap<>(); + map.put("spring.pulsar.defaults.type-mappings[0].message-type", TestMessage.class.getName()); + map.put("spring.pulsar.defaults.type-mappings[0].topic-name", "foo-topic"); + map.put("spring.pulsar.defaults.type-mappings[1].message-type", String.class.getName()); + map.put("spring.pulsar.defaults.type-mappings[1].topic-name", "string-topic"); + PulsarProperties.Defaults properties = bindProperties(map).getDefaults(); + TypeMapping expectedTopic1 = new TypeMapping(TestMessage.class, "foo-topic", null); + TypeMapping expectedTopic2 = new TypeMapping(String.class, "string-topic", null); + assertThat(properties.getTypeMappings()).containsExactly(expectedTopic1, expectedTopic2); + } + + @Test + void bindWhenTypeMappingsWithSchemaOnly() { + Map map = new HashMap<>(); + map.put("spring.pulsar.defaults.type-mappings[0].message-type", TestMessage.class.getName()); + map.put("spring.pulsar.defaults.type-mappings[0].schema-info.schema-type", "JSON"); + PulsarProperties.Defaults properties = bindProperties(map).getDefaults(); + TypeMapping expected = new TypeMapping(TestMessage.class, null, new SchemaInfo(SchemaType.JSON, null)); + assertThat(properties.getTypeMappings()).containsExactly(expected); + } + + @Test + void bindWhenTypeMappingsWithTopicAndSchema() { + Map map = new HashMap<>(); + map.put("spring.pulsar.defaults.type-mappings[0].message-type", TestMessage.class.getName()); + map.put("spring.pulsar.defaults.type-mappings[0].topic-name", "foo-topic"); + map.put("spring.pulsar.defaults.type-mappings[0].schema-info.schema-type", "JSON"); + PulsarProperties.Defaults properties = bindProperties(map).getDefaults(); + TypeMapping expected = new TypeMapping(TestMessage.class, "foo-topic", + new SchemaInfo(SchemaType.JSON, null)); + assertThat(properties.getTypeMappings()).containsExactly(expected); + } + + @Test + void bindWhenTypeMappingsWithKeyValueSchema() { + Map map = new HashMap<>(); + map.put("spring.pulsar.defaults.type-mappings[0].message-type", TestMessage.class.getName()); + map.put("spring.pulsar.defaults.type-mappings[0].schema-info.schema-type", "KEY_VALUE"); + map.put("spring.pulsar.defaults.type-mappings[0].schema-info.message-key-type", String.class.getName()); + PulsarProperties.Defaults properties = bindProperties(map).getDefaults(); + TypeMapping expected = new TypeMapping(TestMessage.class, null, + new SchemaInfo(SchemaType.KEY_VALUE, String.class)); + assertThat(properties.getTypeMappings()).containsExactly(expected); + } + + @Test + void bindWhenNoSchemaThrowsException() { + Map map = new HashMap<>(); + map.put("spring.pulsar.defaults.type-mappings[0].message-type", TestMessage.class.getName()); + map.put("spring.pulsar.defaults.type-mappings[0].schema-info.message-key-type", String.class.getName()); + assertThatExceptionOfType(BindException.class).isThrownBy(() -> bindProperties(map)) + .havingRootCause() + .withMessageContaining("schemaType must not be null"); + } + + @Test + void bindWhenSchemaTypeNoneThrowsException() { + Map map = new HashMap<>(); + map.put("spring.pulsar.defaults.type-mappings[0].message-type", TestMessage.class.getName()); + map.put("spring.pulsar.defaults.type-mappings[0].schema-info.schema-type", "NONE"); + assertThatExceptionOfType(BindException.class).isThrownBy(() -> bindProperties(map)) + .havingRootCause() + .withMessageContaining("schemaType 'NONE' not supported"); + } + + @Test + void bindWhenMessageKeyTypeSetOnNonKeyValueSchemaThrowsException() { + Map map = new HashMap<>(); + map.put("spring.pulsar.defaults.type-mappings[0].message-type", TestMessage.class.getName()); + map.put("spring.pulsar.defaults.type-mappings[0].schema-info.schema-type", "JSON"); + map.put("spring.pulsar.defaults.type-mappings[0].schema-info.message-key-type", String.class.getName()); + assertThatExceptionOfType(BindException.class).isThrownBy(() -> bindProperties(map)) + .havingRootCause() + .withMessageContaining("messageKeyType can only be set when schemaType is KEY_VALUE"); + } + + record TestMessage(String value) { + } + + } + + @Nested + class FunctionProperties { + + @Test + void defaults() { + PulsarProperties.Function properties = new PulsarProperties.Function(); + assertThat(properties.isFailFast()).isTrue(); + assertThat(properties.isPropagateFailures()).isTrue(); + assertThat(properties.isPropagateStopFailures()).isFalse(); + } + + @Test + void bind() { + Map props = new HashMap<>(); + props.put("spring.pulsar.function.fail-fast", "false"); + props.put("spring.pulsar.function.propagate-failures", "false"); + props.put("spring.pulsar.function.propagate-stop-failures", "true"); + PulsarProperties.Function properties = bindProperties(props).getFunction(); + assertThat(properties.isFailFast()).isFalse(); + assertThat(properties.isPropagateFailures()).isFalse(); + assertThat(properties.isPropagateStopFailures()).isTrue(); + } + + } + + @Nested + class ProducerProperties { + + @Test + void bind() { + Map map = new HashMap<>(); + map.put("spring.pulsar.producer.name", "my-producer"); + map.put("spring.pulsar.producer.topic-name", "my-topic"); + map.put("spring.pulsar.producer.send-timeout", "2s"); + map.put("spring.pulsar.producer.message-routing-mode", "custompartition"); + map.put("spring.pulsar.producer.hashing-scheme", "murmur3_32hash"); + map.put("spring.pulsar.producer.batching-enabled", "false"); + map.put("spring.pulsar.producer.chunking-enabled", "true"); + map.put("spring.pulsar.producer.compression-type", "lz4"); + map.put("spring.pulsar.producer.access-mode", "exclusive"); + map.put("spring.pulsar.producer.cache.expire-after-access", "2s"); + map.put("spring.pulsar.producer.cache.maximum-size", "3"); + map.put("spring.pulsar.producer.cache.initial-capacity", "5"); + PulsarProperties.Producer properties = bindProperties(map).getProducer(); + assertThat(properties.getName()).isEqualTo("my-producer"); + assertThat(properties.getTopicName()).isEqualTo("my-topic"); + assertThat(properties.getSendTimeout()).isEqualTo(Duration.ofSeconds(2)); + assertThat(properties.getMessageRoutingMode()).isEqualTo(MessageRoutingMode.CustomPartition); + assertThat(properties.getHashingScheme()).isEqualTo(HashingScheme.Murmur3_32Hash); + assertThat(properties.isBatchingEnabled()).isFalse(); + assertThat(properties.isChunkingEnabled()).isTrue(); + assertThat(properties.getCompressionType()).isEqualTo(CompressionType.LZ4); + assertThat(properties.getAccessMode()).isEqualTo(ProducerAccessMode.Exclusive); + assertThat(properties.getCache().getExpireAfterAccess()).isEqualTo(Duration.ofSeconds(2)); + assertThat(properties.getCache().getMaximumSize()).isEqualTo(3); + assertThat(properties.getCache().getInitialCapacity()).isEqualTo(5); + } + + } + + @Nested + class ConsumerPropertiesTests { + + @Test + void bind() { + Map map = new HashMap<>(); + map.put("spring.pulsar.consumer.name", "my-consumer"); + map.put("spring.pulsar.consumer.subscription.initial-position", "earliest"); + map.put("spring.pulsar.consumer.subscription.mode", "nondurable"); + map.put("spring.pulsar.consumer.subscription.name", "my-subscription"); + map.put("spring.pulsar.consumer.subscription.topics-mode", "all-topics"); + map.put("spring.pulsar.consumer.subscription.type", "shared"); + map.put("spring.pulsar.consumer.topics[0]", "my-topic"); + map.put("spring.pulsar.consumer.topics-pattern", "my-pattern"); + map.put("spring.pulsar.consumer.priority-level", "8"); + map.put("spring.pulsar.consumer.read-compacted", "true"); + map.put("spring.pulsar.consumer.dead-letter-policy.max-redeliver-count", "4"); + map.put("spring.pulsar.consumer.dead-letter-policy.retry-letter-topic", "my-retry-topic"); + map.put("spring.pulsar.consumer.dead-letter-policy.dead-letter-topic", "my-dlt-topic"); + map.put("spring.pulsar.consumer.dead-letter-policy.initial-subscription-name", "my-initial-subscription"); + map.put("spring.pulsar.consumer.retry-enable", "true"); + PulsarProperties.Consumer properties = bindProperties(map).getConsumer(); + assertThat(properties.getName()).isEqualTo("my-consumer"); + assertThat(properties.getSubscription()).satisfies((subscription) -> { + assertThat(subscription.getName()).isEqualTo("my-subscription"); + assertThat(subscription.getType()).isEqualTo(SubscriptionType.Shared); + assertThat(subscription.getMode()).isEqualTo(SubscriptionMode.NonDurable); + assertThat(subscription.getInitialPosition()).isEqualTo(SubscriptionInitialPosition.Earliest); + assertThat(subscription.getTopicsMode()).isEqualTo(RegexSubscriptionMode.AllTopics); + }); + assertThat(properties.getTopics()).containsExactly("my-topic"); + assertThat(properties.getTopicsPattern().toString()).isEqualTo("my-pattern"); + assertThat(properties.getPriorityLevel()).isEqualTo(8); + assertThat(properties.isReadCompacted()).isTrue(); + assertThat(properties.getDeadLetterPolicy()).satisfies((policy) -> { + assertThat(policy.getMaxRedeliverCount()).isEqualTo(4); + assertThat(policy.getRetryLetterTopic()).isEqualTo("my-retry-topic"); + assertThat(policy.getDeadLetterTopic()).isEqualTo("my-dlt-topic"); + assertThat(policy.getInitialSubscriptionName()).isEqualTo("my-initial-subscription"); + }); + assertThat(properties.isRetryEnable()).isTrue(); + } + + } + + @Nested + class ListenerProperties { + + @Test + void bind() { + Map map = new HashMap<>(); + map.put("spring.pulsar.listener.schema-type", "avro"); + map.put("spring.pulsar.listener.observation-enabled", "true"); + PulsarProperties.Listener properties = bindProperties(map).getListener(); + assertThat(properties.getSchemaType()).isEqualTo(SchemaType.AVRO); + assertThat(properties.isObservationEnabled()).isTrue(); + } + + } + + @Nested + class ReaderProperties { + + @Test + void bind() { + Map map = new HashMap<>(); + map.put("spring.pulsar.reader.name", "my-reader"); + map.put("spring.pulsar.reader.topics", "my-topic"); + map.put("spring.pulsar.reader.subscription-name", "my-subscription"); + map.put("spring.pulsar.reader.subscription-role-prefix", "sub-role"); + map.put("spring.pulsar.reader.read-compacted", "true"); + PulsarProperties.Reader properties = bindProperties(map).getReader(); + assertThat(properties.getName()).isEqualTo("my-reader"); + assertThat(properties.getTopics()).containsExactly("my-topic"); + assertThat(properties.getSubscriptionName()).isEqualTo("my-subscription"); + assertThat(properties.getSubscriptionRolePrefix()).isEqualTo("sub-role"); + assertThat(properties.isReadCompacted()).isTrue(); + } + + } + + @Nested + class TemplateProperties { + + @Test + void bind() { + Map map = new HashMap<>(); + map.put("spring.pulsar.template.observations-enabled", "true"); + PulsarProperties.Template properties = bindProperties(map).getTemplate(); + assertThat(properties.isObservationsEnabled()).isTrue(); + } + + } + + @Nested + class TransactionProperties { + + @Test + void bind() { + Map map = new HashMap<>(); + map.put("spring.pulsar.transaction.enabled", "true"); + PulsarProperties.Transaction properties = bindProperties(map).getTransaction(); + assertThat(properties.isEnabled()).isTrue(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarReactiveAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarReactiveAutoConfigurationTests.java new file mode 100644 index 000000000000..4f3ab011ea2e --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarReactiveAutoConfigurationTests.java @@ -0,0 +1,473 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +import com.github.benmanes.caffeine.cache.Caffeine; +import org.apache.pulsar.client.api.PulsarClient; +import org.apache.pulsar.common.schema.SchemaType; +import org.apache.pulsar.reactive.client.adapter.ProducerCacheProvider; +import org.apache.pulsar.reactive.client.api.ReactiveMessageConsumerBuilder; +import org.apache.pulsar.reactive.client.api.ReactiveMessageReaderBuilder; +import org.apache.pulsar.reactive.client.api.ReactiveMessageSenderBuilder; +import org.apache.pulsar.reactive.client.api.ReactiveMessageSenderCache; +import org.apache.pulsar.reactive.client.api.ReactivePulsarClient; +import org.apache.pulsar.reactive.client.producercache.CaffeineShadedProducerCacheProvider; +import org.assertj.core.api.AbstractObjectAssert; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.core.annotation.Order; +import org.springframework.pulsar.core.DefaultSchemaResolver; +import org.springframework.pulsar.core.DefaultTopicResolver; +import org.springframework.pulsar.core.PulsarAdministration; +import org.springframework.pulsar.core.SchemaResolver; +import org.springframework.pulsar.core.TopicResolver; +import org.springframework.pulsar.reactive.config.DefaultReactivePulsarListenerContainerFactory; +import org.springframework.pulsar.reactive.config.ReactivePulsarListenerContainerFactory; +import org.springframework.pulsar.reactive.config.ReactivePulsarListenerEndpointRegistry; +import org.springframework.pulsar.reactive.config.annotation.ReactivePulsarBootstrapConfiguration; +import org.springframework.pulsar.reactive.config.annotation.ReactivePulsarListenerAnnotationBeanPostProcessor; +import org.springframework.pulsar.reactive.core.DefaultReactivePulsarConsumerFactory; +import org.springframework.pulsar.reactive.core.DefaultReactivePulsarReaderFactory; +import org.springframework.pulsar.reactive.core.DefaultReactivePulsarSenderFactory; +import org.springframework.pulsar.reactive.core.ReactiveMessageConsumerBuilderCustomizer; +import org.springframework.pulsar.reactive.core.ReactiveMessageReaderBuilderCustomizer; +import org.springframework.pulsar.reactive.core.ReactiveMessageSenderBuilderCustomizer; +import org.springframework.pulsar.reactive.core.ReactivePulsarConsumerFactory; +import org.springframework.pulsar.reactive.core.ReactivePulsarReaderFactory; +import org.springframework.pulsar.reactive.core.ReactivePulsarSenderFactory; +import org.springframework.pulsar.reactive.core.ReactivePulsarTemplate; +import org.springframework.pulsar.reactive.listener.ReactivePulsarContainerProperties; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link PulsarReactiveAutoConfiguration}. + * + * @author Chris Bono + * @author Christophe Bornet + * @author Phillip Webb + */ +class PulsarReactiveAutoConfigurationTests { + + private static final String INTERNAL_PULSAR_LISTENER_ANNOTATION_PROCESSOR = "org.springframework.pulsar.config.internalReactivePulsarListenerAnnotationProcessor"; + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(PulsarReactiveAutoConfiguration.class)) + .withBean(PulsarClient.class, () -> mock(PulsarClient.class)); + + @Test + void whenPulsarNotOnClasspathAutoConfigurationIsSkipped() { + new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(PulsarReactiveAutoConfiguration.class)) + .withClassLoader(new FilteredClassLoader(PulsarClient.class)) + .run((context) -> assertThat(context).doesNotHaveBean(PulsarReactiveAutoConfiguration.class)); + } + + @Test + void whenReactivePulsarNotOnClasspathAutoConfigurationIsSkipped() { + this.contextRunner.withClassLoader(new FilteredClassLoader(ReactivePulsarClient.class)) + .run((context) -> assertThat(context).doesNotHaveBean(PulsarReactiveAutoConfiguration.class)); + } + + @Test + void whenReactiveSpringPulsarNotOnClasspathAutoConfigurationIsSkipped() { + this.contextRunner.withClassLoader(new FilteredClassLoader(ReactivePulsarTemplate.class)) + .run((context) -> assertThat(context).doesNotHaveBean(PulsarReactiveAutoConfiguration.class)); + } + + @Test + void whenCustomPulsarListenerAnnotationProcessorDefinedAutoConfigurationIsSkipped() { + this.contextRunner.withBean(INTERNAL_PULSAR_LISTENER_ANNOTATION_PROCESSOR, String.class, () -> "bean") + .run((context) -> assertThat(context).doesNotHaveBean(ReactivePulsarBootstrapConfiguration.class)); + } + + @Test + void autoConfiguresBeans() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PulsarConfiguration.class) + .hasSingleBean(PulsarClient.class) + .hasSingleBean(PulsarAdministration.class) + .hasSingleBean(DefaultSchemaResolver.class) + .hasSingleBean(DefaultTopicResolver.class) + .hasSingleBean(ReactivePulsarClient.class) + .hasSingleBean(CaffeineShadedProducerCacheProvider.class) + .hasSingleBean(ReactiveMessageSenderCache.class) + .hasSingleBean(DefaultReactivePulsarSenderFactory.class) + .hasSingleBean(ReactivePulsarTemplate.class) + .hasSingleBean(DefaultReactivePulsarConsumerFactory.class) + .hasSingleBean(DefaultReactivePulsarListenerContainerFactory.class) + .hasSingleBean(ReactivePulsarListenerAnnotationBeanPostProcessor.class) + .hasSingleBean(ReactivePulsarListenerEndpointRegistry.class)); + } + + @Test + @SuppressWarnings("rawtypes") + void injectsExpectedBeansIntoReactivePulsarClient() { + this.contextRunner.run((context) -> { + PulsarClient pulsarClient = context.getBean(PulsarClient.class); + assertThat(context).hasNotFailed() + .getBean(ReactivePulsarClient.class) + .extracting("reactivePulsarResourceAdapter") + .extracting("pulsarClientSupplier", InstanceOfAssertFactories.type(Supplier.class)) + .extracting(Supplier::get) + .isSameAs(pulsarClient); + }); + } + + @ParameterizedTest + @ValueSource(classes = { ReactivePulsarClient.class, ProducerCacheProvider.class, ReactiveMessageSenderCache.class, + ReactivePulsarSenderFactory.class, ReactivePulsarConsumerFactory.class, ReactivePulsarReaderFactory.class, + ReactivePulsarTemplate.class }) + void whenHasUserDefinedBeanDoesNotAutoConfigureBean(Class beanClass) { + T bean = mock(beanClass); + this.contextRunner.withBean(beanClass.getName(), beanClass, () -> bean) + .run((context) -> assertThat(context).getBean(beanClass).isSameAs(bean)); + } + + @Nested + class SenderFactoryTests { + + private final ApplicationContextRunner contextRunner = PulsarReactiveAutoConfigurationTests.this.contextRunner; + + @Test + void injectsExpectedBeans() { + ReactivePulsarClient client = mock(ReactivePulsarClient.class); + ReactiveMessageSenderCache cache = mock(ReactiveMessageSenderCache.class); + this.contextRunner.withPropertyValues("spring.pulsar.producer.topic-name=test-topic") + .withBean("customReactivePulsarClient", ReactivePulsarClient.class, () -> client) + .withBean("customReactiveMessageSenderCache", ReactiveMessageSenderCache.class, () -> cache) + .run((context) -> { + DefaultReactivePulsarSenderFactory senderFactory = context + .getBean(DefaultReactivePulsarSenderFactory.class); + assertThat(senderFactory) + .extracting("reactivePulsarClient", InstanceOfAssertFactories.type(ReactivePulsarClient.class)) + .isSameAs(client); + assertThat(senderFactory) + .extracting("reactiveMessageSenderCache", + InstanceOfAssertFactories.type(ReactiveMessageSenderCache.class)) + .isSameAs(cache); + assertThat(senderFactory) + .extracting("topicResolver", InstanceOfAssertFactories.type(TopicResolver.class)) + .isSameAs(context.getBean(TopicResolver.class)); + }); + } + + @Test + void injectsExpectedBeansIntoReactiveMessageSenderCache() { + ProducerCacheProvider provider = mock(ProducerCacheProvider.class); + this.contextRunner.withBean("customProducerCacheProvider", ProducerCacheProvider.class, () -> provider) + .run((context) -> assertThat(context).getBean(ReactiveMessageSenderCache.class) + .extracting("cacheProvider", InstanceOfAssertFactories.type(ProducerCacheProvider.class)) + .isSameAs(provider)); + } + + @Test + void whenHasUserDefinedCustomizersAppliesInCorrectOrder() { + this.contextRunner.withPropertyValues("spring.pulsar.producer.name=fromPropsCustomizer") + .withUserConfiguration(ReactiveMessageSenderBuilderCustomizerConfig.class) + .run((context) -> { + DefaultReactivePulsarSenderFactory producerFactory = context + .getBean(DefaultReactivePulsarSenderFactory.class); + Customizers, ReactiveMessageSenderBuilder> customizers = Customizers + .of(ReactiveMessageSenderBuilder.class, ReactiveMessageSenderBuilderCustomizer::customize); + assertThat(customizers.fromField(producerFactory, "defaultConfigCustomizers")).callsInOrder( + ReactiveMessageSenderBuilder::producerName, "fromPropsCustomizer", "fromCustomizer1", + "fromCustomizer2"); + }); + } + + @TestConfiguration(proxyBeanMethods = false) + static class ReactiveMessageSenderBuilderCustomizerConfig { + + @Bean + @Order(200) + ReactiveMessageSenderBuilderCustomizer customizerFoo() { + return (builder) -> builder.producerName("fromCustomizer2"); + } + + @Bean + @Order(100) + ReactiveMessageSenderBuilderCustomizer customizerBar() { + return (builder) -> builder.producerName("fromCustomizer1"); + } + + } + + } + + @Nested + class TemplateTests { + + private final ApplicationContextRunner contextRunner = PulsarReactiveAutoConfigurationTests.this.contextRunner; + + @Test + @SuppressWarnings("rawtypes") + void injectsExpectedBeans() { + ReactivePulsarSenderFactory senderFactory = mock(ReactivePulsarSenderFactory.class); + SchemaResolver schemaResolver = mock(SchemaResolver.class); + this.contextRunner + .withBean("customReactivePulsarSenderFactory", ReactivePulsarSenderFactory.class, () -> senderFactory) + .withBean("schemaResolver", SchemaResolver.class, () -> schemaResolver) + .run((context) -> assertThat(context).getBean(ReactivePulsarTemplate.class).satisfies((template) -> { + assertThat(template).extracting("reactiveMessageSenderFactory").isSameAs(senderFactory); + assertThat(template).extracting("schemaResolver").isSameAs(schemaResolver); + })); + } + + } + + @Nested + class ConsumerFactoryTests { + + private final ApplicationContextRunner contextRunner = PulsarReactiveAutoConfigurationTests.this.contextRunner; + + @Test + void injectsExpectedBeans() { + ReactivePulsarClient client = mock(ReactivePulsarClient.class); + this.contextRunner.withBean("customReactivePulsarClient", ReactivePulsarClient.class, () -> client) + .run((context) -> { + ReactivePulsarConsumerFactory consumerFactory = context + .getBean(DefaultReactivePulsarConsumerFactory.class); + assertThat(consumerFactory) + .extracting("reactivePulsarClient", InstanceOfAssertFactories.type(ReactivePulsarClient.class)) + .isSameAs(client); + }); + } + + @Test + void whenHasUserDefinedCustomizersAppliesInCorrectOrder() { + this.contextRunner.withPropertyValues("spring.pulsar.consumer.name=fromPropsCustomizer") + .withUserConfiguration(ReactiveMessageConsumerBuilderCustomizerConfig.class) + .run((context) -> { + DefaultReactivePulsarConsumerFactory consumerFactory = context + .getBean(DefaultReactivePulsarConsumerFactory.class); + Customizers, ReactiveMessageConsumerBuilder> customizers = Customizers + .of(ReactiveMessageConsumerBuilder.class, ReactiveMessageConsumerBuilderCustomizer::customize); + assertThat(customizers.fromField(consumerFactory, "defaultConfigCustomizers")).callsInOrder( + ReactiveMessageConsumerBuilder::consumerName, "fromPropsCustomizer", "fromCustomizer1", + "fromCustomizer2"); + }); + } + + @TestConfiguration(proxyBeanMethods = false) + static class ReactiveMessageConsumerBuilderCustomizerConfig { + + @Bean + @Order(200) + ReactiveMessageConsumerBuilderCustomizer customizerFoo() { + return (builder) -> builder.consumerName("fromCustomizer2"); + } + + @Bean + @Order(100) + ReactiveMessageConsumerBuilderCustomizer customizerBar() { + return (builder) -> builder.consumerName("fromCustomizer1"); + } + + } + + } + + @Nested + class ListenerTests { + + private final ApplicationContextRunner contextRunner = PulsarReactiveAutoConfigurationTests.this.contextRunner; + + @Test + void whenHasUserDefinedBeanDoesNotAutoConfigureBean() { + ReactivePulsarListenerContainerFactory listenerContainerFactory = mock( + ReactivePulsarListenerContainerFactory.class); + this.contextRunner + .withBean("reactivePulsarListenerContainerFactory", ReactivePulsarListenerContainerFactory.class, + () -> listenerContainerFactory) + .run((context) -> assertThat(context).getBean(ReactivePulsarListenerContainerFactory.class) + .isSameAs(listenerContainerFactory)); + } + + @Test + void whenHasUserDefinedReactivePulsarListenerAnnotationBeanPostProcessorDoesNotAutoConfigureBean() { + ReactivePulsarListenerAnnotationBeanPostProcessor listenerAnnotationBeanPostProcessor = mock( + ReactivePulsarListenerAnnotationBeanPostProcessor.class); + this.contextRunner.withBean(INTERNAL_PULSAR_LISTENER_ANNOTATION_PROCESSOR, + ReactivePulsarListenerAnnotationBeanPostProcessor.class, () -> listenerAnnotationBeanPostProcessor) + .run((context) -> assertThat(context).getBean(ReactivePulsarListenerAnnotationBeanPostProcessor.class) + .isSameAs(listenerAnnotationBeanPostProcessor)); + } + + @Test + void whenHasCustomProperties() { + List properties = new ArrayList<>(); + properties.add("spring.pulsar.listener.schema-type=avro"); + this.contextRunner.withPropertyValues(properties.toArray(String[]::new)).run((context) -> { + DefaultReactivePulsarListenerContainerFactory factory = context + .getBean(DefaultReactivePulsarListenerContainerFactory.class); + assertThat(factory.getContainerProperties().getSchemaType()).isEqualTo(SchemaType.AVRO); + }); + } + + @Test + void injectsExpectedBeans() { + ReactivePulsarConsumerFactory consumerFactory = mock(ReactivePulsarConsumerFactory.class); + SchemaResolver schemaResolver = mock(SchemaResolver.class); + this.contextRunner + .withBean("customReactivePulsarConsumerFactory", ReactivePulsarConsumerFactory.class, + () -> consumerFactory) + .withBean("schemaResolver", SchemaResolver.class, () -> schemaResolver) + .run((context) -> { + DefaultReactivePulsarListenerContainerFactory containerFactory = context + .getBean(DefaultReactivePulsarListenerContainerFactory.class); + assertThat(containerFactory).extracting("consumerFactory").isSameAs(consumerFactory); + assertThat(containerFactory) + .extracting(DefaultReactivePulsarListenerContainerFactory::getContainerProperties) + .extracting(ReactivePulsarContainerProperties::getSchemaResolver) + .isSameAs(schemaResolver); + }); + } + + } + + @Nested + class ReaderFactoryTests { + + private final ApplicationContextRunner contextRunner = PulsarReactiveAutoConfigurationTests.this.contextRunner; + + @Test + void injectsExpectedBeans() { + ReactivePulsarClient client = mock(ReactivePulsarClient.class); + this.contextRunner.withPropertyValues("spring.pulsar.reader.name=test-reader") + .withBean("customReactivePulsarClient", ReactivePulsarClient.class, () -> client) + .run((context) -> { + DefaultReactivePulsarReaderFactory readerFactory = context + .getBean(DefaultReactivePulsarReaderFactory.class); + assertThat(readerFactory) + .extracting("reactivePulsarClient", InstanceOfAssertFactories.type(ReactivePulsarClient.class)) + .isSameAs(client); + }); + } + + @Test + void whenHasUserDefinedCustomizersAppliesInCorrectOrder() { + this.contextRunner.withPropertyValues("spring.pulsar.reader.name=fromPropsCustomizer") + .withUserConfiguration(ReactiveMessageReaderBuilderCustomizerConfig.class) + .run((context) -> { + DefaultReactivePulsarReaderFactory readerFactory = context + .getBean(DefaultReactivePulsarReaderFactory.class); + Customizers, ReactiveMessageReaderBuilder> customizers = Customizers + .of(ReactiveMessageReaderBuilder.class, ReactiveMessageReaderBuilderCustomizer::customize); + assertThat(customizers.fromField(readerFactory, "defaultConfigCustomizers")).callsInOrder( + ReactiveMessageReaderBuilder::readerName, "fromPropsCustomizer", "fromCustomizer1", + "fromCustomizer2"); + }); + } + + @TestConfiguration(proxyBeanMethods = false) + static class ReactiveMessageReaderBuilderCustomizerConfig { + + @Bean + @Order(200) + ReactiveMessageReaderBuilderCustomizer customizerFoo() { + return (builder) -> builder.readerName("fromCustomizer2"); + } + + @Bean + @Order(100) + ReactiveMessageReaderBuilderCustomizer customizerBar() { + return (builder) -> builder.readerName("fromCustomizer1"); + } + + } + + } + + @Nested + class SenderCacheAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = PulsarReactiveAutoConfigurationTests.this.contextRunner; + + @Test + void whenNoPropertiesEnablesCaching() { + this.contextRunner.run(this::assertCaffeineProducerCacheProvider); + } + + @Test + void whenCachingEnabledEnablesCaching() { + this.contextRunner.withPropertyValues("spring.pulsar.producer.cache.enabled=true") + .run(this::assertCaffeineProducerCacheProvider); + } + + @Test + void whenCachingDisabledDoesNotEnableCaching() { + this.contextRunner.withPropertyValues("spring.pulsar.producer.cache.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(ProducerCacheProvider.class) + .doesNotHaveBean(ReactiveMessageSenderCache.class)); + } + + @Test + void whenCachingEnabledAndCaffeineNotOnClasspathStillUsesCaffeine() { + // The reactive client shades Caffeine - it should still be used + this.contextRunner.withClassLoader(new FilteredClassLoader(Caffeine.class)) + .withPropertyValues("spring.pulsar.producer.cache.enabled=true") + .run(this::assertCaffeineProducerCacheProvider); + } + + @Test + void whenCachingEnabledAndNoCacheProviderAvailable() { + // The reactive client uses a shaded caffeine cache provider as its internal + // cache + this.contextRunner.withClassLoader(new FilteredClassLoader(CaffeineShadedProducerCacheProvider.class)) + .withPropertyValues("spring.pulsar.producer.cache.enabled=true") + .run((context) -> assertThat(context).doesNotHaveBean(ProducerCacheProvider.class) + .getBean(ReactiveMessageSenderCache.class) + .extracting("cacheProvider") + .isExactlyInstanceOf(CaffeineShadedProducerCacheProvider.class)); + } + + @Test + void whenCustomCachingPropertiesCreatesConfiguredBean() { + this.contextRunner + .withPropertyValues("spring.pulsar.producer.cache.expire-after-access=100s", + "spring.pulsar.producer.cache.maximum-size=5150", + "spring.pulsar.producer.cache.initial-capacity=200") + .run((context) -> assertCaffeineProducerCacheProvider(context).extracting("cache.cache") + .hasFieldOrPropertyWithValue("expiresAfterAccessNanos", Duration.ofSeconds(100).toNanos()) + .hasFieldOrPropertyWithValue("maximum", 5150L)); + } + + private AbstractObjectAssert assertCaffeineProducerCacheProvider( + AssertableApplicationContext context) { + return assertThat(context).hasSingleBean(ReactiveMessageSenderCache.class) + .getBean(ProducerCacheProvider.class) + .isExactlyInstanceOf(CaffeineShadedProducerCacheProvider.class); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarReactivePropertiesMapperTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarReactivePropertiesMapperTests.java new file mode 100644 index 000000000000..df078b21a354 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarReactivePropertiesMapperTests.java @@ -0,0 +1,146 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.pulsar; + +import java.time.Duration; +import java.util.List; +import java.util.regex.Pattern; + +import org.apache.pulsar.client.api.CompressionType; +import org.apache.pulsar.client.api.DeadLetterPolicy; +import org.apache.pulsar.client.api.HashingScheme; +import org.apache.pulsar.client.api.MessageRoutingMode; +import org.apache.pulsar.client.api.ProducerAccessMode; +import org.apache.pulsar.client.api.RegexSubscriptionMode; +import org.apache.pulsar.client.api.SubscriptionInitialPosition; +import org.apache.pulsar.client.api.SubscriptionMode; +import org.apache.pulsar.client.api.SubscriptionType; +import org.apache.pulsar.common.schema.SchemaType; +import org.apache.pulsar.reactive.client.api.ReactiveMessageConsumerBuilder; +import org.apache.pulsar.reactive.client.api.ReactiveMessageReaderBuilder; +import org.apache.pulsar.reactive.client.api.ReactiveMessageSenderBuilder; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.pulsar.PulsarProperties.Consumer; +import org.springframework.boot.autoconfigure.pulsar.PulsarProperties.Consumer.Subscription; +import org.springframework.pulsar.reactive.listener.ReactivePulsarContainerProperties; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link PulsarReactivePropertiesMapper}. + * + * @author Chris Bono + * @author Phillip Webb + */ +class PulsarReactivePropertiesMapperTests { + + @Test + @SuppressWarnings("unchecked") + void customizeMessageSenderBuilder() { + PulsarProperties properties = new PulsarProperties(); + properties.getProducer().setName("name"); + properties.getProducer().setTopicName("topicname"); + properties.getProducer().setSendTimeout(Duration.ofSeconds(1)); + properties.getProducer().setMessageRoutingMode(MessageRoutingMode.RoundRobinPartition); + properties.getProducer().setHashingScheme(HashingScheme.JavaStringHash); + properties.getProducer().setBatchingEnabled(false); + properties.getProducer().setChunkingEnabled(true); + properties.getProducer().setCompressionType(CompressionType.SNAPPY); + properties.getProducer().setAccessMode(ProducerAccessMode.Exclusive); + ReactiveMessageSenderBuilder builder = mock(ReactiveMessageSenderBuilder.class); + new PulsarReactivePropertiesMapper(properties).customizeMessageSenderBuilder(builder); + then(builder).should().producerName("name"); + then(builder).should().topic("topicname"); + then(builder).should().sendTimeout(Duration.ofSeconds(1)); + then(builder).should().messageRoutingMode(MessageRoutingMode.RoundRobinPartition); + then(builder).should().hashingScheme(HashingScheme.JavaStringHash); + then(builder).should().batchingEnabled(false); + then(builder).should().chunkingEnabled(true); + then(builder).should().compressionType(CompressionType.SNAPPY); + then(builder).should().accessMode(ProducerAccessMode.Exclusive); + } + + @Test + @SuppressWarnings("unchecked") + void customizeMessageConsumerBuilder() { + PulsarProperties properties = new PulsarProperties(); + List topics = List.of("mytopic"); + Pattern topisPattern = Pattern.compile("my-pattern"); + properties.getConsumer().setName("name"); + properties.getConsumer().setTopics(topics); + properties.getConsumer().setTopicsPattern(topisPattern); + properties.getConsumer().setPriorityLevel(123); + properties.getConsumer().setReadCompacted(true); + Consumer.DeadLetterPolicy deadLetterPolicy = new Consumer.DeadLetterPolicy(); + deadLetterPolicy.setDeadLetterTopic("my-dlt"); + deadLetterPolicy.setMaxRedeliverCount(1); + properties.getConsumer().setDeadLetterPolicy(deadLetterPolicy); + properties.getConsumer().setRetryEnable(false); + Subscription subscriptionProperties = properties.getConsumer().getSubscription(); + subscriptionProperties.setName("subname"); + subscriptionProperties.setInitialPosition(SubscriptionInitialPosition.Earliest); + subscriptionProperties.setMode(SubscriptionMode.NonDurable); + subscriptionProperties.setTopicsMode(RegexSubscriptionMode.NonPersistentOnly); + subscriptionProperties.setType(SubscriptionType.Key_Shared); + ReactiveMessageConsumerBuilder builder = mock(ReactiveMessageConsumerBuilder.class); + new PulsarReactivePropertiesMapper(properties).customizeMessageConsumerBuilder(builder); + then(builder).should().consumerName("name"); + then(builder).should().topics(topics); + then(builder).should().topicsPattern(topisPattern); + then(builder).should().priorityLevel(123); + then(builder).should().readCompacted(true); + then(builder).should().deadLetterPolicy(new DeadLetterPolicy(1, null, "my-dlt", null)); + then(builder).should().retryLetterTopicEnable(false); + then(builder).should().subscriptionName("subname"); + then(builder).should().subscriptionInitialPosition(SubscriptionInitialPosition.Earliest); + then(builder).should().subscriptionMode(SubscriptionMode.NonDurable); + then(builder).should().topicsPatternSubscriptionMode(RegexSubscriptionMode.NonPersistentOnly); + then(builder).should().subscriptionType(SubscriptionType.Key_Shared); + } + + @Test + void customizeContainerProperties() { + PulsarProperties properties = new PulsarProperties(); + properties.getConsumer().getSubscription().setType(SubscriptionType.Shared); + properties.getListener().setSchemaType(SchemaType.AVRO); + ReactivePulsarContainerProperties containerProperties = new ReactivePulsarContainerProperties<>(); + new PulsarReactivePropertiesMapper(properties).customizeContainerProperties(containerProperties); + assertThat(containerProperties.getSubscriptionType()).isEqualTo(SubscriptionType.Shared); + assertThat(containerProperties.getSchemaType()).isEqualTo(SchemaType.AVRO); + } + + @Test + @SuppressWarnings("unchecked") + void customizeMessageReaderBuilder() { + List topics = List.of("my-topic"); + PulsarProperties properties = new PulsarProperties(); + properties.getReader().setName("name"); + properties.getReader().setTopics(topics); + properties.getReader().setSubscriptionName("subname"); + properties.getReader().setSubscriptionRolePrefix("srp"); + ReactiveMessageReaderBuilder builder = mock(ReactiveMessageReaderBuilder.class); + new PulsarReactivePropertiesMapper(properties).customizeMessageReaderBuilder(builder); + then(builder).should().readerName("name"); + then(builder).should().topics(topics); + then(builder).should().subscriptionName("subname"); + then(builder).should().generatedSubscriptionNamePrefix("srp"); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProxyAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProxyAutoConfigurationTests.java new file mode 100644 index 000000000000..a6250d0e63e6 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProxyAutoConfigurationTests.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.r2dbc; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import io.r2dbc.spi.ConnectionFactory; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.r2dbc.ConnectionFactoryBuilder; +import org.springframework.boot.r2dbc.ConnectionFactoryDecorator; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link R2dbcProxyAutoConfiguration}. + * + * @author Tadaya Tsuyukubo + * @author Moritz Halbritter + */ +class R2dbcProxyAutoConfigurationTests { + + private final ApplicationContextRunner runner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(R2dbcProxyAutoConfiguration.class)); + + @Test + void shouldSupplyConnectionFactoryDecorator() { + this.runner.run((context) -> assertThat(context).hasSingleBean(ConnectionFactoryDecorator.class)); + } + + @Test + void shouldNotSupplyBeansIfR2dbcSpiIsNotOnClasspath() { + this.runner.withClassLoader(new FilteredClassLoader("io.r2dbc.spi")) + .run((context) -> assertThat(context).doesNotHaveBean(ConnectionFactoryDecorator.class)); + } + + @Test + void shouldNotSupplyBeansIfR2dbcProxyIsNotOnClasspath() { + this.runner.withClassLoader(new FilteredClassLoader("io.r2dbc.proxy")) + .run((context) -> assertThat(context).doesNotHaveBean(ConnectionFactoryDecorator.class)); + } + + @Test + void shouldApplyCustomizers() { + this.runner.withUserConfiguration(ProxyConnectionFactoryCustomizerConfig.class).run((context) -> { + ConnectionFactoryDecorator decorator = context.getBean(ConnectionFactoryDecorator.class); + ConnectionFactory connectionFactory = ConnectionFactoryBuilder + .withUrl("r2dbc:h2:mem:///" + UUID.randomUUID()) + .build(); + decorator.decorate(connectionFactory); + assertThat(context.getBean(ProxyConnectionFactoryCustomizerConfig.class).called).containsExactly("first", + "second"); + }); + } + + @Configuration(proxyBeanMethods = false) + private static final class ProxyConnectionFactoryCustomizerConfig { + + private final List called = new ArrayList<>(); + + @Bean + @Order(1) + ProxyConnectionFactoryCustomizer first() { + return (builder) -> this.called.add("first"); + } + + @Bean + @Order(2) + ProxyConnectionFactoryCustomizer second() { + return (builder) -> this.called.add("second"); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/reactor/ReactorAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/reactor/ReactorAutoConfigurationTests.java new file mode 100644 index 000000000000..1587fc2050db --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/reactor/ReactorAutoConfigurationTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.reactor; + +import java.util.concurrent.atomic.AtomicReference; + +import io.micrometer.context.ContextRegistry; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Hooks; +import reactor.core.publisher.Mono; +import reactor.util.context.Context; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ReactorAutoConfiguration}. + * + * @author Brian Clozel + * @author Moritz Halbritter + */ +class ReactorAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(ReactorAutoConfiguration.class)); + + private static final String THREADLOCAL_KEY = "ReactorAutoConfigurationTests"; + + private static final ThreadLocal THREADLOCAL_VALUE = ThreadLocal.withInitial(() -> "initial"); + + @BeforeEach + @AfterEach + void resetStaticState() { + Hooks.disableAutomaticContextPropagation(); + } + + @BeforeAll + static void initializeThreadLocalAccessors() { + ContextRegistry globalRegistry = ContextRegistry.getInstance(); + globalRegistry.registerThreadLocalAccessor(THREADLOCAL_KEY, THREADLOCAL_VALUE); + } + + @AfterAll + static void removeThreadLocalAccessors() { + ContextRegistry globalRegistry = ContextRegistry.getInstance(); + globalRegistry.removeThreadLocalAccessor(THREADLOCAL_KEY); + } + + @Test + void shouldNotConfigurePropagationByDefault() { + AtomicReference threadLocalValue = new AtomicReference<>(); + this.contextRunner.run((applicationContext) -> { + Mono.just("test") + .doOnNext((element) -> threadLocalValue.set(THREADLOCAL_VALUE.get())) + .contextWrite(Context.of(THREADLOCAL_KEY, "updated")) + .block(); + assertThat(threadLocalValue.get()).isEqualTo("initial"); + }); + } + + @Test + void shouldConfigurePropagationIfSetToAuto() { + AtomicReference threadLocalValue = new AtomicReference<>(); + this.contextRunner.withPropertyValues("spring.reactor.context-propagation=auto").run((applicationContext) -> { + Mono.just("test") + .doOnNext((element) -> threadLocalValue.set(THREADLOCAL_VALUE.get())) + .contextWrite(Context.of(THREADLOCAL_KEY, "updated")) + .block(); + assertThat(threadLocalValue.get()).isEqualTo("updated"); + }); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketPropertiesTests.java new file mode 100644 index 000000000000..eefd1b7212de --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketPropertiesTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.rsocket; + +import org.junit.jupiter.api.Test; +import reactor.netty.http.server.WebsocketServerSpec; + +import org.springframework.boot.autoconfigure.rsocket.RSocketProperties.Server.Spec; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link RSocketProperties}. + * + * @author Stephane Nicoll + */ +class RSocketPropertiesTests { + + @Test + void defaultServerSpecValuesAreConsistent() { + WebsocketServerSpec spec = WebsocketServerSpec.builder().build(); + Spec properties = new RSocketProperties().getServer().getSpec(); + assertThat(properties.getProtocols()).isEqualTo(spec.protocols()); + assertThat(properties.getMaxFramePayloadLength().toBytes()).isEqualTo(spec.maxFramePayloadLength()); + assertThat(properties.isHandlePing()).isEqualTo(spec.handlePing()); + assertThat(properties.isCompress()).isEqualTo(spec.compress()); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfigurationTests.java index 0ddf63903d7b..62569319c380 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfigurationTests.java @@ -34,7 +34,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.codec.CharSequenceEncoder; import org.springframework.core.codec.StringDecoder; -import org.springframework.http.client.reactive.ReactorResourceFactory; +import org.springframework.http.client.ReactorResourceFactory; import org.springframework.messaging.rsocket.RSocketStrategies; import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; import org.springframework.util.unit.DataSize; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2WebSecurityConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2WebSecurityConfigurationTests.java index 190859e9f7a3..b9cf9f8b6b3f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2WebSecurityConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2WebSecurityConfigurationTests.java @@ -48,6 +48,7 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.ObjectUtils; +import org.springframework.web.filter.CompositeFilter; import static org.assertj.core.api.Assertions.assertThat; @@ -68,7 +69,7 @@ void securityConfigurerConfiguresOAuth2Login() { .run((context) -> { ClientRegistrationRepository expected = context.getBean(ClientRegistrationRepository.class); ClientRegistrationRepository actual = (ClientRegistrationRepository) ReflectionTestUtils.getField( - getFilters(context, OAuth2LoginAuthenticationFilter.class).get(0), + getSecurityFilters(context, OAuth2LoginAuthenticationFilter.class).get(0), "clientRegistrationRepository"); assertThat(isEqual(expected.findByRegistrationId("first"), actual.findByRegistrationId("first"))) .isTrue(); @@ -85,7 +86,7 @@ void securityConfigurerConfiguresAuthorizationCode() { .run((context) -> { ClientRegistrationRepository expected = context.getBean(ClientRegistrationRepository.class); ClientRegistrationRepository actual = (ClientRegistrationRepository) ReflectionTestUtils.getField( - getFilters(context, OAuth2AuthorizationCodeGrantFilter.class).get(0), + getSecurityFilters(context, OAuth2AuthorizationCodeGrantFilter.class).get(0), "clientRegistrationRepository"); assertThat(isEqual(expected.findByRegistrationId("first"), actual.findByRegistrationId("first"))) .isTrue(); @@ -98,8 +99,8 @@ void securityConfigurerConfiguresAuthorizationCode() { void securityConfigurerBacksOffWhenClientRegistrationBeanAbsent() { this.contextRunner.withUserConfiguration(TestConfig.class, OAuth2WebSecurityConfiguration.class) .run((context) -> { - assertThat(getFilters(context, OAuth2LoginAuthenticationFilter.class)).isEmpty(); - assertThat(getFilters(context, OAuth2AuthorizationCodeGrantFilter.class)).isEmpty(); + assertThat(getSecurityFilters(context, OAuth2LoginAuthenticationFilter.class)).isEmpty(); + assertThat(getSecurityFilters(context, OAuth2AuthorizationCodeGrantFilter.class)).isEmpty(); }); } @@ -124,8 +125,8 @@ void securityFilterChainConfigBacksOffWhenOtherSecurityFilterChainBeanPresent() this.contextRunner.withConfiguration(AutoConfigurations.of(WebMvcAutoConfiguration.class)) .withUserConfiguration(TestSecurityFilterChainConfiguration.class, OAuth2WebSecurityConfiguration.class) .run((context) -> { - assertThat(getFilters(context, OAuth2LoginAuthenticationFilter.class)).isEmpty(); - assertThat(getFilters(context, OAuth2AuthorizationCodeGrantFilter.class)).isEmpty(); + assertThat(getSecurityFilters(context, OAuth2LoginAuthenticationFilter.class)).isEmpty(); + assertThat(getSecurityFilters(context, OAuth2AuthorizationCodeGrantFilter.class)).isEmpty(); assertThat(context).getBean(OAuth2AuthorizedClientService.class).isNotNull(); }); } @@ -137,8 +138,8 @@ void securityFilterChainConfigConditionalOnSecurityFilterChainClass() { OAuth2WebSecurityConfiguration.class) .withClassLoader(new FilteredClassLoader(SecurityFilterChain.class)) .run((context) -> { - assertThat(getFilters(context, OAuth2LoginAuthenticationFilter.class)).isEmpty(); - assertThat(getFilters(context, OAuth2AuthorizationCodeGrantFilter.class)).isEmpty(); + assertThat(getSecurityFilters(context, OAuth2LoginAuthenticationFilter.class)).isEmpty(); + assertThat(getSecurityFilters(context, OAuth2AuthorizationCodeGrantFilter.class)).isEmpty(); }); } @@ -164,11 +165,29 @@ void authorizedClientRepositoryBeanIsConditionalOnMissingBean() { }); } - private List getFilters(AssertableWebApplicationContext context, Class filter) { - FilterChainProxy filterChain = (FilterChainProxy) context.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN); - List filterChains = filterChain.getFilterChains(); - List filters = filterChains.get(0).getFilters(); - return filters.stream().filter(filter::isInstance).toList(); + private List getSecurityFilters(AssertableWebApplicationContext context, Class filter) { + return getSecurityFilterChain(context).getFilters().stream().filter(filter::isInstance).toList(); + } + + private SecurityFilterChain getSecurityFilterChain(AssertableWebApplicationContext context) { + Filter springSecurityFilterChain = context.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN, Filter.class); + FilterChainProxy filterChainProxy = getFilterChainProxy(springSecurityFilterChain); + SecurityFilterChain securityFilterChain = filterChainProxy.getFilterChains().get(0); + return securityFilterChain; + } + + private FilterChainProxy getFilterChainProxy(Filter filter) { + if (filter instanceof FilterChainProxy filterChainProxy) { + return filterChainProxy; + } + if (filter instanceof CompositeFilter) { + List filters = (List) ReflectionTestUtils.getField(filter, "filters"); + return (FilterChainProxy) filters.stream() + .filter(FilterChainProxy.class::isInstance) + .findFirst() + .orElseThrow(); + } + throw new IllegalStateException("No FilterChainProxy found"); } private boolean isEqual(ClientRegistration reg1, ClientRegistration reg2) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/JwtConverterCustomizationsArgumentsProvider.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/JwtConverterCustomizationsArgumentsProvider.java new file mode 100644 index 000000000000..7ef5d20fa91f --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/JwtConverterCustomizationsArgumentsProvider.java @@ -0,0 +1,94 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.security.oauth2.resource; + +import java.time.Instant; +import java.util.UUID; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Named; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; + +import org.springframework.security.oauth2.jwt.Jwt; + +/** + * {@link ArgumentsProvider Arguments provider} supplying different Spring Boot properties + * to customize JWT converter behavior, JWT token for conversion, expected principal name + * and expected authorities. + * + * @author Yan Kardziyaka + */ +public final class JwtConverterCustomizationsArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext extensionContext) { + String customPrefix = "CUSTOM_AUTHORITY_PREFIX_"; + String customDelimiter = "[~,#:]"; + String customAuthoritiesClaim = "custom_authorities"; + String customPrincipalClaim = "custom_principal"; + String jwkSetUriProperty = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com"; + String authorityPrefixProperty = "spring.security.oauth2.resourceserver.jwt.authority-prefix=" + customPrefix; + String authoritiesDelimiterProperty = "spring.security.oauth2.resourceserver.jwt.authorities-claim-delimiter=" + + customDelimiter; + String authoritiesClaimProperty = "spring.security.oauth2.resourceserver.jwt.authorities-claim-name=" + + customAuthoritiesClaim; + String principalClaimProperty = "spring.security.oauth2.resourceserver.jwt.principal-claim-name=" + + customPrincipalClaim; + String[] customPrefixProps = { jwkSetUriProperty, authorityPrefixProperty }; + String[] customDelimiterProps = { jwkSetUriProperty, authorityPrefixProperty, authoritiesDelimiterProperty }; + String[] customAuthoritiesClaimProps = { jwkSetUriProperty, authoritiesClaimProperty }; + String[] customPrincipalClaimProps = { jwkSetUriProperty, principalClaimProperty }; + String[] allJwtConverterProps = { jwkSetUriProperty, authorityPrefixProperty, authoritiesDelimiterProperty, + authoritiesClaimProperty, principalClaimProperty }; + String[] jwtScopes = { "custom_scope0", "custom_scope1" }; + String subjectValue = UUID.randomUUID().toString(); + String customPrincipalValue = UUID.randomUUID().toString(); + Jwt.Builder jwtBuilder = Jwt.withTokenValue("token") + .header("alg", "none") + .expiresAt(Instant.MAX) + .issuedAt(Instant.MIN) + .issuer("https://issuer.example.org") + .jti("jti") + .notBefore(Instant.MIN) + .subject(subjectValue) + .claim(customPrincipalClaim, customPrincipalValue); + Jwt noAuthoritiesCustomizationsJwt = jwtBuilder.claim("scp", jwtScopes[0] + " " + jwtScopes[1]).build(); + Jwt customAuthoritiesDelimiterJwt = jwtBuilder.claim("scp", jwtScopes[0] + "~" + jwtScopes[1]).build(); + Jwt customAuthoritiesClaimJwt = jwtBuilder.claim("scp", null) + .claim(customAuthoritiesClaim, jwtScopes[0] + " " + jwtScopes[1]) + .build(); + Jwt customAuthoritiesClaimAndDelimiterJwt = jwtBuilder.claim("scp", null) + .claim(customAuthoritiesClaim, jwtScopes[0] + "~" + jwtScopes[1]) + .build(); + String[] customPrefixAuthorities = { customPrefix + jwtScopes[0], customPrefix + jwtScopes[1] }; + String[] defaultPrefixAuthorities = { "SCOPE_" + jwtScopes[0], "SCOPE_" + jwtScopes[1] }; + return Stream.of( + Arguments.of(Named.named("Custom prefix for GrantedAuthority", customPrefixProps), + noAuthoritiesCustomizationsJwt, subjectValue, customPrefixAuthorities), + Arguments.of(Named.named("Custom delimiter for JWT scopes", customDelimiterProps), + customAuthoritiesDelimiterJwt, subjectValue, customPrefixAuthorities), + Arguments.of(Named.named("Custom JWT authority claim name", customAuthoritiesClaimProps), + customAuthoritiesClaimJwt, subjectValue, defaultPrefixAuthorities), + Arguments.of(Named.named("Custom JWT principal claim name", customPrincipalClaimProps), + noAuthoritiesCustomizationsJwt, customPrincipalValue, defaultPrefixAuthorities), + Arguments.of(Named.named("All JWT converter customizations", allJwtConverterProps), + customAuthoritiesClaimAndDelimiterJwt, customPrincipalValue, customPrefixAuthorities)); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java index cc5a652d10c2..771c5cb4b677 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,14 +17,17 @@ package org.springframework.boot.autoconfigure.security.oauth2.resource.reactive; import java.io.IOException; -import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.time.Duration; import java.time.Instant; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.function.Consumer; import java.util.stream.Stream; import com.fasterxml.jackson.core.JsonProcessingException; @@ -33,12 +36,16 @@ import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.assertj.core.api.InstanceOfAssertFactories; +import org.assertj.core.api.ThrowingConsumer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ArgumentsSource; import org.mockito.InOrder; import reactor.core.publisher.Mono; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.security.oauth2.resource.JwtConverterCustomizationsArgumentsProvider; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; @@ -48,10 +55,12 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver; import org.springframework.security.config.BeanIds; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator; @@ -59,13 +68,13 @@ import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimValidator; import org.springframework.security.oauth2.jwt.JwtIssuerValidator; -import org.springframework.security.oauth2.jwt.JwtTimestampValidator; import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.SupplierReactiveJwtDecoder; import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager; import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenReactiveAuthenticationManager; +import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector; import org.springframework.security.web.server.MatcherSecurityWebFilterChain; import org.springframework.security.web.server.SecurityWebFilterChain; @@ -87,6 +96,8 @@ * @author HaiTao Zhang * @author Anastasiia Losieva * @author Mushtaq Ahmed + * @author Roman Golovin + * @author Yan Kardziyaka */ class ReactiveOAuth2ResourceServerAutoConfigurationTests { @@ -438,7 +449,6 @@ void autoConfigurationWhenIntrospectionUriAvailableShouldBeConditionalOnClass() .run((context) -> assertThat(context).doesNotHaveBean(ReactiveOpaqueTokenIntrospector.class)); } - @SuppressWarnings("unchecked") @Test void autoConfigurationShouldConfigureResourceServerUsingJwkSetUriAndIssuerUri() throws Exception { this.server = new MockWebServer(); @@ -454,15 +464,11 @@ void autoConfigurationShouldConfigureResourceServerUsingJwkSetUriAndIssuerUri() .run((context) -> { assertThat(context).hasSingleBean(ReactiveJwtDecoder.class); ReactiveJwtDecoder reactiveJwtDecoder = context.getBean(ReactiveJwtDecoder.class); - DelegatingOAuth2TokenValidator jwtValidator = (DelegatingOAuth2TokenValidator) ReflectionTestUtils - .getField(reactiveJwtDecoder, "jwtValidator"); - Collection> tokenValidators = (Collection>) ReflectionTestUtils - .getField(jwtValidator, "tokenValidators"); - assertThat(tokenValidators).hasAtLeastOneElementOfType(JwtIssuerValidator.class); + validate(jwt().claim("iss", issuer), reactiveJwtDecoder, + (validators) -> assertThat(validators).hasAtLeastOneElementOfType(JwtIssuerValidator.class)); }); } - @SuppressWarnings("unchecked") @Test void autoConfigurationShouldNotConfigureIssuerUriAndAudienceJwtValidatorIfPropertyNotConfigured() throws Exception { this.server = new MockWebServer(); @@ -476,13 +482,8 @@ void autoConfigurationShouldNotConfigureIssuerUriAndAudienceJwtValidatorIfProper .run((context) -> { assertThat(context).hasSingleBean(ReactiveJwtDecoder.class); ReactiveJwtDecoder reactiveJwtDecoder = context.getBean(ReactiveJwtDecoder.class); - DelegatingOAuth2TokenValidator jwtValidator = (DelegatingOAuth2TokenValidator) ReflectionTestUtils - .getField(reactiveJwtDecoder, "jwtValidator"); - Collection> tokenValidators = (Collection>) ReflectionTestUtils - .getField(jwtValidator, "tokenValidators"); - assertThat(tokenValidators).hasExactlyElementsOfTypes(JwtTimestampValidator.class); - assertThat(tokenValidators).doesNotHaveAnyElementsOfTypes(JwtClaimValidator.class); - assertThat(tokenValidators).doesNotHaveAnyElementsOfTypes(JwtIssuerValidator.class); + validate(jwt(), reactiveJwtDecoder, + (validators) -> assertThat(validators).hasSize(2).noneSatisfy(audClaimValidator())); }); } @@ -502,39 +503,15 @@ void autoConfigurationShouldConfigureIssuerAndAudienceJwtValidatorIfPropertyProv .run((context) -> { assertThat(context).hasSingleBean(ReactiveJwtDecoder.class); ReactiveJwtDecoder reactiveJwtDecoder = context.getBean(ReactiveJwtDecoder.class); - validate(issuerUri, reactiveJwtDecoder); + validate( + jwt().claim("iss", URI.create(issuerUri).toURL()) + .claim("aud", List.of("https://test-audience.com")), + reactiveJwtDecoder, + (validators) -> assertThat(validators).hasAtLeastOneElementOfType(JwtIssuerValidator.class) + .satisfiesOnlyOnce(audClaimValidator())); }); } - @SuppressWarnings("unchecked") - private void validate(String issuerUri, ReactiveJwtDecoder jwtDecoder) throws MalformedURLException { - DelegatingOAuth2TokenValidator jwtValidator = (DelegatingOAuth2TokenValidator) ReflectionTestUtils - .getField(jwtDecoder, "jwtValidator"); - Jwt.Builder builder = jwt().claim("aud", Collections.singletonList("https://test-audience.com")); - if (issuerUri != null) { - builder.claim("iss", new URL(issuerUri)); - } - Jwt jwt = builder.build(); - assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); - Collection> delegates = (Collection>) ReflectionTestUtils - .getField(jwtValidator, "tokenValidators"); - validateDelegates(issuerUri, delegates); - } - - @SuppressWarnings("unchecked") - private void validateDelegates(String issuerUri, Collection> delegates) { - assertThat(delegates).hasAtLeastOneElementOfType(JwtClaimValidator.class); - OAuth2TokenValidator delegatingValidator = delegates.stream() - .filter((v) -> v instanceof DelegatingOAuth2TokenValidator) - .findFirst() - .get(); - Collection> nestedDelegates = (Collection>) ReflectionTestUtils - .getField(delegatingValidator, "tokenValidators"); - if (issuerUri != null) { - assertThat(nestedDelegates).hasAtLeastOneElementOfType(JwtIssuerValidator.class); - } - } - @SuppressWarnings("unchecked") @Test void autoConfigurationShouldConfigureAudienceValidatorIfPropertyProvidedAndIssuerUri() throws Exception { @@ -552,7 +529,12 @@ void autoConfigurationShouldConfigureAudienceValidatorIfPropertyProvidedAndIssue Mono jwtDecoderSupplier = (Mono) ReflectionTestUtils .getField(supplierJwtDecoderBean, "jwtDecoderMono"); ReactiveJwtDecoder jwtDecoder = jwtDecoderSupplier.block(); - validate(issuerUri, jwtDecoder); + validate( + jwt().claim("iss", URI.create(issuerUri).toURL()) + .claim("aud", List.of("https://test-audience.com")), + jwtDecoder, + (validators) -> assertThat(validators).hasAtLeastOneElementOfType(JwtIssuerValidator.class) + .satisfiesOnlyOnce(audClaimValidator())); }); } @@ -570,7 +552,33 @@ void autoConfigurationShouldConfigureAudienceValidatorIfPropertyProvidedAndPubli .run((context) -> { assertThat(context).hasSingleBean(ReactiveJwtDecoder.class); ReactiveJwtDecoder jwtDecoder = context.getBean(ReactiveJwtDecoder.class); - validate(null, jwtDecoder); + validate(jwt().claim("aud", List.of("https://test-audience.com")), jwtDecoder, + (validators) -> assertThat(validators).satisfiesOnlyOnce(audClaimValidator())); + }); + } + + @SuppressWarnings("unchecked") + @Test + void autoConfigurationShouldConfigureCustomValidators() throws Exception { + this.server = new MockWebServer(); + this.server.start(); + String path = "test"; + String issuer = this.server.url(path).toString(); + String cleanIssuerPath = cleanIssuerPath(issuer); + setupMockResponse(cleanIssuerPath); + String issuerUri = "http://" + this.server.getHostName() + ":" + this.server.getPort() + "/" + path; + this.contextRunner + .withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com", + "spring.security.oauth2.resourceserver.jwt.issuer-uri=" + issuerUri) + .withUserConfiguration(CustomJwtClaimValidatorConfig.class) + .run((context) -> { + assertThat(context).hasSingleBean(ReactiveJwtDecoder.class); + ReactiveJwtDecoder reactiveJwtDecoder = context.getBean(ReactiveJwtDecoder.class); + OAuth2TokenValidator customValidator = (OAuth2TokenValidator) context + .getBean("customJwtClaimValidator"); + validate(jwt().claim("iss", URI.create(issuerUri).toURL()).claim("custom_claim", "custom_claim_value"), + reactiveJwtDecoder, (validators) -> assertThat(validators).contains(customValidator) + .hasAtLeastOneElementOfType(JwtIssuerValidator.class)); }); } @@ -600,6 +608,94 @@ void audienceValidatorWhenAudienceInvalid() throws Exception { }); } + @SuppressWarnings("unchecked") + @Test + void customValidatorWhenInvalid() throws Exception { + this.server = new MockWebServer(); + this.server.start(); + String path = "test"; + String issuer = this.server.url(path).toString(); + String cleanIssuerPath = cleanIssuerPath(issuer); + setupMockResponse(cleanIssuerPath); + String issuerUri = "http://" + this.server.getHostName() + ":" + this.server.getPort() + "/" + path; + this.contextRunner + .withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com", + "spring.security.oauth2.resourceserver.jwt.issuer-uri=" + issuerUri) + .withUserConfiguration(CustomJwtClaimValidatorConfig.class) + .run((context) -> { + assertThat(context).hasSingleBean(ReactiveJwtDecoder.class); + ReactiveJwtDecoder jwtDecoder = context.getBean(ReactiveJwtDecoder.class); + DelegatingOAuth2TokenValidator jwtValidator = (DelegatingOAuth2TokenValidator) ReflectionTestUtils + .getField(jwtDecoder, "jwtValidator"); + Jwt jwt = jwt().claim("iss", new URL(issuerUri)).claim("custom_claim", "invalid_value").build(); + assertThat(jwtValidator.validate(jwt).hasErrors()).isTrue(); + }); + } + + @Test + void shouldNotConfigureJwtConverterIfNoPropertiesAreSet() { + this.contextRunner + .run((context) -> assertThat(context).doesNotHaveBean(ReactiveJwtAuthenticationConverter.class)); + } + + @Test + void shouldConfigureJwtConverterIfPrincipalClaimNameIsSet() { + this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.principal-claim-name=dummy") + .run((context) -> assertThat(context).hasSingleBean(ReactiveJwtAuthenticationConverter.class)); + } + + @Test + void shouldConfigureJwtConverterIfAuthorityPrefixIsSet() { + this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authority-prefix=dummy") + .run((context) -> assertThat(context).hasSingleBean(ReactiveJwtAuthenticationConverter.class)); + } + + @Test + void shouldConfigureJwtConverterIfAuthorityClaimsNameIsSet() { + this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authorities-claim-name=dummy") + .run((context) -> assertThat(context).hasSingleBean(ReactiveJwtAuthenticationConverter.class)); + } + + @ParameterizedTest(name = "{0}") + @ArgumentsSource(JwtConverterCustomizationsArgumentsProvider.class) + void autoConfigurationShouldConfigureResourceServerWithJwtConverterCustomizations(String[] properties, Jwt jwt, + String expectedPrincipal, String[] expectedAuthorities) { + this.contextRunner.withPropertyValues(properties).run((context) -> { + ReactiveJwtAuthenticationConverter converter = context.getBean(ReactiveJwtAuthenticationConverter.class); + AbstractAuthenticationToken token = converter.convert(jwt).block(); + assertThat(token).isNotNull().extracting(AbstractAuthenticationToken::getName).isEqualTo(expectedPrincipal); + assertThat(token.getAuthorities()).extracting(GrantedAuthority::getAuthority) + .containsExactlyInAnyOrder(expectedAuthorities); + assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class); + assertFilterConfiguredWithJwtAuthenticationManager(context); + }); + } + + @Test + void jwtAuthenticationConverterByJwtConfigIsConditionalOnMissingBean() { + String propertiesPrincipalClaim = "principal_from_properties"; + String propertiesPrincipalValue = "from_props"; + String userConfigPrincipalValue = "from_user_config"; + this.contextRunner + .withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com", + "spring.security.oauth2.resourceserver.jwt.principal-claim-name=" + propertiesPrincipalClaim) + .withUserConfiguration(CustomJwtConverterConfig.class) + .run((context) -> { + ReactiveJwtAuthenticationConverter converter = context + .getBean(ReactiveJwtAuthenticationConverter.class); + Jwt jwt = jwt().claim(propertiesPrincipalClaim, propertiesPrincipalValue) + .claim(CustomJwtConverterConfig.PRINCIPAL_CLAIM, userConfigPrincipalValue) + .build(); + AbstractAuthenticationToken token = converter.convert(jwt).block(); + assertThat(token).isNotNull() + .extracting(AbstractAuthenticationToken::getName) + .isEqualTo(userConfigPrincipalValue) + .isNotEqualTo(propertiesPrincipalValue); + assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class); + assertFilterConfiguredWithJwtAuthenticationManager(context); + }); + } + private void assertFilterConfiguredWithJwtAuthenticationManager(AssertableReactiveWebApplicationContext context) { MatcherSecurityWebFilterChain filterChain = (MatcherSecurityWebFilterChain) context .getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN); @@ -683,6 +779,37 @@ static Jwt.Builder jwt() { .subject("mock-test-subject"); } + @SuppressWarnings("unchecked") + private void validate(Jwt.Builder builder, ReactiveJwtDecoder jwtDecoder, + ThrowingConsumer>> validatorsConsumer) { + DelegatingOAuth2TokenValidator jwtValidator = (DelegatingOAuth2TokenValidator) ReflectionTestUtils + .getField(jwtDecoder, "jwtValidator"); + assertThat(jwtValidator.validate(builder.build()).hasErrors()).isFalse(); + validatorsConsumer.accept(extractValidators(jwtValidator)); + } + + @SuppressWarnings("unchecked") + private List> extractValidators(DelegatingOAuth2TokenValidator delegatingValidator) { + Collection> delegates = (Collection>) ReflectionTestUtils + .getField(delegatingValidator, "tokenValidators"); + List> extracted = new ArrayList<>(); + for (OAuth2TokenValidator delegate : delegates) { + if (delegate instanceof DelegatingOAuth2TokenValidator delegatingDelegate) { + extracted.addAll(extractValidators(delegatingDelegate)); + } + else { + extracted.add(delegate); + } + } + return extracted; + } + + private Consumer> audClaimValidator() { + return (validator) -> assertThat(validator).isInstanceOf(JwtClaimValidator.class) + .extracting("claim") + .isEqualTo("aud"); + } + @EnableWebFluxSecurity static class TestConfig { @@ -740,4 +867,28 @@ SecurityWebFilterChain testSpringSecurityFilterChain(ServerHttpSecurity http) { } + @Configuration(proxyBeanMethods = false) + static class CustomJwtClaimValidatorConfig { + + @Bean + JwtClaimValidator customJwtClaimValidator() { + return new JwtClaimValidator<>("custom_claim", "custom_claim_value"::equals); + } + + } + + @Configuration(proxyBeanMethods = false) + static class CustomJwtConverterConfig { + + static String PRINCIPAL_CLAIM = "principal_from_user_configuration"; + + @Bean + ReactiveJwtAuthenticationConverter customReactiveJwtAuthenticationConverter() { + ReactiveJwtAuthenticationConverter converter = new ReactiveJwtAuthenticationConverter(); + converter.setPrincipalClaimName(PRINCIPAL_CLAIM); + return converter; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java index 878415ec9f9f..9de6d030a840 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,16 @@ package org.springframework.boot.autoconfigure.security.oauth2.resource.servlet; -import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.time.Instant; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import java.util.function.Supplier; import com.fasterxml.jackson.core.JsonProcessingException; @@ -33,11 +35,15 @@ import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.assertj.core.api.InstanceOfAssertFactories; +import org.assertj.core.api.ThrowingConsumer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ArgumentsSource; import org.mockito.InOrder; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.security.oauth2.resource.JwtConverterCustomizationsArgumentsProvider; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; @@ -48,19 +54,21 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.config.BeanIds; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimValidator; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtIssuerValidator; -import org.springframework.security.oauth2.jwt.JwtTimestampValidator; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.jwt.SupplierJwtDecoder; import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter; @@ -80,6 +88,8 @@ * @author Artsiom Yudovin * @author HaiTao Zhang * @author Mushtaq Ahmed + * @author Roman Golovin + * @author Yan Kardziyaka */ class OAuth2ResourceServerAutoConfigurationTests { @@ -190,8 +200,8 @@ void autoConfigurationUsingPublicKeyValueWithMultipleJwsAlgorithmsShouldFail() { }); } - @Test @SuppressWarnings("unchecked") + @Test void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws Exception { this.server = new MockWebServer(); this.server.start(); @@ -215,8 +225,8 @@ void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws E assertThat(this.server.getRequestCount()).isEqualTo(2); } - @Test @SuppressWarnings("unchecked") + @Test void autoConfigurationShouldConfigureResourceServerUsingOidcRfc8414IssuerUri() throws Exception { this.server = new MockWebServer(); this.server.start(); @@ -240,8 +250,8 @@ void autoConfigurationShouldConfigureResourceServerUsingOidcRfc8414IssuerUri() t assertThat(this.server.getRequestCount()).isEqualTo(3); } - @Test @SuppressWarnings("unchecked") + @Test void autoConfigurationShouldConfigureResourceServerUsingOAuthIssuerUri() throws Exception { this.server = new MockWebServer(); this.server.start(); @@ -420,7 +430,7 @@ void autoConfigurationWhenJwkSetUriAndIntrospectionUriAvailable() { assertThat(context).hasSingleBean(OpaqueTokenIntrospector.class); assertThat(context).hasSingleBean(JwtDecoder.class); assertThat(getBearerTokenFilter(context)).extracting("authenticationManagerResolver.arg$1.providers") - .asList() + .asInstanceOf(InstanceOfAssertFactories.LIST) .hasAtLeastOneElementOfType(JwtAuthenticationProvider.class); }); } @@ -472,9 +482,8 @@ void autoConfigurationShouldConfigureResourceServerUsingJwkSetUriAndIssuerUri() .run((context) -> { assertThat(context).hasSingleBean(JwtDecoder.class); JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class); - assertThat(jwtDecoder).extracting("jwtValidator.tokenValidators") - .asInstanceOf(InstanceOfAssertFactories.collection(OAuth2TokenValidator.class)) - .hasAtLeastOneElementOfType(JwtIssuerValidator.class); + validate(jwt().claim("iss", issuer), jwtDecoder, + (validators) -> assertThat(validators).hasAtLeastOneElementOfType(JwtIssuerValidator.class)); }); } @@ -491,11 +500,8 @@ void autoConfigurationShouldNotConfigureIssuerUriAndAudienceJwtValidatorIfProper .run((context) -> { assertThat(context).hasSingleBean(JwtDecoder.class); JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class); - assertThat(jwtDecoder).extracting("jwtValidator.tokenValidators") - .asInstanceOf(InstanceOfAssertFactories.collection(OAuth2TokenValidator.class)) - .hasExactlyElementsOfTypes(JwtTimestampValidator.class) - .doesNotHaveAnyElementsOfTypes(JwtClaimValidator.class) - .doesNotHaveAnyElementsOfTypes(JwtIssuerValidator.class); + validate(jwt(), jwtDecoder, + (validators) -> assertThat(validators).hasSize(2).noneSatisfy(audClaimValidator())); }); } @@ -515,7 +521,12 @@ void autoConfigurationShouldConfigureAudienceAndIssuerJwtValidatorIfPropertyProv .run((context) -> { assertThat(context).hasSingleBean(JwtDecoder.class); JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class); - validate(issuerUri, jwtDecoder); + validate( + jwt().claim("iss", URI.create(issuerUri).toURL()) + .claim("aud", List.of("https://test-audience.com")), + jwtDecoder, + (validators) -> assertThat(validators).hasAtLeastOneElementOfType(JwtIssuerValidator.class) + .satisfiesOnlyOnce(audClaimValidator())); }); } @@ -536,36 +547,39 @@ void autoConfigurationShouldConfigureAudienceValidatorIfPropertyProvidedAndIssue Supplier jwtDecoderSupplier = (Supplier) ReflectionTestUtils .getField(supplierJwtDecoderBean, "delegate"); JwtDecoder jwtDecoder = jwtDecoderSupplier.get(); - validate(issuerUri, jwtDecoder); + validate( + jwt().claim("iss", URI.create(issuerUri).toURL()) + .claim("aud", List.of("https://test-audience.com")), + jwtDecoder, + (validators) -> assertThat(validators).hasAtLeastOneElementOfType(JwtIssuerValidator.class) + .satisfiesOnlyOnce(audClaimValidator())); }); } @SuppressWarnings("unchecked") - private void validate(String issuerUri, JwtDecoder jwtDecoder) throws MalformedURLException { - DelegatingOAuth2TokenValidator jwtValidator = (DelegatingOAuth2TokenValidator) ReflectionTestUtils - .getField(jwtDecoder, "jwtValidator"); - Jwt.Builder builder = jwt().claim("aud", Collections.singletonList("https://test-audience.com")); - if (issuerUri != null) { - builder.claim("iss", new URL(issuerUri)); - } - Jwt jwt = builder.build(); - assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); - Collection> delegates = (Collection>) ReflectionTestUtils - .getField(jwtValidator, "tokenValidators"); - validateDelegates(issuerUri, delegates); - } - - private void validateDelegates(String issuerUri, Collection> delegates) { - assertThat(delegates).hasAtLeastOneElementOfType(JwtClaimValidator.class); - OAuth2TokenValidator delegatingValidator = delegates.stream() - .filter((v) -> v instanceof DelegatingOAuth2TokenValidator) - .findFirst() - .get(); - if (issuerUri != null) { - assertThat(delegatingValidator).extracting("tokenValidators") - .asInstanceOf(InstanceOfAssertFactories.collection(OAuth2TokenValidator.class)) - .hasAtLeastOneElementOfType(JwtIssuerValidator.class); - } + @Test + void autoConfigurationShouldConfigureCustomValidators() throws Exception { + this.server = new MockWebServer(); + this.server.start(); + String path = "test"; + String issuer = this.server.url(path).toString(); + String cleanIssuerPath = cleanIssuerPath(issuer); + setupMockResponse(cleanIssuerPath); + String issuerUri = "http://" + this.server.getHostName() + ":" + this.server.getPort() + "/" + path; + this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=" + issuerUri) + .withUserConfiguration(CustomJwtClaimValidatorConfig.class) + .run((context) -> { + SupplierJwtDecoder supplierJwtDecoderBean = context.getBean(SupplierJwtDecoder.class); + Supplier jwtDecoderSupplier = (Supplier) ReflectionTestUtils + .getField(supplierJwtDecoderBean, "delegate"); + JwtDecoder jwtDecoder = jwtDecoderSupplier.get(); + assertThat(context).hasBean("customJwtClaimValidator"); + OAuth2TokenValidator customValidator = (OAuth2TokenValidator) context + .getBean("customJwtClaimValidator"); + validate(jwt().claim("iss", URI.create(issuerUri).toURL()).claim("custom_claim", "custom_claim_value"), + jwtDecoder, (validators) -> assertThat(validators).contains(customValidator) + .hasAtLeastOneElementOfType(JwtIssuerValidator.class)); + }); } @Test @@ -582,7 +596,8 @@ void autoConfigurationShouldConfigureAudienceValidatorIfPropertyProvidedAndPubli .run((context) -> { assertThat(context).hasSingleBean(JwtDecoder.class); JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class); - validate(null, jwtDecoder); + validate(jwt().claim("aud", List.of("https://test-audience.com")), jwtDecoder, + (validators) -> assertThat(validators).satisfiesOnlyOnce(audClaimValidator())); }); } @@ -631,6 +646,68 @@ void opaqueTokenSecurityConfigurerBacksOffWhenSecurityFilterChainBeanIsPresent() .run((context) -> assertThat(context).hasSingleBean(SecurityFilterChain.class)); } + @ParameterizedTest(name = "{0}") + @ArgumentsSource(JwtConverterCustomizationsArgumentsProvider.class) + void autoConfigurationShouldConfigureResourceServerWithJwtConverterCustomizations(String[] properties, Jwt jwt, + String expectedPrincipal, String[] expectedAuthorities) { + this.contextRunner.withPropertyValues(properties).run((context) -> { + JwtAuthenticationConverter converter = context.getBean(JwtAuthenticationConverter.class); + AbstractAuthenticationToken token = converter.convert(jwt); + assertThat(token).isNotNull().extracting(AbstractAuthenticationToken::getName).isEqualTo(expectedPrincipal); + assertThat(token.getAuthorities()).extracting(GrantedAuthority::getAuthority) + .containsExactlyInAnyOrder(expectedAuthorities); + assertThat(context).hasSingleBean(JwtDecoder.class); + assertThat(getBearerTokenFilter(context)).isNotNull(); + }); + } + + @Test + void shouldNotConfigureJwtConverterIfNoPropertiesAreSet() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(JwtAuthenticationConverter.class)); + } + + @Test + void shouldConfigureJwtConverterIfPrincipalClaimNameIsSet() { + this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.principal-claim-name=dummy") + .run((context) -> assertThat(context).hasSingleBean(JwtAuthenticationConverter.class)); + } + + @Test + void shouldConfigureJwtConverterIfAuthorityPrefixIsSet() { + this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authority-prefix=dummy") + .run((context) -> assertThat(context).hasSingleBean(JwtAuthenticationConverter.class)); + } + + @Test + void shouldConfigureJwtConverterIfAuthorityClaimsNameIsSet() { + this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authorities-claim-name=dummy") + .run((context) -> assertThat(context).hasSingleBean(JwtAuthenticationConverter.class)); + } + + @Test + void jwtAuthenticationConverterByJwtConfigIsConditionalOnMissingBean() { + String propertiesPrincipalClaim = "principal_from_properties"; + String propertiesPrincipalValue = "from_props"; + String userConfigPrincipalValue = "from_user_config"; + this.contextRunner + .withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com", + "spring.security.oauth2.resourceserver.jwt.principal-claim-name=" + propertiesPrincipalClaim) + .withUserConfiguration(CustomJwtConverterConfig.class) + .run((context) -> { + JwtAuthenticationConverter converter = context.getBean(JwtAuthenticationConverter.class); + Jwt jwt = jwt().claim(propertiesPrincipalClaim, propertiesPrincipalValue) + .claim(CustomJwtConverterConfig.PRINCIPAL_CLAIM, userConfigPrincipalValue) + .build(); + AbstractAuthenticationToken token = converter.convert(jwt); + assertThat(token).isNotNull() + .extracting(AbstractAuthenticationToken::getName) + .isEqualTo(userConfigPrincipalValue) + .isNotEqualTo(propertiesPrincipalValue); + assertThat(context).hasSingleBean(JwtDecoder.class); + assertThat(getBearerTokenFilter(context)).isNotNull(); + }); + } + private Filter getBearerTokenFilter(AssertableWebApplicationContext context) { FilterChainProxy filterChain = (FilterChainProxy) context.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN); List filterChains = filterChain.getFilterChains(); @@ -692,6 +769,37 @@ static Jwt.Builder jwt() { .subject("mock-test-subject"); } + @SuppressWarnings("unchecked") + private void validate(Jwt.Builder builder, JwtDecoder jwtDecoder, + ThrowingConsumer>> validatorsConsumer) { + DelegatingOAuth2TokenValidator jwtValidator = (DelegatingOAuth2TokenValidator) ReflectionTestUtils + .getField(jwtDecoder, "jwtValidator"); + assertThat(jwtValidator.validate(builder.build()).hasErrors()).isFalse(); + validatorsConsumer.accept(extractValidators(jwtValidator)); + } + + @SuppressWarnings("unchecked") + private List> extractValidators(DelegatingOAuth2TokenValidator delegatingValidator) { + Collection> delegates = (Collection>) ReflectionTestUtils + .getField(delegatingValidator, "tokenValidators"); + List> extracted = new ArrayList<>(); + for (OAuth2TokenValidator delegate : delegates) { + if (delegate instanceof DelegatingOAuth2TokenValidator delegatingDelegate) { + extracted.addAll(extractValidators(delegatingDelegate)); + } + else { + extracted.add(delegate); + } + } + return extracted; + } + + private Consumer> audClaimValidator() { + return (validator) -> assertThat(validator).isInstanceOf(JwtClaimValidator.class) + .extracting("claim") + .isEqualTo("aud"); + } + @Configuration(proxyBeanMethods = false) @EnableWebSecurity static class TestConfig { @@ -745,4 +853,28 @@ SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception } + @Configuration(proxyBeanMethods = false) + static class CustomJwtClaimValidatorConfig { + + @Bean + JwtClaimValidator customJwtClaimValidator() { + return new JwtClaimValidator<>("custom_claim", "custom_claim_value"::equals); + } + + } + + @Configuration(proxyBeanMethods = false) + static class CustomJwtConverterConfig { + + static String PRINCIPAL_CLAIM = "principal_from_user_configuration"; + + @Bean + JwtAuthenticationConverter customJwtAuthenticationConverter() { + JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); + converter.setPrincipalClaimName(PRINCIPAL_CLAIM); + return converter; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerAutoConfigurationTests.java index 3f2a89e73769..c1c043eecdb2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerAutoConfigurationTests.java @@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -59,8 +60,11 @@ void autoConfigurationConditionalOnClassOauth2Authorization() { } @Test + @ClassPathExclusions({ "spring-security-oauth2-client-*.jar", "spring-security-oauth2-resource-server-*.jar", + "spring-security-saml2-service-provider-*.jar" }) void autoConfigurationDoesNotCauseUserDetailsServiceToBackOff() { - this.contextRunner.run((context) -> assertThat(context).hasBean("inMemoryUserDetailsManager")); + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(UserDetailsServiceAutoConfiguration.class) + .hasBean("inMemoryUserDetailsManager")); } @Test diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapperTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapperTests.java index 8fbfb1eb4f2f..5773df36336b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapperTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -113,6 +113,37 @@ void getAuthorizationServerSettingsWhenValidParametersShouldAdapt() { oidc.setUserInfoUri("/user"); AuthorizationServerSettings settings = this.mapper.asAuthorizationServerSettings(); assertThat(settings.getIssuer()).isEqualTo("https://example.com"); + assertThat(settings.isMultipleIssuersAllowed()).isFalse(); + assertThat(settings.getAuthorizationEndpoint()).isEqualTo("/authorize"); + assertThat(settings.getDeviceAuthorizationEndpoint()).isEqualTo("/device_authorization"); + assertThat(settings.getDeviceVerificationEndpoint()).isEqualTo("/device_verification"); + assertThat(settings.getTokenEndpoint()).isEqualTo("/token"); + assertThat(settings.getJwkSetEndpoint()).isEqualTo("/jwks"); + assertThat(settings.getTokenRevocationEndpoint()).isEqualTo("/revoke"); + assertThat(settings.getTokenIntrospectionEndpoint()).isEqualTo("/introspect"); + assertThat(settings.getOidcLogoutEndpoint()).isEqualTo("/logout"); + assertThat(settings.getOidcClientRegistrationEndpoint()).isEqualTo("/register"); + assertThat(settings.getOidcUserInfoEndpoint()).isEqualTo("/user"); + } + + @Test + void getAuthorizationServerSettingsWhenMultipleIssuersAllowedShouldAdapt() { + this.properties.setMultipleIssuersAllowed(true); + OAuth2AuthorizationServerProperties.Endpoint endpoints = this.properties.getEndpoint(); + endpoints.setAuthorizationUri("/authorize"); + endpoints.setDeviceAuthorizationUri("/device_authorization"); + endpoints.setDeviceVerificationUri("/device_verification"); + endpoints.setTokenUri("/token"); + endpoints.setJwkSetUri("/jwks"); + endpoints.setTokenRevocationUri("/revoke"); + endpoints.setTokenIntrospectionUri("/introspect"); + OAuth2AuthorizationServerProperties.OidcEndpoint oidc = endpoints.getOidc(); + oidc.setLogoutUri("/logout"); + oidc.setClientRegistrationUri("/register"); + oidc.setUserInfoUri("/user"); + AuthorizationServerSettings settings = this.mapper.asAuthorizationServerSettings(); + assertThat(settings.getIssuer()).isNull(); + assertThat(settings.isMultipleIssuersAllowed()).isTrue(); assertThat(settings.getAuthorizationEndpoint()).isEqualTo("/authorize"); assertThat(settings.getDeviceAuthorizationEndpoint()).isEqualTo("/device_authorization"); assertThat(settings.getDeviceVerificationEndpoint()).isEqualTo("/device_verification"); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveSecurityAutoConfigurationTests.java index 4436ab7360e0..006f7b228f1a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveSecurityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveSecurityAutoConfigurationTests.java @@ -24,7 +24,11 @@ import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.web.reactive.config.WebFluxConfigurer; @@ -38,21 +42,46 @@ */ class ReactiveSecurityAutoConfigurationTests { - private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner(); + private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class)); @Test void backsOffWhenWebFilterChainProxyBeanPresent() { - this.contextRunner.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class)) - .withUserConfiguration(WebFilterChainProxyConfiguration.class) + this.contextRunner.withUserConfiguration(WebFilterChainProxyConfiguration.class) .run((context) -> assertThat(context).hasSingleBean(WebFilterChainProxy.class)); } @Test - void enablesWebFluxSecurity() { + void autoConfiguresDenyAllReactiveAuthenticationManagerWhenNoAlternativeIsAvailable() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ReactiveSecurityAutoConfiguration.class) + .hasBean("denyAllAuthenticationManager")); + } + + @Test + void enablesWebFluxSecurityWhenUserDetailsServiceIsPresent() { + this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(WebFilterChainProxy.class); + assertThat(context).doesNotHaveBean("denyAllAuthenticationManager"); + }); + } + + @Test + void enablesWebFluxSecurityWhenReactiveAuthenticationManagerIsPresent() { this.contextRunner - .withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class, - ReactiveUserDetailsServiceAutoConfiguration.class)) - .run((context) -> assertThat(context).getBean(WebFilterChainProxy.class).isNotNull()); + .withBean(ReactiveAuthenticationManager.class, () -> mock(ReactiveAuthenticationManager.class)) + .run((context) -> { + assertThat(context).hasSingleBean(WebFilterChainProxy.class); + assertThat(context).doesNotHaveBean("denyAllAuthenticationManager"); + }); + } + + @Test + void enablesWebFluxSecurityWhenSecurityWebFilterChainIsPresent() { + this.contextRunner.withBean(SecurityWebFilterChain.class, () -> mock(SecurityWebFilterChain.class)) + .run((context) -> { + assertThat(context).hasSingleBean(WebFilterChainProxy.class); + assertThat(context).doesNotHaveBean("denyAllAuthenticationManager"); + }); } @Test @@ -60,8 +89,7 @@ void autoConfigurationIsConditionalOnClass() { this.contextRunner .withClassLoader(new FilteredClassLoader(Flux.class, EnableWebFluxSecurity.class, WebFilterChainProxy.class, WebFluxConfigurer.class)) - .withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class, - ReactiveUserDetailsServiceAutoConfiguration.class)) + .withUserConfiguration(UserDetailsServiceConfiguration.class) .run((context) -> assertThat(context).doesNotHaveBean(WebFilterChainProxy.class)); } @@ -75,4 +103,15 @@ WebFilterChainProxy webFilterChainProxy() { } + @Configuration(proxyBeanMethods = false) + static class UserDetailsServiceConfiguration { + + @Bean + MapReactiveUserDetailsService userDetailsService() { + return new MapReactiveUserDetailsService( + User.withUsername("alice").password("secret").roles("admin").build()); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfigurationTests.java index e2389c322a89..1b673b6dc962 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -39,6 +40,7 @@ import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector; @@ -58,15 +60,21 @@ class ReactiveUserDetailsServiceAutoConfigurationTests { @Test void configuresADefaultUser() { - this.contextRunner.withUserConfiguration(TestSecurityConfiguration.class).run((context) -> { - ReactiveUserDetailsService userDetailsService = context.getBean(ReactiveUserDetailsService.class); - assertThat(userDetailsService.findByUsername("user").block(Duration.ofSeconds(30))).isNotNull(); - }); + this.contextRunner + .withClassLoader( + new FilteredClassLoader(ClientRegistrationRepository.class, ReactiveOpaqueTokenIntrospector.class)) + .withUserConfiguration(TestSecurityConfiguration.class) + .run((context) -> { + ReactiveUserDetailsService userDetailsService = context.getBean(ReactiveUserDetailsService.class); + assertThat(userDetailsService.findByUsername("user").block(Duration.ofSeconds(30))).isNotNull(); + }); } @Test void userDetailsServiceWhenRSocketConfigured() { new ApplicationContextRunner() + .withClassLoader( + new FilteredClassLoader(ClientRegistrationRepository.class, ReactiveOpaqueTokenIntrospector.class)) .withConfiguration(AutoConfigurations.of(ReactiveUserDetailsServiceAutoConfiguration.class, RSocketMessagingAutoConfiguration.class, RSocketStrategiesAutoConfiguration.class)) .withUserConfiguration(TestRSocketSecurityConfiguration.class) @@ -109,20 +117,33 @@ void doesNotConfigureDefaultUserIfResourceServerWithJWTIsUsed() { } @Test - void doesNotConfigureDefaultUserIfResourceServerWithOpaqueIsUsed() { - this.contextRunner.withUserConfiguration(ReactiveOpaqueTokenIntrospectorConfiguration.class).run((context) -> { - assertThat(context).hasSingleBean(ReactiveOpaqueTokenIntrospector.class); - assertThat(context).doesNotHaveBean(ReactiveUserDetailsService.class); - }); + void doesNotConfigureDefaultUserIfResourceServerIsPresent() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ReactiveUserDetailsService.class)); + } + + @Test + void configuresDefaultUserWhenResourceServerIsPresentAndUsernameIsConfigured() { + this.contextRunner.withPropertyValues("spring.security.user.name=carol") + .run((context) -> assertThat(context).hasSingleBean(ReactiveUserDetailsService.class)); + } + + @Test + void configuresDefaultUserWhenResourceServerIsPresentAndPasswordIsConfigured() { + this.contextRunner.withPropertyValues("spring.security.user.password=p4ssw0rd") + .run((context) -> assertThat(context).hasSingleBean(ReactiveUserDetailsService.class)); } @Test void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() { - this.contextRunner.withUserConfiguration(TestSecurityConfiguration.class).run(((context) -> { - MapReactiveUserDetailsService userDetailsService = context.getBean(MapReactiveUserDetailsService.class); - String password = userDetailsService.findByUsername("user").block(Duration.ofSeconds(30)).getPassword(); - assertThat(password).startsWith("{noop}"); - })); + this.contextRunner + .withClassLoader( + new FilteredClassLoader(ClientRegistrationRepository.class, ReactiveOpaqueTokenIntrospector.class)) + .withUserConfiguration(TestSecurityConfiguration.class) + .run(((context) -> { + MapReactiveUserDetailsService userDetailsService = context.getBean(MapReactiveUserDetailsService.class); + String password = userDetailsService.findByUsername("user").block(Duration.ofSeconds(30)).getPassword(); + assertThat(password).startsWith("{noop}"); + })); } @Test @@ -142,7 +163,10 @@ void userDetailsServiceWhenPasswordEncoderBeanPresent() { } private void testPasswordEncoding(Class configClass, String providedPassword, String expectedPassword) { - this.contextRunner.withUserConfiguration(configClass) + this.contextRunner + .withClassLoader( + new FilteredClassLoader(ClientRegistrationRepository.class, ReactiveOpaqueTokenIntrospector.class)) + .withUserConfiguration(configClass) .withPropertyValues("spring.security.user.password=" + providedPassword) .run(((context) -> { MapReactiveUserDetailsService userDetailsService = context.getBean(MapReactiveUserDetailsService.class); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/rsocket/RSocketSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/rsocket/RSocketSecurityAutoConfigurationTests.java index 95d807269000..a479bb5d1da7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/rsocket/RSocketSecurityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/rsocket/RSocketSecurityAutoConfigurationTests.java @@ -22,12 +22,15 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration; import org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration; -import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; import org.springframework.boot.rsocket.server.RSocketServerCustomizer; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; import org.springframework.security.config.annotation.rsocket.RSocketSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; import org.springframework.security.messaging.handler.invocation.reactive.AuthenticationPrincipalArgumentResolver; import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor; @@ -42,9 +45,9 @@ class RSocketSecurityAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(ReactiveUserDetailsServiceAutoConfiguration.class, - RSocketSecurityAutoConfiguration.class, RSocketMessagingAutoConfiguration.class, - RSocketStrategiesAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(RSocketSecurityAutoConfiguration.class, + RSocketMessagingAutoConfiguration.class, RSocketStrategiesAutoConfiguration.class)) + .withUserConfiguration(UserDetailsServiceConfiguration.class); @Test void autoConfigurationEnablesRSocketSecurity() { @@ -81,4 +84,15 @@ void autoConfigurationAddsCustomizerForAuthenticationPrincipalArgumentResolver() }); } + @Configuration(proxyBeanMethods = false) + static class UserDetailsServiceConfiguration { + + @Bean + MapReactiveUserDetailsService userDetailsService() { + return new MapReactiveUserDetailsService( + User.withUsername("alice").password("secret").roles("admin").build()); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfigurationTests.java index b3f51121b243..5c8b4ae27da4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfigurationTests.java @@ -46,6 +46,8 @@ import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.filter.CompositeFilter; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -208,7 +210,7 @@ void relyingPartyRegistrationRepositoryShouldBeConditionalOnMissingBean() { @Test void samlLoginShouldBeConfigured() { this.contextRunner.withPropertyValues(getPropertyValues()) - .run((context) -> assertThat(hasFilter(context, Saml2WebSsoAuthenticationFilter.class)).isTrue()); + .run((context) -> assertThat(hasSecurityFilter(context, Saml2WebSsoAuthenticationFilter.class)).isTrue()); } @Test @@ -216,7 +218,7 @@ void samlLoginShouldBackOffWhenASecurityFilterChainBeanIsPresent() { this.contextRunner.withConfiguration(AutoConfigurations.of(WebMvcAutoConfiguration.class)) .withUserConfiguration(TestSecurityFilterChainConfig.class) .withPropertyValues(getPropertyValues()) - .run((context) -> assertThat(hasFilter(context, Saml2WebSsoAuthenticationFilter.class)).isFalse()); + .run((context) -> assertThat(hasSecurityFilter(context, Saml2WebSsoAuthenticationFilter.class)).isFalse()); } @Test @@ -229,7 +231,7 @@ void samlLoginShouldShouldBeConditionalOnSecurityWebFilterClass() { @Test void samlLogoutShouldBeConfigured() { this.contextRunner.withPropertyValues(getPropertyValues()) - .run((context) -> assertThat(hasFilter(context, Saml2LogoutRequestFilter.class)).isTrue()); + .run((context) -> assertThat(hasSecurityFilter(context, Saml2LogoutRequestFilter.class)).isTrue()); } private String[] getPropertyValuesWithoutSigningCredentials(boolean signRequests) { @@ -323,11 +325,29 @@ private String[] getPropertyValues() { PREFIX + ".foo.acs.binding=redirect" }; } - private boolean hasFilter(AssertableWebApplicationContext context, Class filter) { - FilterChainProxy filterChain = (FilterChainProxy) context.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN); - List filterChains = filterChain.getFilterChains(); - List filters = filterChains.get(0).getFilters(); - return filters.stream().anyMatch(filter::isInstance); + private boolean hasSecurityFilter(AssertableWebApplicationContext context, Class filter) { + return getSecurityFilterChain(context).getFilters().stream().anyMatch(filter::isInstance); + } + + private SecurityFilterChain getSecurityFilterChain(AssertableWebApplicationContext context) { + Filter springSecurityFilterChain = context.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN, Filter.class); + FilterChainProxy filterChainProxy = getFilterChainProxy(springSecurityFilterChain); + SecurityFilterChain securityFilterChain = filterChainProxy.getFilterChains().get(0); + return securityFilterChain; + } + + private FilterChainProxy getFilterChainProxy(Filter filter) { + if (filter instanceof FilterChainProxy filterChainProxy) { + return filterChainProxy; + } + if (filter instanceof CompositeFilter) { + List filters = (List) ReflectionTestUtils.getField(filter, "filters"); + return (FilterChainProxy) filters.stream() + .filter(FilterChainProxy.class::isInstance) + .findFirst() + .orElseThrow(); + } + throw new IllegalStateException("No FilterChainProxy found"); } private void setupMockResponse(MockWebServer server, Resource resourceBody) throws Exception { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyPropertiesTests.java index ff0e8106209f..64d2f2d7dc37 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyPropertiesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ * Tests for {@link Saml2RelyingPartyProperties}. * * @author Madhura Bhave + * @author Lasse Wulff */ class Saml2RelyingPartyPropertiesTests { @@ -102,6 +103,13 @@ void customizeSsoSignRequestsIsNullByDefault() { .getSignRequest()).isNull(); } + @Test + void customizeNameIdFormat() { + bind("spring.security.saml2.relyingparty.registration.simplesamlphp.name-id-format", "sampleNameIdFormat"); + assertThat(this.properties.getRegistration().get("simplesamlphp").getNameIdFormat()) + .isEqualTo("sampleNameIdFormat"); + } + private void bind(String name, String value) { bind(Collections.singletonMap(name, value)); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/SecurityFilterAutoConfigurationEarlyInitializationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/SecurityFilterAutoConfigurationEarlyInitializationTests.java index c186f9c009c2..40bcf8bddc53 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/SecurityFilterAutoConfigurationEarlyInitializationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/SecurityFilterAutoConfigurationEarlyInitializationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,8 @@ import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; +import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.context.annotation.Bean; @@ -63,6 +65,9 @@ class SecurityFilterAutoConfigurationEarlyInitializationTests { Pattern.MULTILINE); @Test + @DirtiesUrlFactories + @ClassPathExclusions({ "spring-security-oauth2-client-*.jar", "spring-security-oauth2-resource-server-*.jar", + "spring-security-saml2-service-provider-*.jar" }) void testSecurityFilterDoesNotCauseEarlyInitialization(CapturedOutput output) { try (AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext()) { TestPropertyValues.of("server.port:0").applyTo(context); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfigurationTests.java index b0375a3afc85..6de9275db26b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.security.servlet; import java.util.Collections; +import java.util.function.Function; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -24,6 +25,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; @@ -64,7 +66,7 @@ class UserDetailsServiceAutoConfigurationTests { @Test void testDefaultUsernamePassword(CapturedOutput output) { - this.contextRunner.run((context) -> { + this.contextRunner.with(noOtherFormsOfAuthenticationOnTheClasspath()).run((context) -> { UserDetailsService manager = context.getBean(UserDetailsService.class); assertThat(output).contains("Using generated security password:"); assertThat(manager.loadUserByUsername("user")).isNotNull(); @@ -126,11 +128,13 @@ void defaultUserNotCreatedIfResourceServerWithJWTIsUsed() { @Test void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() { - this.contextRunner.withUserConfiguration(TestSecurityConfiguration.class).run(((context) -> { - InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class); - String password = userDetailsService.loadUserByUsername("user").getPassword(); - assertThat(password).startsWith("{noop}"); - })); + this.contextRunner.with(noOtherFormsOfAuthenticationOnTheClasspath()) + .withUserConfiguration(TestSecurityConfiguration.class) + .run(((context) -> { + InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class); + String password = userDetailsService.loadUserByUsername("user").getPassword(); + assertThat(password).startsWith("{noop}"); + })); } @Test @@ -150,20 +154,55 @@ void userDetailsServiceWhenPasswordEncoderBeanPresent() { } @Test - void userDetailsServiceWhenClientRegistrationRepositoryBeanPresent() { - this.contextRunner.withUserConfiguration(TestConfigWithClientRegistrationRepository.class) + void userDetailsServiceWhenClientRegistrationRepositoryPresent() { + this.contextRunner + .withClassLoader( + new FilteredClassLoader(OpaqueTokenIntrospector.class, RelyingPartyRegistrationRepository.class)) + .run(((context) -> assertThat(context).doesNotHaveBean(InMemoryUserDetailsManager.class))); + } + + @Test + void userDetailsServiceWhenOpaqueTokenIntrospectorPresent() { + this.contextRunner + .withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, + RelyingPartyRegistrationRepository.class)) .run(((context) -> assertThat(context).doesNotHaveBean(InMemoryUserDetailsManager.class))); } @Test - void userDetailsServiceWhenRelyingPartyRegistrationRepositoryBeanPresent() { + void userDetailsServiceWhenRelyingPartyRegistrationRepositoryPresent() { this.contextRunner - .withBean(RelyingPartyRegistrationRepository.class, () -> mock(RelyingPartyRegistrationRepository.class)) + .withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class)) .run(((context) -> assertThat(context).doesNotHaveBean(InMemoryUserDetailsManager.class))); } + @Test + void userDetailsServiceWhenRelyingPartyRegistrationRepositoryPresentAndUsernameConfigured() { + this.contextRunner + .withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class)) + .withPropertyValues("spring.security.user.name=alice") + .run(((context) -> assertThat(context).hasSingleBean(InMemoryUserDetailsManager.class))); + } + + @Test + void userDetailsServiceWhenRelyingPartyRegistrationRepositoryPresentAndPasswordConfigured() { + this.contextRunner + .withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class)) + .withPropertyValues("spring.security.user.password=secret") + .run(((context) -> assertThat(context).hasSingleBean(InMemoryUserDetailsManager.class))); + } + + private Function noOtherFormsOfAuthenticationOnTheClasspath() { + return (contextRunner) -> contextRunner + .withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class, + RelyingPartyRegistrationRepository.class)); + } + private void testPasswordEncoding(Class configClass, String providedPassword, String expectedPassword) { - this.contextRunner.withUserConfiguration(configClass) + this.contextRunner.with(noOtherFormsOfAuthenticationOnTheClasspath()) + .withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class, + RelyingPartyRegistrationRepository.class)) + .withUserConfiguration(configClass) .withPropertyValues("spring.security.user.password=" + providedPassword) .run(((context) -> { InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfigurationTests.java index b4e02caa7926..05ea524454e8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,10 +24,8 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener; import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration; import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer; -import org.springframework.boot.logging.LogLevel; import org.springframework.boot.r2dbc.init.R2dbcScriptDatabaseInitializer; import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer; import org.springframework.boot.sql.init.DatabaseInitializationSettings; @@ -66,7 +64,6 @@ void whenConnectionFactoryIsAvailableThenR2dbcInitializerIsAutoConfigured() { @Test void whenConnectionFactoryIsAvailableAndModeIsNeverThenInitializerIsNotAutoConfigured() { this.contextRunner.withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class)) - .withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO)) .withPropertyValues("spring.sql.init.mode:never") .run((context) -> assertThat(context).doesNotHaveBean(AbstractScriptDatabaseInitializer.class)); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/BundleContentNotWatchableFailureAnalyzerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/BundleContentNotWatchableFailureAnalyzerTests.java new file mode 100644 index 000000000000..fdaf0bda8373 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/BundleContentNotWatchableFailureAnalyzerTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.ssl; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.diagnostics.FailureAnalysis; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link BundleContentNotWatchableFailureAnalyzer}. + * + * @author Moritz Halbritter + */ +class BundleContentNotWatchableFailureAnalyzerTests { + + @Test + void shouldAnalyze() { + FailureAnalysis failureAnalysis = performAnalysis(null); + assertThat(failureAnalysis.getDescription()).isEqualTo( + "The content of 'name' is not watchable. Only 'file:' resources are watchable, but 'classpath:resource.pem' has been set"); + assertThat(failureAnalysis.getAction()) + .isEqualTo("Update your application to correct the invalid configuration:\n" + + "Either use a watchable resource, or disable bundle reloading by setting reload-on-update = false on the bundle."); + } + + @Test + void shouldAnalyzeWithBundle() { + FailureAnalysis failureAnalysis = performAnalysis("bundle-1"); + assertThat(failureAnalysis.getDescription()).isEqualTo( + "The content of 'name' from bundle 'bundle-1' is not watchable'. Only 'file:' resources are watchable, but 'classpath:resource.pem' has been set"); + } + + private FailureAnalysis performAnalysis(String bundle) { + BundleContentNotWatchableException failure = new BundleContentNotWatchableException( + new BundleContentProperty("name", "classpath:resource.pem")); + if (bundle != null) { + failure = failure.withBundleName(bundle); + } + return new BundleContentNotWatchableFailureAnalyzer().analyze(failure); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/BundleContentPropertyTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/BundleContentPropertyTests.java new file mode 100644 index 000000000000..caf822da60e4 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/BundleContentPropertyTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.ssl; + +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link BundleContentProperty}. + * + * @author Moritz Halbritter + * @author Phillip Webb + */ +class BundleContentPropertyTests { + + private static final String PEM_TEXT = """ + -----BEGIN CERTIFICATE----- + -----END CERTIFICATE----- + """; + + @Test + void isPemContentWhenValueIsPemTextReturnsTrue() { + BundleContentProperty property = new BundleContentProperty("name", PEM_TEXT); + assertThat(property.isPemContent()).isTrue(); + } + + @Test + void isPemContentWhenValueIsNotPemTextReturnsFalse() { + BundleContentProperty property = new BundleContentProperty("name", "file.pem"); + assertThat(property.isPemContent()).isFalse(); + } + + @Test + void hasValueWhenHasValueReturnsTrue() { + BundleContentProperty property = new BundleContentProperty("name", "file.pem"); + assertThat(property.hasValue()).isTrue(); + } + + @Test + void hasValueWhenHasNullValueReturnsFalse() { + BundleContentProperty property = new BundleContentProperty("name", null); + assertThat(property.hasValue()).isFalse(); + } + + @Test + void hasValueWhenHasEmptyValueReturnsFalse() { + BundleContentProperty property = new BundleContentProperty("name", ""); + assertThat(property.hasValue()).isFalse(); + } + + @Test + void toWatchPathWhenNotPathThrowsException() { + BundleContentProperty property = new BundleContentProperty("name", PEM_TEXT); + assertThatIllegalStateException().isThrownBy(property::toWatchPath) + .withMessage("Unable to convert value of property 'name' to a path"); + } + + @Test + void toWatchPathWhenPathReturnsPath() throws URISyntaxException { + URL resource = getClass().getResource("keystore.jks"); + Path file = Path.of(resource.toURI()).toAbsolutePath(); + BundleContentProperty property = new BundleContentProperty("name", file.toString()); + assertThat(property.toWatchPath()).isEqualTo(file); + } + + @Test + void shouldThrowBundleContentNotWatchableExceptionIfContentIsNotWatchable() { + BundleContentProperty property = new BundleContentProperty("name", "https://example.com/"); + assertThatExceptionOfType(BundleContentNotWatchableException.class).isThrownBy(property::toWatchPath) + .withMessageContaining("Only 'file:' resources are watchable"); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/CertificateMatcherTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/CertificateMatcherTests.java new file mode 100644 index 000000000000..c1516bdf6358 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/CertificateMatcherTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.ssl; + +import java.security.cert.Certificate; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link CertificateMatcher}. + * + * @author Moritz Halbritter + * @author Phillip Webb + */ +class CertificateMatcherTests { + + @CertificateMatchingTest + void matchesWhenMatchReturnsTrue(CertificateMatchingTestSource source) { + CertificateMatcher matcher = new CertificateMatcher(source.privateKey()); + assertThat(matcher.matches(source.matchingCertificate())).isTrue(); + } + + @CertificateMatchingTest + void matchesWhenNoMatchReturnsFalse(CertificateMatchingTestSource source) { + CertificateMatcher matcher = new CertificateMatcher(source.privateKey()); + for (Certificate nonMatchingCertificate : source.nonMatchingCertificates()) { + assertThat(matcher.matches(nonMatchingCertificate)).isFalse(); + } + } + + @CertificateMatchingTest + void matchesAnyWhenNoneMatchReturnsFalse(CertificateMatchingTestSource source) { + CertificateMatcher matcher = new CertificateMatcher(source.privateKey()); + assertThat(matcher.matchesAny(source.nonMatchingCertificates())).isFalse(); + } + + @CertificateMatchingTest + void matchesAnyWhenOneMatchesReturnsTrue(CertificateMatchingTestSource source) { + CertificateMatcher matcher = new CertificateMatcher(source.privateKey()); + List certificates = new ArrayList<>(source.nonMatchingCertificates()); + certificates.add(source.matchingCertificate()); + assertThat(matcher.matchesAny(certificates)).isTrue(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/CertificateMatchingTest.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/CertificateMatchingTest.java new file mode 100644 index 000000000000..fcf5e39d6534 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/CertificateMatchingTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.ssl; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Annotation for a {@code ParameterizedTest @ParameterizedTest} with a + * {@link CertificateMatchingTestSource} parameter. + * + * @author Phillip Webb + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ParameterizedTest(name = "{0}") +@MethodSource("org.springframework.boot.autoconfigure.ssl.CertificateMatchingTestSource#create") +public @interface CertificateMatchingTest { + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/CertificateMatchingTestSource.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/CertificateMatchingTestSource.java new file mode 100644 index 000000000000..8c12302d1590 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/CertificateMatchingTestSource.java @@ -0,0 +1,125 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.ssl; + +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.NamedParameterSpec; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Source used with {@link CertificateMatchingTest @CertificateMatchingTest} annotated + * tests that provides access to useful test material. + * + * @param algorithm the algorithm + * @param privateKey the private key to use for matching + * @param matchingCertificate a certificate that matches the private key + * @param nonMatchingCertificates a list of certificate that do not match the private key + * @param nonMatchingPrivateKeys a list of private keys that do not match the certificate + * @author Moritz Halbritter + * @author Phillip Webb + */ +record CertificateMatchingTestSource(CertificateMatchingTestSource.Algorithm algorithm, PrivateKey privateKey, + X509Certificate matchingCertificate, List nonMatchingCertificates, + List nonMatchingPrivateKeys) { + + private static final List ALGORITHMS; + static { + List algorithms = new ArrayList<>(); + Stream.of("RSA", "DSA", "ed25519", "ed448").map(Algorithm::of).forEach(algorithms::add); + Stream.of("secp256r1", "secp521r1").map(Algorithm::ec).forEach(algorithms::add); + ALGORITHMS = List.copyOf(algorithms); + } + + CertificateMatchingTestSource(Algorithm algorithm, KeyPair matchingKeyPair, List nonMatchingKeyPairs) { + this(algorithm, matchingKeyPair.getPrivate(), asCertificate(matchingKeyPair), + nonMatchingKeyPairs.stream().map(CertificateMatchingTestSource::asCertificate).toList(), + nonMatchingKeyPairs.stream().map(KeyPair::getPrivate).toList()); + } + + private static X509Certificate asCertificate(KeyPair keyPair) { + X509Certificate certificate = mock(X509Certificate.class); + given(certificate.getPublicKey()).willReturn(keyPair.getPublic()); + return certificate; + } + + @Override + public String toString() { + return this.algorithm.toString(); + } + + static List create() + throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + Map keyPairs = new LinkedHashMap<>(); + for (Algorithm algorithm : ALGORITHMS) { + keyPairs.put(algorithm, algorithm.generateKeyPair()); + } + List parameters = new ArrayList<>(); + keyPairs.forEach((algorithm, matchingKeyPair) -> { + List nonMatchingKeyPairs = new ArrayList<>(keyPairs.values()); + nonMatchingKeyPairs.remove(matchingKeyPair); + parameters.add(new CertificateMatchingTestSource(algorithm, matchingKeyPair, nonMatchingKeyPairs)); + }); + return List.copyOf(parameters); + } + + /** + * An individual algorithm. + * + * @param name the algorithm name + * @param spec the algorithm spec or {@code null} + */ + record Algorithm(String name, AlgorithmParameterSpec spec) { + + KeyPair generateKeyPair() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + KeyPairGenerator generator = KeyPairGenerator.getInstance(this.name); + if (this.spec != null) { + generator.initialize(this.spec); + } + return generator.generateKeyPair(); + } + + @Override + public String toString() { + String spec = (this.spec instanceof NamedParameterSpec namedSpec) ? namedSpec.getName() : ""; + return this.name + ((!spec.isEmpty()) ? ":" + spec : ""); + } + + static Algorithm of(String name) { + return new Algorithm(name, null); + } + + static Algorithm ec(String curve) { + return new Algorithm("EC", new ECGenParameterSpec(curve)); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/FileWatcherTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/FileWatcherTests.java new file mode 100644 index 000000000000..5b3e18e3f79e --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/FileWatcherTests.java @@ -0,0 +1,200 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.ssl; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.fail; + +/** + * Tests for {@link FileWatcher}. + * + * @author Moritz Halbritter + */ +class FileWatcherTests { + + private FileWatcher fileWatcher; + + @BeforeEach + void setUp() { + this.fileWatcher = new FileWatcher(Duration.ofMillis(10)); + } + + @AfterEach + void tearDown() throws IOException { + this.fileWatcher.close(); + } + + @Test + void shouldTriggerOnFileCreation(@TempDir Path tempDir) throws Exception { + Path newFile = tempDir.resolve("new-file.txt"); + WaitingCallback callback = new WaitingCallback(); + this.fileWatcher.watch(Set.of(tempDir), callback); + Files.createFile(newFile); + callback.expectChanges(); + } + + @Test + void shouldTriggerOnFileDeletion(@TempDir Path tempDir) throws Exception { + Path deletedFile = tempDir.resolve("deleted-file.txt"); + Files.createFile(deletedFile); + WaitingCallback callback = new WaitingCallback(); + this.fileWatcher.watch(Set.of(tempDir), callback); + Files.delete(deletedFile); + callback.expectChanges(); + } + + @Test + void shouldTriggerOnFileModification(@TempDir Path tempDir) throws Exception { + Path deletedFile = tempDir.resolve("modified-file.txt"); + Files.createFile(deletedFile); + WaitingCallback callback = new WaitingCallback(); + this.fileWatcher.watch(Set.of(tempDir), callback); + Files.writeString(deletedFile, "Some content"); + callback.expectChanges(); + } + + @Test + void shouldWatchFile(@TempDir Path tempDir) throws Exception { + Path watchedFile = tempDir.resolve("watched.txt"); + Files.createFile(watchedFile); + WaitingCallback callback = new WaitingCallback(); + this.fileWatcher.watch(Set.of(watchedFile), callback); + Files.writeString(watchedFile, "Some content"); + callback.expectChanges(); + } + + @Test + void shouldIgnoreNotWatchedFiles(@TempDir Path tempDir) throws Exception { + Path watchedFile = tempDir.resolve("watched.txt"); + Path notWatchedFile = tempDir.resolve("not-watched.txt"); + Files.createFile(watchedFile); + Files.createFile(notWatchedFile); + WaitingCallback callback = new WaitingCallback(); + this.fileWatcher.watch(Set.of(watchedFile), callback); + Files.writeString(notWatchedFile, "Some content"); + callback.expectNoChanges(); + } + + @Test + void shouldFailIfDirectoryOrFileDoesNotExist(@TempDir Path tempDir) { + Path directory = tempDir.resolve("dir1"); + assertThatExceptionOfType(UncheckedIOException.class) + .isThrownBy(() -> this.fileWatcher.watch(Set.of(directory), new WaitingCallback())) + .withMessage("Failed to register paths for watching: [%s]".formatted(directory)); + } + + @Test + void shouldNotFailIfDirectoryIsRegisteredMultipleTimes(@TempDir Path tempDir) { + WaitingCallback callback = new WaitingCallback(); + assertThatCode(() -> { + this.fileWatcher.watch(Set.of(tempDir), callback); + this.fileWatcher.watch(Set.of(tempDir), callback); + }).doesNotThrowAnyException(); + } + + @Test + void shouldNotFailIfStoppedMultipleTimes(@TempDir Path tempDir) { + WaitingCallback callback = new WaitingCallback(); + this.fileWatcher.watch(Set.of(tempDir), callback); + assertThatCode(() -> { + this.fileWatcher.close(); + this.fileWatcher.close(); + }).doesNotThrowAnyException(); + } + + @Test + void testRelativeFiles() throws Exception { + Path watchedFile = Path.of(UUID.randomUUID() + ".txt"); + Files.createFile(watchedFile); + try { + WaitingCallback callback = new WaitingCallback(); + this.fileWatcher.watch(Set.of(watchedFile), callback); + Files.delete(watchedFile); + callback.expectChanges(); + } + finally { + Files.deleteIfExists(watchedFile); + } + } + + @Test + void testRelativeDirectories() throws Exception { + Path watchedDirectory = Path.of(UUID.randomUUID() + "/"); + Path file = watchedDirectory.resolve("file.txt"); + Files.createDirectory(watchedDirectory); + try { + WaitingCallback callback = new WaitingCallback(); + this.fileWatcher.watch(Set.of(watchedDirectory), callback); + Files.createFile(file); + callback.expectChanges(); + } + finally { + Files.deleteIfExists(file); + Files.deleteIfExists(watchedDirectory); + } + } + + private static final class WaitingCallback implements Runnable { + + private final CountDownLatch latch = new CountDownLatch(1); + + volatile boolean changed = false; + + @Override + public void run() { + this.changed = true; + this.latch.countDown(); + } + + void expectChanges() throws InterruptedException { + waitForChanges(true); + assertThat(this.changed).as("changed").isTrue(); + } + + void expectNoChanges() throws InterruptedException { + waitForChanges(false); + assertThat(this.changed).as("changed").isFalse(); + } + + void waitForChanges(boolean fail) throws InterruptedException { + if (!this.latch.await(5, TimeUnit.SECONDS)) { + if (fail) { + fail("Timeout while waiting for changes"); + } + } + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundleTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundleTests.java index 1b745328310a..d6b770a3d927 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundleTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundleTests.java @@ -20,12 +20,15 @@ import java.security.KeyStore; import java.security.cert.Certificate; import java.util.Set; +import java.util.function.Consumer; import org.junit.jupiter.api.Test; import org.springframework.boot.ssl.SslBundle; +import org.springframework.util.function.ThrowingConsumer; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link PropertiesSslBundle}. @@ -35,6 +38,8 @@ */ class PropertiesSslBundleTests { + private static final char[] EMPTY_KEY_PASSWORD = new char[] {}; + @Test void pemPropertiesAreMappedToSslBundle() throws Exception { PemSslBundleProperties properties = new PemSslBundleProperties(); @@ -61,10 +66,10 @@ void pemPropertiesAreMappedToSslBundle() throws Exception { Certificate certificate = sslBundle.getStores().getKeyStore().getCertificate("alias"); assertThat(certificate).isNotNull(); assertThat(certificate.getType()).isEqualTo("X.509"); - Key key = sslBundle.getStores().getKeyStore().getKey("alias", null); + Key key = sslBundle.getStores().getKeyStore().getKey("alias", "secret".toCharArray()); assertThat(key).isNotNull(); assertThat(key.getAlgorithm()).isEqualTo("RSA"); - certificate = sslBundle.getStores().getTrustStore().getCertificate("alias"); + certificate = sslBundle.getStores().getTrustStore().getCertificate("ssl"); assertThat(certificate).isNotNull(); assertThat(certificate.getType()).isEqualTo("X.509"); } @@ -99,4 +104,47 @@ void jksPropertiesAreMappedToSslBundle() { assertThat(trustStore.getProvider().getName()).isEqualTo("SUN"); } + @Test + void getWithPemSslBundlePropertiesWhenVerifyKeyStoreAgainstSingleCertificateWithMatchCreatesBundle() { + PemSslBundleProperties properties = new PemSslBundleProperties(); + properties.getKeystore().setCertificate("classpath:org/springframework/boot/autoconfigure/ssl/key1.crt"); + properties.getKeystore().setPrivateKey("classpath:org/springframework/boot/autoconfigure/ssl/key1.pem"); + properties.getKeystore().setVerifyKeys(true); + properties.getKey().setAlias("test-alias"); + SslBundle bundle = PropertiesSslBundle.get(properties); + assertThat(bundle.getStores().getKeyStore()).satisfies(storeContainingCertAndKey("test-alias")); + } + + @Test + void getWithPemSslBundlePropertiesWhenVerifyKeyStoreAgainstCertificateChainWithMatchCreatesBundle() { + PemSslBundleProperties properties = new PemSslBundleProperties(); + properties.getKeystore().setCertificate("classpath:org/springframework/boot/autoconfigure/ssl/key2-chain.crt"); + properties.getKeystore().setPrivateKey("classpath:org/springframework/boot/autoconfigure/ssl/key2.pem"); + properties.getKeystore().setVerifyKeys(true); + properties.getKey().setAlias("test-alias"); + SslBundle bundle = PropertiesSslBundle.get(properties); + assertThat(bundle.getStores().getKeyStore()).satisfies(storeContainingCertAndKey("test-alias")); + } + + @Test + void getWithPemSslBundlePropertiesWhenVerifyKeyStoreWithNoMatchThrowsException() { + PemSslBundleProperties properties = new PemSslBundleProperties(); + properties.getKeystore().setCertificate("classpath:org/springframework/boot/autoconfigure/ssl/key2.crt"); + properties.getKeystore().setPrivateKey("classpath:org/springframework/boot/autoconfigure/ssl/key1.pem"); + properties.getKeystore().setVerifyKeys(true); + properties.getKey().setAlias("test-alias"); + assertThatIllegalStateException().isThrownBy(() -> PropertiesSslBundle.get(properties)) + .withMessageContaining("Private key in keystore matches none of the certificates"); + } + + private Consumer storeContainingCertAndKey(String keyAlias) { + return ThrowingConsumer.of((keyStore) -> { + assertThat(keyStore).isNotNull(); + assertThat(keyStore.getType()).isEqualTo(KeyStore.getDefaultType()); + assertThat(keyStore.containsAlias(keyAlias)).isTrue(); + assertThat(keyStore.getCertificate(keyAlias)).isNotNull(); + assertThat(keyStore.getKey(keyAlias, EMPTY_KEY_PASSWORD)).isNotNull(); + }); + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrarTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrarTests.java new file mode 100644 index 000000000000..759bb474609b --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrarTests.java @@ -0,0 +1,180 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.ssl; + +import java.nio.file.Path; +import java.util.Set; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.springframework.boot.ssl.SslBundleRegistry; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.assertArg; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.times; + +/** + * Tests for {@link SslPropertiesBundleRegistrar}. + * + * @author Moritz Halbritter + */ +class SslPropertiesBundleRegistrarTests { + + private SslPropertiesBundleRegistrar registrar; + + private FileWatcher fileWatcher; + + private SslProperties properties; + + private SslBundleRegistry registry; + + @BeforeEach + void setUp() { + this.properties = new SslProperties(); + this.fileWatcher = Mockito.mock(FileWatcher.class); + this.registrar = new SslPropertiesBundleRegistrar(this.properties, this.fileWatcher); + this.registry = Mockito.mock(SslBundleRegistry.class); + } + + @Test + void shouldWatchJksBundles() { + JksSslBundleProperties jks = new JksSslBundleProperties(); + jks.setReloadOnUpdate(true); + jks.getKeystore().setLocation("classpath:test.jks"); + jks.getKeystore().setPassword("secret"); + jks.getTruststore().setLocation("classpath:test.jks"); + jks.getTruststore().setPassword("secret"); + this.properties.getBundle().getJks().put("bundle1", jks); + this.registrar.registerBundles(this.registry); + then(this.registry).should(times(1)).registerBundle(eq("bundle1"), any()); + then(this.fileWatcher).should().watch(assertArg((set) -> pathEndingWith(set, "test.jks")), any()); + } + + @Test + void shouldWatchPemBundles() { + PemSslBundleProperties pem = new PemSslBundleProperties(); + pem.setReloadOnUpdate(true); + pem.getKeystore().setCertificate("classpath:org/springframework/boot/autoconfigure/ssl/rsa-cert.pem"); + pem.getKeystore().setPrivateKey("classpath:org/springframework/boot/autoconfigure/ssl/rsa-key.pem"); + pem.getTruststore().setCertificate("classpath:org/springframework/boot/autoconfigure/ssl/ed25519-cert.pem"); + pem.getTruststore().setPrivateKey("classpath:org/springframework/boot/autoconfigure/ssl/ed25519-key.pem"); + this.properties.getBundle().getPem().put("bundle1", pem); + this.registrar.registerBundles(this.registry); + then(this.registry).should(times(1)).registerBundle(eq("bundle1"), any()); + then(this.fileWatcher).should() + .watch(assertArg((set) -> pathEndingWith(set, "rsa-cert.pem", "rsa-key.pem")), any()); + } + + @Test + void shouldFailIfPemKeystoreCertificateIsEmbedded() { + PemSslBundleProperties pem = new PemSslBundleProperties(); + pem.setReloadOnUpdate(true); + pem.getKeystore().setCertificate(""" + -----BEGIN CERTIFICATE----- + MIICCzCCAb2gAwIBAgIUZbDi7G5czH+Yi0k2EMWxdf00XagwBQYDK2VwMHsxCzAJ + BgNVBAYTAlhYMRIwEAYDVQQIDAlTdGF0ZU5hbWUxETAPBgNVBAcMCENpdHlOYW1l + MRQwEgYDVQQKDAtDb21wYW55TmFtZTEbMBkGA1UECwwSQ29tcGFueVNlY3Rpb25O + YW1lMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMjMwOTExMTIxNDMwWhcNMzMwOTA4 + MTIxNDMwWjB7MQswCQYDVQQGEwJYWDESMBAGA1UECAwJU3RhdGVOYW1lMREwDwYD + VQQHDAhDaXR5TmFtZTEUMBIGA1UECgwLQ29tcGFueU5hbWUxGzAZBgNVBAsMEkNv + bXBhbnlTZWN0aW9uTmFtZTESMBAGA1UEAwwJbG9jYWxob3N0MCowBQYDK2VwAyEA + Q/DDA4BSgZ+Hx0DUxtIRjVjN+OcxXVURwAWc3Gt9GUyjUzBRMB0GA1UdDgQWBBSv + EdpoaBMBoxgO96GFbf03k07DSTAfBgNVHSMEGDAWgBSvEdpoaBMBoxgO96GFbf03 + k07DSTAPBgNVHRMBAf8EBTADAQH/MAUGAytlcANBAHMXDkGd57d4F4cRk/8UjhxD + 7OtRBZfdfznSvlhJIMNfH5q0zbC2eO3hWCB3Hrn/vIeswGP8Ov4AJ6eXeX44BQM= + -----END CERTIFICATE----- + """.strip()); + this.properties.getBundle().getPem().put("bundle1", pem); + assertThatIllegalStateException().isThrownBy(() -> this.registrar.registerBundles(this.registry)) + .withMessageContaining("Unable to register SSL bundle 'bundle1'") + .havingCause() + .withMessage("Unable to watch for reload on update"); + } + + @Test + void shouldFailIfPemKeystorePrivateKeyIsEmbedded() { + PemSslBundleProperties pem = new PemSslBundleProperties(); + pem.setReloadOnUpdate(true); + pem.getKeystore().setCertificate("classpath:org/springframework/boot/autoconfigure/ssl/ed25519-cert.pem"); + pem.getKeystore().setPrivateKey(""" + -----BEGIN PRIVATE KEY----- + MC4CAQAwBQYDK2VwBCIEIC29RnMVTcyqXEAIO1b/6p7RdbM6TiqvnztVQ4IxYxUh + -----END PRIVATE KEY----- + """.strip()); + this.properties.getBundle().getPem().put("bundle1", pem); + assertThatIllegalStateException().isThrownBy(() -> this.registrar.registerBundles(this.registry)) + .withMessageContaining("Unable to register SSL bundle 'bundle1'") + .havingCause() + .withMessage("Unable to watch for reload on update"); + } + + @Test + void shouldFailIfPemTruststoreCertificateIsEmbedded() { + PemSslBundleProperties pem = new PemSslBundleProperties(); + pem.setReloadOnUpdate(true); + pem.getTruststore().setCertificate(""" + -----BEGIN CERTIFICATE----- + MIICCzCCAb2gAwIBAgIUZbDi7G5czH+Yi0k2EMWxdf00XagwBQYDK2VwMHsxCzAJ + BgNVBAYTAlhYMRIwEAYDVQQIDAlTdGF0ZU5hbWUxETAPBgNVBAcMCENpdHlOYW1l + MRQwEgYDVQQKDAtDb21wYW55TmFtZTEbMBkGA1UECwwSQ29tcGFueVNlY3Rpb25O + YW1lMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMjMwOTExMTIxNDMwWhcNMzMwOTA4 + MTIxNDMwWjB7MQswCQYDVQQGEwJYWDESMBAGA1UECAwJU3RhdGVOYW1lMREwDwYD + VQQHDAhDaXR5TmFtZTEUMBIGA1UECgwLQ29tcGFueU5hbWUxGzAZBgNVBAsMEkNv + bXBhbnlTZWN0aW9uTmFtZTESMBAGA1UEAwwJbG9jYWxob3N0MCowBQYDK2VwAyEA + Q/DDA4BSgZ+Hx0DUxtIRjVjN+OcxXVURwAWc3Gt9GUyjUzBRMB0GA1UdDgQWBBSv + EdpoaBMBoxgO96GFbf03k07DSTAfBgNVHSMEGDAWgBSvEdpoaBMBoxgO96GFbf03 + k07DSTAPBgNVHRMBAf8EBTADAQH/MAUGAytlcANBAHMXDkGd57d4F4cRk/8UjhxD + 7OtRBZfdfznSvlhJIMNfH5q0zbC2eO3hWCB3Hrn/vIeswGP8Ov4AJ6eXeX44BQM= + -----END CERTIFICATE----- + """.strip()); + this.properties.getBundle().getPem().put("bundle1", pem); + assertThatIllegalStateException().isThrownBy(() -> this.registrar.registerBundles(this.registry)) + .withMessageContaining("Unable to register SSL bundle 'bundle1'") + .havingCause() + .withMessage("Unable to watch for reload on update"); + } + + @Test + void shouldFailIfPemTruststorePrivateKeyIsEmbedded() { + PemSslBundleProperties pem = new PemSslBundleProperties(); + pem.setReloadOnUpdate(true); + pem.getTruststore().setCertificate("classpath:org/springframework/boot/autoconfigure/ssl/ed25519-cert.pem"); + pem.getTruststore().setPrivateKey(""" + -----BEGIN PRIVATE KEY----- + MC4CAQAwBQYDK2VwBCIEIC29RnMVTcyqXEAIO1b/6p7RdbM6TiqvnztVQ4IxYxUh + -----END PRIVATE KEY----- + """.strip()); + this.properties.getBundle().getPem().put("bundle1", pem); + assertThatIllegalStateException().isThrownBy(() -> this.registrar.registerBundles(this.registry)) + .withMessageContaining("Unable to register SSL bundle 'bundle1'") + .havingCause() + .withMessage("Unable to watch for reload on update"); + } + + private void pathEndingWith(Set paths, String... suffixes) { + for (String suffix : suffixes) { + assertThat(paths).anyMatch((path) -> path.getFileName().toString().endsWith(suffix)); + } + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java index c1d0507903ac..f8c6f2f337fb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,23 +17,29 @@ package org.springframework.boot.autoconfigure.task; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.task.TaskExecutorBuilder; -import org.springframework.boot.task.TaskExecutorCustomizer; +import org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder; +import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskDecorator; import org.springframework.core.task.TaskExecutor; @@ -43,7 +49,6 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; /** @@ -51,6 +56,8 @@ * * @author Stephane Nicoll * @author Camille Vienot + * @author Moritz Halbritter + * @author Yanming Zhou */ @ExtendWith(OutputCaptureExtension.class) class TaskExecutionAutoConfigurationTests { @@ -59,20 +66,44 @@ class TaskExecutionAutoConfigurationTests { .withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class)); @Test - void taskExecutorBuilderShouldApplyCustomSettings() { + void shouldSupplyBeans() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(ThreadPoolTaskExecutorBuilder.class); + assertThat(context).hasSingleBean(ThreadPoolTaskExecutor.class); + assertThat(context).hasSingleBean(SimpleAsyncTaskExecutorBuilder.class); + }); + } + + @Test + void simpleAsyncTaskExecutorBuilderShouldReadProperties() { this.contextRunner - .withPropertyValues("spring.task.execution.pool.queue-capacity=10", - "spring.task.execution.pool.core-size=2", "spring.task.execution.pool.max-size=4", - "spring.task.execution.pool.allow-core-thread-timeout=true", - "spring.task.execution.pool.keep-alive=5s", "spring.task.execution.shutdown.await-termination=true", - "spring.task.execution.shutdown.await-termination-period=30s", - "spring.task.execution.thread-name-prefix=mytest-") - .run(assertTaskExecutor((taskExecutor) -> { + .withPropertyValues("spring.task.execution.thread-name-prefix=mytest-", + "spring.task.execution.simple.concurrency-limit=1", + "spring.task.execution.shutdown.await-termination=true", + "spring.task.execution.shutdown.await-termination-period=30s") + .run(assertSimpleAsyncTaskExecutor((taskExecutor) -> { + assertThat(taskExecutor.getConcurrencyLimit()).isEqualTo(1); + assertThat(taskExecutor.getThreadNamePrefix()).isEqualTo("mytest-"); + assertThat(taskExecutor).hasFieldOrPropertyWithValue("taskTerminationTimeout", 30000L); + })); + } + + @Test + void threadPoolTaskExecutorBuilderShouldApplyCustomSettings() { + this.contextRunner.withPropertyValues("spring.task.execution.pool.queue-capacity=10", + "spring.task.execution.pool.core-size=2", "spring.task.execution.pool.max-size=4", + "spring.task.execution.pool.allow-core-thread-timeout=true", "spring.task.execution.pool.keep-alive=5s", + "spring.task.execution.pool.shutdown.accept-tasks-after-context-close=true", + "spring.task.execution.shutdown.await-termination=true", + "spring.task.execution.shutdown.await-termination-period=30s", + "spring.task.execution.thread-name-prefix=mytest-") + .run(assertThreadPoolTaskExecutor((taskExecutor) -> { assertThat(taskExecutor).hasFieldOrPropertyWithValue("queueCapacity", 10); assertThat(taskExecutor.getCorePoolSize()).isEqualTo(2); assertThat(taskExecutor.getMaxPoolSize()).isEqualTo(4); assertThat(taskExecutor).hasFieldOrPropertyWithValue("allowCoreThreadTimeOut", true); assertThat(taskExecutor.getKeepAliveSeconds()).isEqualTo(5); + assertThat(taskExecutor).hasFieldOrPropertyWithValue("acceptTasksAfterContextClose", true); assertThat(taskExecutor).hasFieldOrPropertyWithValue("waitForTasksToCompleteOnShutdown", true); assertThat(taskExecutor).hasFieldOrPropertyWithValue("awaitTerminationMillis", 30000L); assertThat(taskExecutor.getThreadNamePrefix()).isEqualTo("mytest-"); @@ -80,25 +111,25 @@ void taskExecutorBuilderShouldApplyCustomSettings() { } @Test - void taskExecutorBuilderWhenHasCustomBuilderShouldUseCustomBuilder() { - this.contextRunner.withUserConfiguration(CustomTaskExecutorBuilderConfig.class).run((context) -> { - assertThat(context).hasSingleBean(TaskExecutorBuilder.class); - assertThat(context.getBean(TaskExecutorBuilder.class)) - .isSameAs(context.getBean(CustomTaskExecutorBuilderConfig.class).taskExecutorBuilder); + void threadPoolTaskExecutorBuilderWhenHasCustomBuilderShouldUseCustomBuilder() { + this.contextRunner.withUserConfiguration(CustomThreadPoolTaskExecutorBuilderConfig.class).run((context) -> { + assertThat(context).hasSingleBean(ThreadPoolTaskExecutorBuilder.class); + assertThat(context.getBean(ThreadPoolTaskExecutorBuilder.class)) + .isSameAs(context.getBean(CustomThreadPoolTaskExecutorBuilderConfig.class).builder); }); } @Test - void taskExecutorBuilderShouldUseTaskDecorator() { + void threadPoolTaskExecutorBuilderShouldUseTaskDecorator() { this.contextRunner.withUserConfiguration(TaskDecoratorConfig.class).run((context) -> { - assertThat(context).hasSingleBean(TaskExecutorBuilder.class); - ThreadPoolTaskExecutor executor = context.getBean(TaskExecutorBuilder.class).build(); + assertThat(context).hasSingleBean(ThreadPoolTaskExecutorBuilder.class); + ThreadPoolTaskExecutor executor = context.getBean(ThreadPoolTaskExecutorBuilder.class).build(); assertThat(executor).extracting("taskDecorator").isSameAs(context.getBean(TaskDecorator.class)); }); } @Test - void taskExecutorAutoConfiguredIsLazy() { + void whenThreadPoolTaskExecutorIsAutoConfiguredThenItIsLazy() { this.contextRunner.run((context) -> { assertThat(context).hasSingleBean(Executor.class).hasBean("applicationTaskExecutor"); BeanDefinition beanDefinition = context.getSourceApplicationContext() @@ -109,6 +140,68 @@ void taskExecutorAutoConfiguredIsLazy() { }); } + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void whenVirtualThreadsAreEnabledThenSimpleAsyncTaskExecutorWithVirtualThreadsIsAutoConfigured() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> { + assertThat(context).hasSingleBean(Executor.class).hasBean("applicationTaskExecutor"); + assertThat(context).getBean("applicationTaskExecutor").isInstanceOf(SimpleAsyncTaskExecutor.class); + SimpleAsyncTaskExecutor taskExecutor = context.getBean("applicationTaskExecutor", + SimpleAsyncTaskExecutor.class); + assertThat(virtualThreadName(taskExecutor)).startsWith("task-"); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void whenTaskNamePrefixIsConfiguredThenSimpleAsyncTaskExecutorWithVirtualThreadsUsesIt() { + this.contextRunner + .withPropertyValues("spring.threads.virtual.enabled=true", + "spring.task.execution.thread-name-prefix=custom-") + .run((context) -> { + SimpleAsyncTaskExecutor taskExecutor = context.getBean("applicationTaskExecutor", + SimpleAsyncTaskExecutor.class); + assertThat(virtualThreadName(taskExecutor)).startsWith("custom-"); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void whenVirtualThreadsAreAvailableButNotEnabledThenThreadPoolTaskExecutorIsAutoConfigured() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(Executor.class).hasBean("applicationTaskExecutor"); + assertThat(context).getBean("applicationTaskExecutor").isInstanceOf(ThreadPoolTaskExecutor.class); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void whenTaskDecoratorIsDefinedThenSimpleAsyncTaskExecutorWithVirtualThreadsUsesIt() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true") + .withUserConfiguration(TaskDecoratorConfig.class) + .run((context) -> { + SimpleAsyncTaskExecutor executor = context.getBean(SimpleAsyncTaskExecutor.class); + assertThat(executor).extracting("taskDecorator").isSameAs(context.getBean(TaskDecorator.class)); + }); + } + + @Test + void simpleAsyncTaskExecutorBuilderUsesPlatformThreadsByDefault() { + this.contextRunner.run((context) -> { + SimpleAsyncTaskExecutorBuilder builder = context.getBean(SimpleAsyncTaskExecutorBuilder.class); + assertThat(builder).hasFieldOrPropertyWithValue("virtualThreads", null); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void simpleAsyncTaskExecutorBuilderUsesVirtualThreadsWhenEnabled() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> { + SimpleAsyncTaskExecutorBuilder builder = context.getBean(SimpleAsyncTaskExecutorBuilder.class); + assertThat(builder).hasFieldOrPropertyWithValue("virtualThreads", true); + }); + } + @Test void taskExecutorWhenHasCustomTaskExecutorShouldBackOff() { this.contextRunner.withUserConfiguration(CustomTaskExecutorConfig.class).run((context) -> { @@ -118,12 +211,14 @@ void taskExecutorWhenHasCustomTaskExecutorShouldBackOff() { } @Test - void taskExecutorBuilderShouldApplyCustomizer() { - this.contextRunner.withUserConfiguration(TaskExecutorCustomizerConfig.class).run((context) -> { - TaskExecutorCustomizer customizer = context.getBean(TaskExecutorCustomizer.class); - ThreadPoolTaskExecutor executor = context.getBean(TaskExecutorBuilder.class).build(); - then(customizer).should().customize(executor); - }); + @EnabledForJreRange(min = JRE.JAVA_21) + void whenVirtualThreadsAreEnabledAndCustomTaskExecutorIsDefinedThenSimpleAsyncTaskExecutorThatUsesVirtualThreadsBacksOff() { + this.contextRunner.withUserConfiguration(CustomTaskExecutorConfig.class) + .withPropertyValues("spring.threads.virtual.enabled=true") + .run((context) -> { + assertThat(context).hasSingleBean(Executor.class); + assertThat(context.getBean(Executor.class)).isSameAs(context.getBean("customTaskExecutor")); + }); } @Test @@ -150,33 +245,46 @@ void enableAsyncUsesAutoConfiguredOneByDefaultEvenThoughSchedulingIsConfigured() }); } - private ContextConsumer assertTaskExecutor( + private ContextConsumer assertThreadPoolTaskExecutor( Consumer taskExecutor) { return (context) -> { - assertThat(context).hasSingleBean(TaskExecutorBuilder.class); - TaskExecutorBuilder builder = context.getBean(TaskExecutorBuilder.class); + assertThat(context).hasSingleBean(ThreadPoolTaskExecutorBuilder.class); + ThreadPoolTaskExecutorBuilder builder = context.getBean(ThreadPoolTaskExecutorBuilder.class); taskExecutor.accept(builder.build()); }; } - @Configuration(proxyBeanMethods = false) - static class CustomTaskExecutorBuilderConfig { - - private final TaskExecutorBuilder taskExecutorBuilder = new TaskExecutorBuilder(); - - @Bean - TaskExecutorBuilder customTaskExecutorBuilder() { - return this.taskExecutorBuilder; - } + private ContextConsumer assertSimpleAsyncTaskExecutor( + Consumer taskExecutor) { + return (context) -> { + assertThat(context).hasSingleBean(SimpleAsyncTaskExecutorBuilder.class); + SimpleAsyncTaskExecutorBuilder builder = context.getBean(SimpleAsyncTaskExecutorBuilder.class); + taskExecutor.accept(builder.build()); + }; + } + private String virtualThreadName(SimpleAsyncTaskExecutor taskExecutor) throws InterruptedException { + AtomicReference threadReference = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + taskExecutor.execute(() -> { + Thread currentThread = Thread.currentThread(); + threadReference.set(currentThread); + latch.countDown(); + }); + assertThat(latch.await(30, TimeUnit.SECONDS)).isTrue(); + Thread thread = threadReference.get(); + assertThat(thread).extracting("virtual").as("%s is virtual", thread).isEqualTo(true); + return thread.getName(); } @Configuration(proxyBeanMethods = false) - static class TaskExecutorCustomizerConfig { + static class CustomThreadPoolTaskExecutorBuilderConfig { + + private final ThreadPoolTaskExecutorBuilder builder = new ThreadPoolTaskExecutorBuilder(); @Bean - TaskExecutorCustomizer mockTaskExecutorCustomizer() { - return mock(TaskExecutorCustomizer.class); + ThreadPoolTaskExecutorBuilder customThreadPoolTaskExecutorBuilder() { + return this.builder; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfigurationTests.java index 990e8cb6dbe8..74dc49d97403 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,12 +26,18 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import org.assertj.core.api.InstanceOfAssertFactories; import org.awaitility.Awaitility; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; import org.springframework.boot.LazyInitializationBeanFactoryPostProcessor; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.task.TaskSchedulerCustomizer; +import org.springframework.boot.task.SimpleAsyncTaskSchedulerBuilder; +import org.springframework.boot.task.SimpleAsyncTaskSchedulerCustomizer; +import org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder; +import org.springframework.boot.task.ThreadPoolTaskSchedulerCustomizer; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -49,6 +55,7 @@ * Tests for {@link TaskSchedulingAutoConfiguration}. * * @author Stephane Nicoll + * @author Moritz Halbritter */ class TaskSchedulingAutoConfigurationTests { @@ -67,6 +74,14 @@ void noSchedulingDoesNotExposeScheduledBeanLazyInitializationExcludeFilter() { .run((context) -> assertThat(context).doesNotHaveBean(ScheduledBeanLazyInitializationExcludeFilter.class)); } + @Test + void shouldSupplyBeans() { + this.contextRunner.withUserConfiguration(SchedulingConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(ThreadPoolTaskSchedulerBuilder.class); + assertThat(context).hasSingleBean(ThreadPoolTaskScheduler.class); + }); + } + @Test void enableSchedulingWithNoTaskExecutorAutoConfiguresOne() { this.contextRunner @@ -85,10 +100,64 @@ void enableSchedulingWithNoTaskExecutorAutoConfiguresOne() { }); } + @Test + void simpleAsyncTaskSchedulerBuilderShouldReadProperties() { + this.contextRunner + .withPropertyValues("spring.task.scheduling.simple.concurrency-limit=1", + "spring.task.scheduling.thread-name-prefix=scheduling-test-", + "spring.task.scheduling.shutdown.await-termination=true", + "spring.task.scheduling.shutdown.await-termination-period=30s") + .withUserConfiguration(SchedulingConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(SimpleAsyncTaskSchedulerBuilder.class); + SimpleAsyncTaskSchedulerBuilder builder = context.getBean(SimpleAsyncTaskSchedulerBuilder.class); + assertThat(builder).hasFieldOrPropertyWithValue("threadNamePrefix", "scheduling-test-"); + assertThat(builder).hasFieldOrPropertyWithValue("concurrencyLimit", 1); + assertThat(builder).hasFieldOrPropertyWithValue("taskTerminationTimeout", Duration.ofSeconds(30)); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void simpleAsyncTaskSchedulerBuilderShouldUseVirtualThreadsIfEnabled() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true") + .withUserConfiguration(SchedulingConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(SimpleAsyncTaskSchedulerBuilder.class); + SimpleAsyncTaskSchedulerBuilder builder = context.getBean(SimpleAsyncTaskSchedulerBuilder.class); + assertThat(builder).hasFieldOrPropertyWithValue("virtualThreads", true); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void simpleAsyncTaskSchedulerBuilderShouldUsePlatformThreadsByDefault() { + this.contextRunner.withUserConfiguration(SchedulingConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(SimpleAsyncTaskSchedulerBuilder.class); + SimpleAsyncTaskSchedulerBuilder builder = context.getBean(SimpleAsyncTaskSchedulerBuilder.class); + assertThat(builder).hasFieldOrPropertyWithValue("virtualThreads", null); + }); + } + + @Test + void simpleAsyncTaskSchedulerBuilderShouldApplyCustomizers() { + SimpleAsyncTaskSchedulerCustomizer customizer = (scheduler) -> { + }; + this.contextRunner.withBean(SimpleAsyncTaskSchedulerCustomizer.class, () -> customizer) + .withUserConfiguration(SchedulingConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(SimpleAsyncTaskSchedulerBuilder.class); + SimpleAsyncTaskSchedulerBuilder builder = context.getBean(SimpleAsyncTaskSchedulerBuilder.class); + assertThat(builder).extracting("customizers") + .asInstanceOf(InstanceOfAssertFactories.collection(SimpleAsyncTaskSchedulerCustomizer.class)) + .containsExactly(customizer); + }); + } + @Test void enableSchedulingWithNoTaskExecutorAppliesCustomizers() { this.contextRunner.withPropertyValues("spring.task.scheduling.thread-name-prefix=scheduling-test-") - .withUserConfiguration(SchedulingConfiguration.class, TaskSchedulerCustomizerConfiguration.class) + .withUserConfiguration(SchedulingConfiguration.class, ThreadPoolTaskSchedulerCustomizerConfiguration.class) .run((context) -> { assertThat(context).hasSingleBean(TaskExecutor.class); TestBean bean = context.getBean(TestBean.class); @@ -122,17 +191,6 @@ void enableSchedulingWithExistingScheduledExecutorServiceBacksOff() { }); } - @Test - void enableSchedulingWithConfigurerBacksOff() { - this.contextRunner.withUserConfiguration(SchedulingConfiguration.class, SchedulingConfigurerConfiguration.class) - .run((context) -> { - assertThat(context).doesNotHaveBean(TaskScheduler.class); - TestBean bean = context.getBean(TestBean.class); - assertThat(bean.latch.await(30, TimeUnit.SECONDS)).isTrue(); - assertThat(bean.threadNames).containsExactly("test-1"); - }); - } - @Test void enableSchedulingWithLazyInitializationInvokeScheduledMethods() { List threadNames = new ArrayList<>(); @@ -177,10 +235,10 @@ ScheduledExecutorService customScheduledExecutorService() { } @Configuration(proxyBeanMethods = false) - static class TaskSchedulerCustomizerConfiguration { + static class ThreadPoolTaskSchedulerCustomizerConfiguration { @Bean - TaskSchedulerCustomizer testTaskSchedulerCustomizer() { + ThreadPoolTaskSchedulerCustomizer testTaskSchedulerCustomizer() { return ((taskScheduler) -> taskScheduler.setThreadNamePrefix("customized-scheduler-")); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/ExecutionListenersTransactionManagerCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/ExecutionListenersTransactionManagerCustomizerTests.java new file mode 100644 index 000000000000..3722680f6d7c --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/ExecutionListenersTransactionManagerCustomizerTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.transaction; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.transaction.ConfigurableTransactionManager; +import org.springframework.transaction.TransactionExecutionListener; + +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link ExecutionListenersTransactionManagerCustomizer}. + * + * @author Andy Wilkinson + */ +class ExecutionListenersTransactionManagerCustomizerTests { + + @Test + void whenTransactionManagerIsCustomizedThenExecutionListenersAreAddedToIt() { + TransactionExecutionListener listener1 = mock(TransactionExecutionListener.class); + TransactionExecutionListener listener2 = mock(TransactionExecutionListener.class); + ConfigurableTransactionManager transactionManager = mock(ConfigurableTransactionManager.class); + new ExecutionListenersTransactionManagerCustomizer(List.of(listener1, listener2)).customize(transactionManager); + then(transactionManager).should().addListener(listener1); + then(transactionManager).should().addListener(listener2); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/TransactionAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/TransactionAutoConfigurationTests.java index 1af0a1151950..e3aa69357502 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/TransactionAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/TransactionAutoConfigurationTests.java @@ -130,18 +130,6 @@ void whenAUserProvidesATransactionalOperatorTheAutoConfiguredOperatorBacksOff() }); } - @Test - void platformTransactionManagerCustomizers() { - this.contextRunner.withUserConfiguration(SeveralPlatformTransactionManagersConfiguration.class) - .run((context) -> { - TransactionManagerCustomizers customizers = context.getBean(TransactionManagerCustomizers.class); - assertThat(customizers).extracting("customizers") - .asList() - .singleElement() - .isInstanceOf(TransactionProperties.class); - }); - } - @Test void transactionNotManagedWithNoTransactionManager() { this.contextRunner.withUserConfiguration(BaseConfiguration.class) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/TransactionManagerCustomizationAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/TransactionManagerCustomizationAutoConfigurationTests.java new file mode 100644 index 000000000000..16c271f64af4 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/TransactionManagerCustomizationAutoConfigurationTests.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.transaction; + +import java.util.Collections; + +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link TransactionManagerCustomizationAutoConfiguration}. + * + * @author Andy Wilkinson + */ +class TransactionManagerCustomizationAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(TransactionManagerCustomizationAutoConfiguration.class)); + + @Test + void autoConfiguresTransactionManagerCustomizers() { + this.contextRunner.run((context) -> { + TransactionManagerCustomizers customizers = context.getBean(TransactionManagerCustomizers.class); + assertThat(customizers).extracting("customizers") + .asInstanceOf(InstanceOfAssertFactories.LIST) + .hasSize(2) + .hasAtLeastOneElementOfType(TransactionProperties.class) + .hasAtLeastOneElementOfType(ExecutionListenersTransactionManagerCustomizer.class); + }); + } + + @Test + void autoConfiguredTransactionManagerCustomizersBacksOff() { + this.contextRunner.withUserConfiguration(CustomTransactionManagerCustomizersConfiguration.class) + .run((context) -> { + TransactionManagerCustomizers customizers = context.getBean(TransactionManagerCustomizers.class); + assertThat(customizers).extracting("customizers") + .asInstanceOf(InstanceOfAssertFactories.LIST) + .isEmpty(); + }); + } + + @Configuration(proxyBeanMethods = false) + static class CustomTransactionManagerCustomizersConfiguration { + + @Bean + TransactionManagerCustomizers customTransactionManagerCustomizers() { + return TransactionManagerCustomizers.of(Collections.>emptyList()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/TransactionManagerCustomizersTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/TransactionManagerCustomizersTests.java index 9f5827813760..d4dc3dc8a5ac 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/TransactionManagerCustomizersTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/TransactionManagerCustomizersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test; import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionManager; import org.springframework.transaction.jta.JtaTransactionManager; import static org.assertj.core.api.Assertions.assertThat; @@ -36,7 +37,7 @@ class TransactionManagerCustomizersTests { @Test void customizeWithNullCustomizersShouldDoNothing() { - new TransactionManagerCustomizers(null).customize(mock(PlatformTransactionManager.class)); + TransactionManagerCustomizers.of(null).customize(mock(TransactionManager.class)); } @Test @@ -44,15 +45,14 @@ void customizeShouldCheckGeneric() { List> list = new ArrayList<>(); list.add(new TestCustomizer<>()); list.add(new TestJtaCustomizer()); - TransactionManagerCustomizers customizers = new TransactionManagerCustomizers(list); + TransactionManagerCustomizers customizers = TransactionManagerCustomizers.of(list); customizers.customize(mock(PlatformTransactionManager.class)); customizers.customize(mock(JtaTransactionManager.class)); assertThat(list.get(0).getCount()).isEqualTo(2); assertThat(list.get(1).getCount()).isOne(); } - static class TestCustomizer - implements PlatformTransactionManagerCustomizer { + static class TestCustomizer implements TransactionManagerCustomizer { private int count; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfigurationTests.java index e70fb24e4775..a2e0647780cc 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfigurationTests.java @@ -43,6 +43,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -62,6 +63,7 @@ * @author Kazuki Shimizu * @author Nishant Raut */ +@ClassPathExclusions("jetty-jndi-*.jar") class JtaAutoConfigurationTests { private AnnotationConfigApplicationContext context; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java index 4a46ae5ddc82..66dc324099c3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java @@ -217,7 +217,7 @@ void userDefinedMethodValidationPostProcessorTakesPrecedence() { .isSameAs(userMethodValidationPostProcessor); assertThat(context.getBeansOfType(MethodValidationPostProcessor.class)).hasSize(1); Object validator = ReflectionTestUtils.getField(userMethodValidationPostProcessor, "validator"); - assertThat(validator).isNotNull().isInstanceOf(Supplier.class); + assertThat(validator).isInstanceOf(Supplier.class); assertThat(context.getBean(Validator.class)).isNotSameAs(((Supplier) validator).get()); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapterTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapterTests.java index bf57084b98ef..641c45dc220f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapterTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapterTests.java @@ -18,7 +18,9 @@ import java.util.HashMap; +import jakarta.validation.Validator; import jakarta.validation.constraints.Min; +import org.hibernate.validator.HibernateValidator; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.FilteredClassLoader; @@ -27,10 +29,13 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; +import org.springframework.validation.Errors; import org.springframework.validation.MapBindingResult; +import org.springframework.validation.SmartValidator; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatRuntimeException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; @@ -91,6 +96,30 @@ void wrapperWhenValidationProviderNotPresentShouldNotThrowException() { .run((context) -> ValidatorAdapter.get(context, null)); } + @Test + void unwrapToJakartaValidatorShouldReturnJakartaValidator() { + this.contextRunner.withUserConfiguration(LocalValidatorFactoryBeanConfig.class).run((context) -> { + ValidatorAdapter wrapper = context.getBean(ValidatorAdapter.class); + assertThat(wrapper.unwrap(Validator.class)).isInstanceOf(Validator.class); + }); + } + + @Test + void whenJakartaValidatorIsWrappedMultipleTimesUnwrapToJakartaValidatorShouldReturnJakartaValidator() { + this.contextRunner.withUserConfiguration(DoubleWrappedConfig.class).run((context) -> { + ValidatorAdapter wrapper = context.getBean(ValidatorAdapter.class); + assertThat(wrapper.unwrap(Validator.class)).isInstanceOf(Validator.class); + }); + } + + @Test + void unwrapToUnsupportedTypeShouldThrow() { + this.contextRunner.withUserConfiguration(LocalValidatorFactoryBeanConfig.class).run((context) -> { + ValidatorAdapter wrapper = context.getBean(ValidatorAdapter.class); + assertThatRuntimeException().isThrownBy(() -> wrapper.unwrap(HibernateValidator.class)); + }); + } + @Configuration(proxyBeanMethods = false) static class LocalValidatorFactoryBeanConfig { @@ -106,6 +135,55 @@ ValidatorAdapter wrapper(LocalValidatorFactoryBean validator) { } + @Configuration(proxyBeanMethods = false) + static class DoubleWrappedConfig { + + @Bean + LocalValidatorFactoryBean validator() { + return new LocalValidatorFactoryBean(); + } + + @Bean + ValidatorAdapter wrapper(LocalValidatorFactoryBean validator) { + return new ValidatorAdapter(new Wrapper(validator), true); + } + + static class Wrapper implements SmartValidator { + + private final SmartValidator delegate; + + Wrapper(SmartValidator delegate) { + this.delegate = delegate; + } + + @Override + public boolean supports(Class clazz) { + return this.delegate.supports(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + this.delegate.validate(target, errors); + } + + @Override + public void validate(Object target, Errors errors, Object... validationHints) { + this.delegate.validate(target, errors, validationHints); + } + + @Override + @SuppressWarnings("unchecked") + public T unwrap(Class type) { + if (type.isInstance(this.delegate)) { + return (T) this.delegate; + } + return this.delegate.unwrap(type); + } + + } + + } + @Configuration(proxyBeanMethods = false) static class NonManagedBeanConfig { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java index eaf0b45c2b0a..0bfeacf8ec67 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,21 +16,14 @@ package org.springframework.boot.autoconfigure.web; -import java.io.IOException; import java.net.InetAddress; -import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; import io.undertow.UndertowOptions; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.apache.catalina.connector.Connector; import org.apache.catalina.core.StandardContext; import org.apache.catalina.core.StandardEngine; @@ -38,8 +31,7 @@ import org.apache.catalina.valves.RemoteIpValve; import org.apache.coyote.AbstractProtocol; import org.apache.tomcat.util.net.AbstractEndpoint; -import org.eclipse.jetty.server.HttpChannel; -import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.jupiter.api.Test; @@ -51,21 +43,13 @@ import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories; -import org.springframework.boot.testsupport.web.servlet.Servlet5ClassPathOverrides; import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; import org.springframework.boot.web.embedded.jetty.JettyWebServer; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; -import org.springframework.boot.web.servlet.ServletContextInitializer; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.client.ClientHttpResponse; +import org.springframework.boot.web.server.MimeMappings; +import org.springframework.boot.web.server.MimeMappings.Mapping; import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.util.unit.DataSize; -import org.springframework.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestTemplate; import static org.assertj.core.api.Assertions.assertThat; @@ -84,6 +68,7 @@ * @author Rafiullah Hamedy * @author Chris Bono * @author Parviz Rozikov + * @author Lasse Wulff */ @DirtiesUrlFactories class ServerPropertiesTests { @@ -114,6 +99,7 @@ void testServerHeader() { } @Test + @SuppressWarnings("removal") void testTomcatBinding() { Map map = new HashMap<>(); map.put("server.tomcat.accesslog.conditionIf", "foo"); @@ -200,27 +186,23 @@ void testContextPathWithLeadingAndTrailingWhitespaceAndContextWithSpace() { } @Test - void testCustomizeUriEncoding() { - bind("server.tomcat.uri-encoding", "US-ASCII"); - assertThat(this.properties.getTomcat().getUriEncoding()).isEqualTo(StandardCharsets.US_ASCII); + void testDefaultMimeMapping() { + assertThat(this.properties.getMimeMappings()).isEmpty(); } @Test - @SuppressWarnings("removal") - @Deprecated(since = "3.0.0", forRemoval = true) - void testCustomizeHeaderSize() { - bind("server.max-http-header-size", "1MB"); - assertThat(this.properties.getMaxHttpHeaderSize()).isEqualTo(DataSize.ofMegabytes(1)); - assertThat(this.properties.getMaxHttpRequestHeaderSize()).isEqualTo(DataSize.ofMegabytes(1)); + void testCustomizedMimeMapping() { + MimeMappings expectedMappings = new MimeMappings(); + expectedMappings.add("mjs", "text/javascript"); + bind("server.mime-mappings.mjs", "text/javascript"); + assertThat(this.properties.getMimeMappings()) + .containsExactly(expectedMappings.getAll().toArray(new Mapping[0])); } @Test - @SuppressWarnings("removal") - @Deprecated(since = "3.0.0", forRemoval = true) - void testCustomizeHeaderSizeUseBytesByDefault() { - bind("server.max-http-header-size", "1024"); - assertThat(this.properties.getMaxHttpHeaderSize()).isEqualTo(DataSize.ofKilobytes(1)); - assertThat(this.properties.getMaxHttpRequestHeaderSize()).isEqualTo(DataSize.ofKilobytes(1)); + void testCustomizeUriEncoding() { + bind("server.tomcat.uri-encoding", "US-ASCII"); + assertThat(this.properties.getTomcat().getUriEncoding()).isEqualTo(StandardCharsets.US_ASCII); } @Test @@ -441,6 +423,7 @@ void tomcatInternalProxiesMatchesDefault() { } @Test + @SuppressWarnings("removal") void tomcatRejectIllegalHeaderMatchesProtocolDefault() throws Exception { assertThat(getDefaultProtocol()).hasFieldOrPropertyWithValue("rejectIllegalHeader", this.properties.getTomcat().isRejectIllegalHeader()); @@ -460,7 +443,6 @@ void tomcatMaxKeepAliveRequestsDefault() throws Exception { } @Test - @Servlet5ClassPathOverrides void jettyThreadPoolPropertyDefaultsShouldMatchServerDefault() { JettyServletWebServerFactory jettyFactory = new JettyServletWebServerFactory(0); JettyWebServer jetty = (JettyWebServer) jettyFactory.getWebServer(); @@ -475,61 +457,12 @@ void jettyThreadPoolPropertyDefaultsShouldMatchServerDefault() { } @Test - @Servlet5ClassPathOverrides void jettyMaxHttpFormPostSizeMatchesDefault() { JettyServletWebServerFactory jettyFactory = new JettyServletWebServerFactory(0); - JettyWebServer jetty = (JettyWebServer) jettyFactory - .getWebServer((ServletContextInitializer) (servletContext) -> servletContext - .addServlet("formPost", new HttpServlet() { - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { - req.getParameterMap(); - } - - }) - .addMapping("/form")); - jetty.start(); - org.eclipse.jetty.server.Connector connector = jetty.getServer().getConnectors()[0]; - final AtomicReference failure = new AtomicReference<>(); - connector.addBean(new HttpChannel.Listener() { - - @Override - public void onDispatchFailure(Request request, Throwable ex) { - failure.set(ex); - } - - }); - try { - RestTemplate template = new RestTemplate(); - template.setErrorHandler(new ResponseErrorHandler() { - - @Override - public boolean hasError(ClientHttpResponse response) throws IOException { - return false; - } - - @Override - public void handleError(ClientHttpResponse response) throws IOException { - - } - - }); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - MultiValueMap body = new LinkedMultiValueMap<>(); - body.add("data", "a".repeat(250000)); - HttpEntity> entity = new HttpEntity<>(body, headers); - template.postForEntity(URI.create("http://localhost:" + jetty.getPort() + "/form"), entity, Void.class); - assertThat(failure.get()).isNotNull(); - String message = failure.get().getCause().getMessage(); - int defaultMaxPostSize = Integer.parseInt(message.substring(message.lastIndexOf(' ')).trim()); - assertThat(this.properties.getJetty().getMaxHttpFormPostSize().toBytes()).isEqualTo(defaultMaxPostSize); - } - finally { - jetty.stop(); - } + JettyWebServer jetty = (JettyWebServer) jettyFactory.getWebServer(); + Server server = jetty.getServer(); + assertThat(this.properties.getJetty().getMaxHttpFormPostSize().toBytes()) + .isEqualTo(((ServletContextHandler) server.getHandler()).getMaxFormContentSize()); } @Test @@ -538,14 +471,6 @@ void undertowMaxHttpPostSizeMatchesDefault() { .isEqualTo(UndertowOptions.DEFAULT_MAX_ENTITY_SIZE); } - @Test - @Deprecated(since = "3.0.0", forRemoval = true) - @SuppressWarnings("removal") - void nettyMaxChunkSizeMatchesHttpDecoderSpecDefault() { - assertThat(this.properties.getNetty().getMaxChunkSize().toBytes()) - .isEqualTo(HttpDecoderSpec.DEFAULT_MAX_CHUNK_SIZE); - } - @Test void nettyMaxInitialLineLengthMatchesHttpDecoderSpecDefault() { assertThat(this.properties.getNetty().getMaxInitialLineLength().toBytes()) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/HttpMessageConvertersRestClientCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/HttpMessageConvertersRestClientCustomizerTests.java new file mode 100644 index 000000000000..f4138baa1819 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/HttpMessageConvertersRestClientCustomizerTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.client; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.client.RestClient; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link HttpMessageConvertersRestClientCustomizer} + * + * @author Phillip Webb + */ +class HttpMessageConvertersRestClientCustomizerTests { + + @Test + void createWhenNullMessageConvertersArrayThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new HttpMessageConvertersRestClientCustomizer((HttpMessageConverter[]) null)) + .withMessage("MessageConverters must not be null"); + } + + @Test + void createWhenNullMessageConvertersDoesNotCustomize() { + HttpMessageConverter c0 = mock(); + assertThat(apply(new HttpMessageConvertersRestClientCustomizer((HttpMessageConverters) null), c0)) + .containsExactly(c0); + } + + @Test + void customizeConfiguresMessageConverters() { + HttpMessageConverter c0 = mock(); + HttpMessageConverter c1 = mock(); + HttpMessageConverter c2 = mock(); + assertThat(apply(new HttpMessageConvertersRestClientCustomizer(c1, c2), c0)).containsExactly(c1, c2); + } + + @SuppressWarnings("unchecked") + private List> apply(HttpMessageConvertersRestClientCustomizer customizer, + HttpMessageConverter... converters) { + List> messageConverters = new ArrayList<>(Arrays.asList(converters)); + RestClient.Builder restClientBuilder = mock(); + ArgumentCaptor>>> captor = ArgumentCaptor.forClass(Consumer.class); + given(restClientBuilder.messageConverters(captor.capture())).willReturn(restClientBuilder); + customizer.customize(restClientBuilder); + captor.getValue().accept(messageConverters); + return messageConverters; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java new file mode 100644 index 000000000000..576d6e45808b --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java @@ -0,0 +1,207 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.client; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.web.client.RestClientCustomizer; +import org.springframework.boot.web.codec.CodecCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClient.Builder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link RestClientAutoConfiguration} + * + * @author Arjen Poutsma + * @author Moritz Halbritter + */ +class RestClientAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class)); + + @Test + void shouldSupplyBeans() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(HttpMessageConvertersRestClientCustomizer.class); + assertThat(context).hasSingleBean(RestClientBuilderConfigurer.class); + assertThat(context).hasSingleBean(RestClient.Builder.class); + }); + } + + @Test + void shouldSupplyRestClientSslIfSslBundlesIsThere() { + this.contextRunner.withBean(SslBundles.class, () -> mock(SslBundles.class)) + .run((context) -> assertThat(context).hasSingleBean(RestClientSsl.class)); + } + + @Test + void shouldCreateBuilder() { + this.contextRunner.run((context) -> { + RestClient.Builder builder = context.getBean(RestClient.Builder.class); + RestClient restClient = builder.build(); + assertThat(restClient).isNotNull(); + }); + } + + @Test + void configurerShouldCallCustomizers() { + this.contextRunner.withUserConfiguration(RestClientCustomizerConfig.class).run((context) -> { + RestClientBuilderConfigurer configurer = context.getBean(RestClientBuilderConfigurer.class); + RestClientCustomizer customizer = context.getBean("restClientCustomizer", RestClientCustomizer.class); + Builder builder = RestClient.builder(); + configurer.configure(builder); + then(customizer).should().customize(builder); + }); + } + + @Test + void restClientShouldApplyCustomizers() { + this.contextRunner.withUserConfiguration(RestClientCustomizerConfig.class).run((context) -> { + RestClient.Builder builder = context.getBean(RestClient.Builder.class); + RestClientCustomizer customizer = context.getBean("restClientCustomizer", RestClientCustomizer.class); + builder.build(); + then(customizer).should().customize(any(RestClient.Builder.class)); + }); + } + + @Test + void shouldGetPrototypeScopedBean() { + this.contextRunner.withUserConfiguration(RestClientCustomizerConfig.class).run((context) -> { + RestClient.Builder firstBuilder = context.getBean(RestClient.Builder.class); + RestClient.Builder secondBuilder = context.getBean(RestClient.Builder.class); + assertThat(firstBuilder).isNotEqualTo(secondBuilder); + }); + } + + @Test + void shouldNotCreateClientBuilderIfAlreadyPresent() { + this.contextRunner.withUserConfiguration(CustomRestClientBuilderConfig.class).run((context) -> { + RestClient.Builder builder = context.getBean(RestClient.Builder.class); + assertThat(builder).isInstanceOf(MyRestClientBuilder.class); + }); + } + + @Test + @SuppressWarnings("unchecked") + void restClientWhenMessageConvertersDefinedShouldHaveMessageConverters() { + this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class)) + .withUserConfiguration(RestClientConfig.class) + .run((context) -> { + RestClient restClient = context.getBean(RestClient.class); + List> expectedConverters = context.getBean(HttpMessageConverters.class) + .getConverters(); + List> actualConverters = (List>) ReflectionTestUtils + .getField(restClient, "messageConverters"); + assertThat(actualConverters).containsExactlyElementsOf(expectedConverters); + }); + } + + @Test + @SuppressWarnings("unchecked") + void restClientWhenNoMessageConvertersDefinedShouldHaveDefaultMessageConverters() { + this.contextRunner.withUserConfiguration(RestClientConfig.class).run((context) -> { + RestClient restClient = context.getBean(RestClient.class); + RestClient defaultRestClient = RestClient.builder().build(); + List> actualConverters = (List>) ReflectionTestUtils + .getField(restClient, "messageConverters"); + List> expectedConverters = (List>) ReflectionTestUtils + .getField(defaultRestClient, "messageConverters"); + assertThat(actualConverters).hasSameSizeAs(expectedConverters); + }); + } + + @Test + @SuppressWarnings({ "unchecked", "rawtypes" }) + void restClientWhenHasCustomMessageConvertersShouldHaveMessageConverters() { + this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class)) + .withUserConfiguration(CustomHttpMessageConverter.class, RestClientConfig.class) + .run((context) -> { + RestClient restClient = context.getBean(RestClient.class); + List> actualConverters = (List>) ReflectionTestUtils + .getField(restClient, "messageConverters"); + assertThat(actualConverters).extracting(HttpMessageConverter::getClass) + .contains((Class) CustomHttpMessageConverter.class); + }); + } + + @Configuration(proxyBeanMethods = false) + static class CodecConfiguration { + + @Bean + CodecCustomizer myCodecCustomizer() { + return mock(CodecCustomizer.class); + } + + } + + @Configuration(proxyBeanMethods = false) + static class RestClientCustomizerConfig { + + @Bean + RestClientCustomizer restClientCustomizer() { + return mock(RestClientCustomizer.class); + } + + } + + @Configuration(proxyBeanMethods = false) + static class CustomRestClientBuilderConfig { + + @Bean + MyRestClientBuilder myRestClientBuilder() { + return mock(MyRestClientBuilder.class); + } + + } + + interface MyRestClientBuilder extends RestClient.Builder { + + } + + @Configuration(proxyBeanMethods = false) + static class RestClientConfig { + + @Bean + RestClient restClient(RestClient.Builder restClientBuilder) { + return restClientBuilder.build(); + } + + } + + static class CustomHttpMessageConverter extends StringHttpMessageConverter { + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientBuilderConfigurerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientBuilderConfigurerTests.java new file mode 100644 index 000000000000..c4c8395c2177 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientBuilderConfigurerTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.client; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.web.client.RestClientCustomizer; +import org.springframework.web.client.RestClient; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link RestClientBuilderConfigurer}. + * + * @author Moritz Halbritter + */ +class RestClientBuilderConfigurerTests { + + @Test + void shouldApplyCustomizers() { + RestClientBuilderConfigurer configurer = new RestClientBuilderConfigurer(); + RestClientCustomizer customizer = mock(RestClientCustomizer.class); + configurer.setRestClientCustomizers(List.of(customizer)); + RestClient.Builder builder = RestClient.builder(); + configurer.configure(builder); + then(customizer).should().customize(builder); + } + + @Test + void shouldSupportNullAsCustomizers() { + RestClientBuilderConfigurer configurer = new RestClientBuilderConfigurer(); + configurer.setRestClientCustomizers(null); + assertThatCode(() -> configurer.configure(RestClient.builder())).doesNotThrowAnyException(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfigurationTests.java index a80f93a73720..582752925976 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfigurationTests.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.support.BeanDefinitionOverrideException; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; @@ -65,6 +66,14 @@ void restTemplateBuilderConfigurerShouldBeLazilyDefined() { .isTrue()); } + @Test + void shouldFailOnCustomRestTemplateBuilderConfigurer() { + this.contextRunner.withUserConfiguration(RestTemplateBuilderConfigurerConfig.class) + .run((context) -> assertThat(context).getFailure() + .isInstanceOf(BeanDefinitionOverrideException.class) + .hasMessageContaining("with name 'restTemplateBuilderConfigurer'")); + } + @Test void restTemplateBuilderShouldBeLazilyDefined() { this.contextRunner @@ -263,6 +272,16 @@ RestTemplateRequestCustomizer restTemplateRequestCustomizer() { } + @Configuration(proxyBeanMethods = false) + static class RestTemplateBuilderConfigurerConfig { + + @Bean + RestTemplateBuilderConfigurer restTemplateBuilderConfigurer() { + return new RestTemplateBuilderConfigurer(); + } + + } + static class CustomHttpMessageConverter extends StringHttpMessageConverter { } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyVirtualThreadsWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyVirtualThreadsWebServerFactoryCustomizerTests.java new file mode 100644 index 000000000000..b98016ea9bce --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyVirtualThreadsWebServerFactoryCustomizerTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.embedded; + +import java.time.Duration; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; + +import org.awaitility.Awaitility; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; + +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.web.embedded.jetty.ConfigurableJettyWebServerFactory; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.assertArg; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link JettyVirtualThreadsWebServerFactoryCustomizer}. + * + * @author Moritz Halbritter + */ +class JettyVirtualThreadsWebServerFactoryCustomizerTests { + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void shouldConfigureVirtualThreads() { + ServerProperties properties = new ServerProperties(); + JettyVirtualThreadsWebServerFactoryCustomizer customizer = new JettyVirtualThreadsWebServerFactoryCustomizer( + properties); + ConfigurableJettyWebServerFactory factory = mock(ConfigurableJettyWebServerFactory.class); + customizer.customize(factory); + then(factory).should().setThreadPool(assertArg((threadPool) -> { + assertThat(threadPool).isInstanceOf(QueuedThreadPool.class); + QueuedThreadPool queuedThreadPool = (QueuedThreadPool) threadPool; + Executor executor = queuedThreadPool.getVirtualThreadsExecutor(); + assertThat(executor).isNotNull(); + AtomicReference threadName = new AtomicReference<>(); + executor.execute(() -> threadName.set(Thread.currentThread().getName())); + Awaitility.await().atMost(Duration.ofSeconds(1)).untilAtomic(threadName, Matchers.notNullValue()); + assertThat(threadName.get()).startsWith("jetty-"); + })); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizerTests.java index c024fc15c6cf..eef94bb88a20 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizerTests.java @@ -47,7 +47,6 @@ import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.source.ConfigurationPropertySources; import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories; -import org.springframework.boot.testsupport.web.servlet.Servlet5ClassPathOverrides; import org.springframework.boot.web.embedded.jetty.ConfigurableJettyWebServerFactory; import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; import org.springframework.boot.web.embedded.jetty.JettyWebServer; @@ -67,7 +66,6 @@ * @author HaiTao Zhang */ @DirtiesUrlFactories -@Servlet5ClassPathOverrides class JettyWebServerFactoryCustomizerTests { private MockEnvironment environment; @@ -263,30 +261,6 @@ void setUseForwardHeaders() { then(factory).should().setUseForwardHeaders(true); } - @Test - void customizeMaxHttpHeaderSize() { - bind("server.max-http-header-size=2048"); - JettyWebServer server = customizeAndGetServer(); - List requestHeaderSizes = getRequestHeaderSizes(server); - assertThat(requestHeaderSizes).containsOnly(2048); - } - - @Test - void customMaxHttpHeaderSizeIgnoredIfNegative() { - bind("server.max-http-header-size=-1"); - JettyWebServer server = customizeAndGetServer(); - List requestHeaderSizes = getRequestHeaderSizes(server); - assertThat(requestHeaderSizes).containsOnly(8192); - } - - @Test - void customMaxHttpHeaderSizeIgnoredIfZero() { - bind("server.max-http-header-size=0"); - JettyWebServer server = customizeAndGetServer(); - List requestHeaderSizes = getRequestHeaderSizes(server); - assertThat(requestHeaderSizes).containsOnly(8192); - } - @Test void customizeMaxRequestHttpHeaderSize() { bind("server.max-http-request-header-size=2048"); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizerTests.java index 1b99ff688f73..70046794354d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizerTests.java @@ -144,7 +144,6 @@ void configureHttpRequestDecoder() { nettyProperties.setValidateHeaders(false); nettyProperties.setInitialBufferSize(DataSize.ofBytes(512)); nettyProperties.setH2cMaxContentLength(DataSize.ofKilobytes(1)); - setMaxChunkSize(nettyProperties); nettyProperties.setMaxInitialLineLength(DataSize.ofKilobytes(32)); NettyReactiveWebServerFactory factory = mock(NettyReactiveWebServerFactory.class); this.customizer.customize(factory); @@ -156,20 +155,9 @@ void configureHttpRequestDecoder() { assertThat(decoder.maxHeaderSize()).isEqualTo(this.serverProperties.getMaxHttpRequestHeaderSize().toBytes()); assertThat(decoder.initialBufferSize()).isEqualTo(nettyProperties.getInitialBufferSize().toBytes()); assertThat(decoder.h2cMaxContentLength()).isEqualTo(nettyProperties.getH2cMaxContentLength().toBytes()); - assertMaxChunkSize(nettyProperties, decoder); assertThat(decoder.maxInitialLineLength()).isEqualTo(nettyProperties.getMaxInitialLineLength().toBytes()); } - @SuppressWarnings("removal") - private void setMaxChunkSize(ServerProperties.Netty nettyProperties) { - nettyProperties.setMaxChunkSize(DataSize.ofKilobytes(16)); - } - - @SuppressWarnings({ "deprecation", "removal" }) - private void assertMaxChunkSize(ServerProperties.Netty nettyProperties, HttpRequestDecoderSpec decoder) { - assertThat(decoder.maxChunkSize()).isEqualTo(nettyProperties.getMaxChunkSize().toBytes()); - } - private void verifyConnectionTimeout(NettyReactiveWebServerFactory factory, Integer expected) { if (expected == null) { then(factory).should(never()).addServerCustomizers(any(NettyServerCustomizer.class)); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizerTests.java new file mode 100644 index 000000000000..5fcf72d1f937 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizerTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.embedded; + +import java.util.function.Consumer; + +import org.apache.tomcat.util.threads.VirtualThreadExecutor; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; + +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.embedded.tomcat.TomcatWebServer; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link TomcatVirtualThreadsWebServerFactoryCustomizer}. + * + * @author Moritz Halbritter + */ +class TomcatVirtualThreadsWebServerFactoryCustomizerTests { + + private final TomcatVirtualThreadsWebServerFactoryCustomizer customizer = new TomcatVirtualThreadsWebServerFactoryCustomizer(); + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void shouldSetVirtualThreadExecutor() { + withWebServer((webServer) -> assertThat(webServer.getTomcat().getConnector().getProtocolHandler().getExecutor()) + .isInstanceOf(VirtualThreadExecutor.class)); + } + + private TomcatWebServer getWebServer() { + TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(0); + this.customizer.customize(factory); + return (TomcatWebServer) factory.getWebServer(); + } + + private void withWebServer(Consumer callback) { + TomcatWebServer webServer = getWebServer(); + webServer.start(); + try { + callback.accept(webServer); + } + finally { + webServer.stop(); + } + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java index c24b4f179cde..f3c1ed493041 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,6 +58,7 @@ * @author Rafiullah Hamedy * @author Victor Mandujano * @author Parviz Rozikov + * @author Moritz Halbritter */ class TomcatWebServerFactoryCustomizerTests { @@ -176,47 +177,6 @@ void customMaxHttpFormPostSize() { (server) -> assertThat(server.getTomcat().getConnector().getMaxPostSize()).isEqualTo(10000)); } - @Test - void customMaxHttpHeaderSize() { - bind("server.max-http-header-size=1KB"); - customizeAndRunServer((server) -> assertThat( - ((AbstractHttp11Protocol) server.getTomcat().getConnector().getProtocolHandler()) - .getMaxHttpRequestHeaderSize()) - .isEqualTo(DataSize.ofKilobytes(1).toBytes())); - } - - @Test - void customMaxHttpHeaderSizeWithHttp2() { - bind("server.max-http-header-size=1KB", "server.http2.enabled=true"); - customizeAndRunServer((server) -> { - AbstractHttp11Protocol protocolHandler = (AbstractHttp11Protocol) server.getTomcat() - .getConnector() - .getProtocolHandler(); - long expectedSize = DataSize.ofKilobytes(1).toBytes(); - assertThat(protocolHandler.getMaxHttpRequestHeaderSize()).isEqualTo(expectedSize); - assertThat(((Http2Protocol) protocolHandler.getUpgradeProtocol("h2c")).getMaxHeaderSize()) - .isEqualTo(expectedSize); - }); - } - - @Test - void customMaxHttpHeaderSizeIgnoredIfNegative() { - bind("server.max-http-header-size=-1"); - customizeAndRunServer((server) -> assertThat( - ((AbstractHttp11Protocol) server.getTomcat().getConnector().getProtocolHandler()) - .getMaxHttpRequestHeaderSize()) - .isEqualTo(DataSize.ofKilobytes(8).toBytes())); - } - - @Test - void customMaxHttpHeaderSizeIgnoredIfZero() { - bind("server.max-http-header-size=0"); - customizeAndRunServer((server) -> assertThat( - ((AbstractHttp11Protocol) server.getTomcat().getConnector().getProtocolHandler()) - .getMaxHttpRequestHeaderSize()) - .isEqualTo(DataSize.ofKilobytes(8).toBytes())); - } - @Test void defaultMaxHttpRequestHeaderSize() { customizeAndRunServer((server) -> assertThat( @@ -436,16 +396,6 @@ void disableRemoteIpValve() { assertThat(factory.getEngineValves()).isEmpty(); } - @Test - @Deprecated(since = "2.7.12", forRemoval = true) - void testCustomizeRejectIllegalHeader() { - bind("server.tomcat.reject-illegal-header=false"); - customizeAndRunServer((server) -> assertThat( - ((AbstractHttp11Protocol) server.getTomcat().getConnector().getProtocolHandler()) - .getRejectIllegalHeader()) - .isFalse()); - } - @Test void errorReportValveIsConfiguredToNotReportStackTraces() { TomcatWebServer server = customizeAndGetServer(); @@ -615,6 +565,18 @@ void ajpConnectorCanBeCustomized() { server.stop(); } + @Test + void configureExecutor() { + bind("server.tomcat.threads.max=10", "server.tomcat.threads.min-spare=2", + "server.tomcat.threads.max-queue-capacity=20"); + customizeAndRunServer((server) -> { + AbstractProtocol protocol = (AbstractProtocol) server.getTomcat().getConnector().getProtocolHandler(); + assertThat(protocol.getMaxThreads()).isEqualTo(10); + assertThat(protocol.getMinSpareThreads()).isEqualTo(2); + assertThat(protocol.getMaxQueueSize()).isEqualTo(20); + }); + } + private void bind(String... inlinedProperties) { TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, inlinedProperties); new Binder(ConfigurationPropertySources.get(this.environment)).bind("server", diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java index 934f0689223a..e5a2c95e8271 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java @@ -85,24 +85,6 @@ void customizeUndertowAccessLog() { then(factory).should().setAccessLogRotate(false); } - @Test - void customMaxHttpHeaderSize() { - bind("server.max-http-header-size=2048"); - assertThat(boundServerOption(UndertowOptions.MAX_HEADER_SIZE)).isEqualTo(2048); - } - - @Test - void customMaxHttpHeaderSizeIgnoredIfNegative() { - bind("server.max-http-header-size=-1"); - assertThat(boundServerOption(UndertowOptions.MAX_HEADER_SIZE)).isNull(); - } - - @Test - void customMaxHttpHeaderSizeIgnoredIfZero() { - bind("server.max-http-header-size=0"); - assertThat(boundServerOption(UndertowOptions.MAX_HEADER_SIZE)).isNull(); - } - @Test void customMaxHttpRequestHeaderSize() { bind("server.max-http-request-header-size=2048"); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfigurationTests.java index 70df1d0c2e23..1537780e8155 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,8 +23,13 @@ import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ContextPathCompositeHandler; import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.http.server.reactive.MockServerHttpResponse; import org.springframework.web.reactive.DispatcherHandler; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.ServerResponse; @@ -40,6 +45,7 @@ * @author Brian Clozel * @author Stephane Nicoll * @author Andy Wilkinson + * @author Lasse Wulff */ class HttpHandlerAutoConfigurationTests { @@ -66,6 +72,20 @@ void shouldConfigureHttpHandlerWithoutWebFluxAutoConfiguration() { .run((context) -> assertThat(context).hasSingleBean(HttpHandler.class)); } + @Test + void customizersAreCalled() { + this.contextRunner.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class)) + .withUserConfiguration(WebHttpHandlerBuilderCustomizers.class) + .run((context) -> { + assertThat(context).hasSingleBean(HttpHandler.class); + HttpHandler httpHandler = context.getBean(HttpHandler.class); + ServerHttpRequest request = MockServerHttpRequest.get("").build(); + ServerHttpResponse response = new MockServerHttpResponse(); + httpHandler.handle(request, response).block(); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.I_AM_A_TEAPOT); + }); + } + @Test void shouldConfigureBasePathCompositeHandler() { this.contextRunner.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class)) @@ -104,4 +124,18 @@ WebHandler webHandler() { } + @Configuration(proxyBeanMethods = false) + static class WebHttpHandlerBuilderCustomizers { + + @Bean + WebHttpHandlerBuilderCustomizer customizerDecorator() { + return (webHttpHandlerBuilder) -> webHttpHandlerBuilder + .httpHandlerDecorator(((httpHandler) -> (request, response) -> { + response.setStatusCode(HttpStatus.I_AM_A_TEAPOT); + return response.setComplete(); + })); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveMultipartAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveMultipartAutoConfigurationTests.java index d5925346e13f..ae3d0099ce22 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveMultipartAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveMultipartAutoConfigurationTests.java @@ -84,17 +84,21 @@ void shouldConfigureMultipartPropertiesForDefaultReader() { void shouldConfigureMultipartPropertiesForPartEventReader() { this.contextRunner .withPropertyValues("spring.webflux.multipart.max-in-memory-size=1GB", - "spring.webflux.multipart.max-headers-size=16KB", "spring.webflux.multipart.headers-charset:UTF_16") + "spring.webflux.multipart.max-headers-size=16KB", + "spring.webflux.multipart.max-disk-usage-per-part=3GB", "spring.webflux.multipart.max-parts=7", + "spring.webflux.multipart.headers-charset:UTF_16") .run((context) -> { CodecCustomizer customizer = context.getBean(CodecCustomizer.class); DefaultServerCodecConfigurer configurer = new DefaultServerCodecConfigurer(); customizer.customize(configurer); PartEventHttpMessageReader partReader = getPartEventReader(configurer); + assertThat(partReader).hasFieldOrPropertyWithValue("maxParts", 7); assertThat(partReader).hasFieldOrPropertyWithValue("maxHeadersSize", Math.toIntExact(DataSize.ofKilobytes(16).toBytes())); assertThat(partReader).hasFieldOrPropertyWithValue("headersCharset", StandardCharsets.UTF_16); assertThat(partReader).hasFieldOrPropertyWithValue("maxInMemorySize", Math.toIntExact(DataSize.ofGigabytes(1).toBytes())); + assertThat(partReader).hasFieldOrPropertyWithValue("maxPartSize", DataSize.ofGigabytes(3).toBytes()); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfigurationTests.java index 7b1b38cf2678..044c1bfff9f1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfigurationTests.java @@ -31,7 +31,6 @@ import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories; -import org.springframework.boot.testsupport.web.servlet.Servlet5ClassPathOverrides; import org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory; import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; @@ -231,7 +230,6 @@ void tomcatProtocolHandlerCustomizerRegisteredAsBeanAndViaFactoryIsOnlyCalledOnc } @Test - @Servlet5ClassPathOverrides void jettyServerCustomizerBeanIsAddedToFactory() { new ReactiveWebApplicationContextRunner(AnnotationConfigReactiveWebApplicationContext::new) .withConfiguration(AutoConfigurations.of(ReactiveWebServerFactoryAutoConfiguration.class)) @@ -244,7 +242,6 @@ void jettyServerCustomizerBeanIsAddedToFactory() { } @Test - @Servlet5ClassPathOverrides void jettyServerCustomizerRegisteredAsBeanAndViaFactoryIsOnlyCalledOnce() { new ReactiveWebApplicationContextRunner(AnnotationConfigReactiveWebServerApplicationContext::new) .withConfiguration(AutoConfigurations.of(ReactiveWebServerFactoryAutoConfiguration.class)) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java index 15759a908e2d..27ae96ee440e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -36,17 +37,23 @@ import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.assertj.core.api.Assertions; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.aop.support.AopUtils; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration; +import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.autoconfigure.validation.ValidatorAdapter; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration.WebFluxConfig; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfigurationTests.OrderedControllerAdviceBeansConfiguration.HighestOrderedControllerAdvice; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfigurationTests.OrderedControllerAdviceBeansConfiguration.LowestOrderedControllerAdvice; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; @@ -62,6 +69,7 @@ import org.springframework.core.annotation.Order; import org.springframework.core.convert.ConversionService; import org.springframework.core.io.ClassPathResource; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.format.Parser; import org.springframework.format.Printer; import org.springframework.format.support.FormattingConversionService; @@ -77,8 +85,10 @@ import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.filter.reactive.HiddenHttpMethodFilter; +import org.springframework.web.method.ControllerAdviceBean; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.accept.RequestedContentTypeResolver; +import org.springframework.web.reactive.config.BlockingExecutionConfigurer; import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration; import org.springframework.web.reactive.config.WebFluxConfigurationSupport; import org.springframework.web.reactive.config.WebFluxConfigurer; @@ -101,8 +111,11 @@ import org.springframework.web.server.i18n.FixedLocaleContextResolver; import org.springframework.web.server.i18n.LocaleContextResolver; import org.springframework.web.server.session.CookieWebSessionIdResolver; +import org.springframework.web.server.session.DefaultWebSessionManager; +import org.springframework.web.server.session.InMemoryWebSessionStore; import org.springframework.web.server.session.WebSessionIdResolver; import org.springframework.web.server.session.WebSessionManager; +import org.springframework.web.server.session.WebSessionStore; import org.springframework.web.util.pattern.PathPattern; import static org.assertj.core.api.Assertions.assertThat; @@ -190,7 +203,9 @@ void shouldMapResourcesToCustomPath() { SimpleUrlHandlerMapping hm = context.getBean("resourceHandlerMapping", SimpleUrlHandlerMapping.class); assertThat(hm.getUrlMap().get("/static/**")).isInstanceOf(ResourceWebHandler.class); ResourceWebHandler staticHandler = (ResourceWebHandler) hm.getUrlMap().get("/static/**"); - assertThat(staticHandler).extracting("locationValues").asList().hasSize(4); + assertThat(staticHandler).extracting("locationValues") + .asInstanceOf(InstanceOfAssertFactories.LIST) + .hasSize(4); }); } @@ -591,7 +606,7 @@ void userConfigurersCanBeOrderedBeforeOrAfterTheAutoConfiguredConfigurer() { .withBean(LowPrecedenceConfigurer.class, LowPrecedenceConfigurer::new) .run((context) -> assertThat(context.getBean(DelegatingWebFluxConfiguration.class)) .extracting("configurers.delegates") - .asList() + .asInstanceOf(InstanceOfAssertFactories.LIST) .extracting((configurer) -> (Class) configurer.getClass()) .containsExactly(HighPrecedenceConfigurer.class, WebFluxConfig.class, LowPrecedenceConfigurer.class)); } @@ -612,6 +627,18 @@ void customSessionTimeoutConfigurationShouldBeApplied() { }))); } + @Test + void customSessionMaxSessionsConfigurationShouldBeApplied() { + this.contextRunner.withPropertyValues("server.reactive.session.max-sessions:123") + .run(assertMaxSessionsWithWebSession(123)); + } + + @Test + void defaultSessionMaxSessionsConfigurationShouldBeInSync() { + int defaultMaxSessions = new InMemoryWebSessionStore().getMaxSessions(); + this.contextRunner.run(assertMaxSessionsWithWebSession(defaultMaxSessions)); + } + @Test void customSessionCookieConfigurationShouldBeApplied() { this.contextRunner.withPropertyValues("server.reactive.session.cookie.name:JSESSIONID", @@ -667,6 +694,76 @@ void problemDetailsBacksOffWhenExceptionHandler() { .hasSingleBean(CustomExceptionHandler.class)); } + @Test + void problemDetailsExceptionHandlerIsOrderedAt0() { + this.contextRunner.withPropertyValues("spring.webflux.problemdetails.enabled:true") + .withUserConfiguration(OrderedControllerAdviceBeansConfiguration.class) + .run((context) -> assertThat( + ControllerAdviceBean.findAnnotatedBeans(context).stream().map(ControllerAdviceBean::getBeanType)) + .asInstanceOf(InstanceOfAssertFactories.list(Class.class)) + .containsExactly(HighestOrderedControllerAdvice.class, ProblemDetailsExceptionHandler.class, + LowestOrderedControllerAdvice.class)); + } + + @Test + void asyncTaskExecutorWithPlatformThreadsAndApplicationTaskExecutor() { + this.contextRunner.withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class)) + .run((context) -> { + assertThat(context).hasSingleBean(AsyncTaskExecutor.class); + assertThat(context.getBean(RequestMappingHandlerAdapter.class)).extracting("scheduler.executor") + .isNull(); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void asyncTaskExecutorWithVirtualThreadsAndApplicationTaskExecutor() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true") + .withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class)) + .run((context) -> { + assertThat(context).hasSingleBean(AsyncTaskExecutor.class); + assertThat(context.getBean(RequestMappingHandlerAdapter.class)).extracting("scheduler.executor") + .isSameAs(context.getBean("applicationTaskExecutor")); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void asyncTaskExecutorWithVirtualThreadsAndNonMatchApplicationTaskExecutorBean() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true") + .withUserConfiguration(CustomApplicationTaskExecutorConfig.class) + .withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class)) + .run((context) -> { + assertThat(context).doesNotHaveBean(AsyncTaskExecutor.class); + assertThat(context.getBean(RequestMappingHandlerAdapter.class)).extracting("scheduler.executor") + .isNotSameAs(context.getBean("applicationTaskExecutor")); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void asyncTaskExecutorWithVirtualThreadsAndWebFluxConfigurerCanOverrideExecutor() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true") + .withUserConfiguration(CustomAsyncTaskExecutorConfigurer.class) + .withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class)) + .run((context) -> assertThat(context.getBean(RequestMappingHandlerAdapter.class)) + .extracting("scheduler.executor") + .isSameAs(context.getBean(CustomAsyncTaskExecutorConfigurer.class).taskExecutor)); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void asyncTaskExecutorWithVirtualThreadsAndCustomNonApplicationTaskExecutor() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true") + .withUserConfiguration(CustomAsyncTaskExecutorConfig.class) + .withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class)) + .run((context) -> { + assertThat(context).hasSingleBean(AsyncTaskExecutor.class); + assertThat(context.getBean(RequestMappingHandlerAdapter.class)).extracting("scheduler.executor") + .isNull(); + }); + } + private ContextConsumer assertExchangeWithSession( Consumer exchange) { return (context) -> { @@ -691,6 +788,16 @@ private ContextConsumer assertSessionTimeoutWithW }; } + private ContextConsumer assertMaxSessionsWithWebSession(int maxSessions) { + return (context) -> { + WebSessionManager sessionManager = context.getBean(WebSessionManager.class); + assertThat(sessionManager).isInstanceOf(DefaultWebSessionManager.class); + WebSessionStore sessionStore = ((DefaultWebSessionManager) sessionManager).getSessionStore(); + assertThat(sessionStore).isInstanceOf(InMemoryWebSessionStore.class); + assertThat(((InMemoryWebSessionStore) sessionStore).getMaxSessions()).isEqualTo(maxSessions); + }; + } + private Map getHandlerMap(ApplicationContext context) { HandlerMapping mapping = context.getBean("resourceHandlerMapping", HandlerMapping.class); if (mapping instanceof SimpleUrlHandlerMapping simpleMapping) { @@ -971,6 +1078,24 @@ static class CustomExceptionHandler extends ResponseEntityExceptionHandler { } + @Configuration(proxyBeanMethods = false) + @Import({ LowestOrderedControllerAdvice.class, HighestOrderedControllerAdvice.class }) + static class OrderedControllerAdviceBeansConfiguration { + + @ControllerAdvice + @Order + static class LowestOrderedControllerAdvice { + + } + + @ControllerAdvice + @Order(Ordered.HIGHEST_PRECEDENCE) + static class HighestOrderedControllerAdvice { + + } + + } + @Aspect static class ExceptionHandlerInterceptor { @@ -981,4 +1106,36 @@ void exceptionHandlerIntercept(JoinPoint joinPoint, Object returnValue) { } + @Configuration(proxyBeanMethods = false) + static class CustomApplicationTaskExecutorConfig { + + @Bean + Executor applicationTaskExecutor() { + return mock(Executor.class); + } + + } + + @Configuration(proxyBeanMethods = false) + static class CustomAsyncTaskExecutorConfig { + + @Bean + AsyncTaskExecutor customTaskExecutor() { + return mock(AsyncTaskExecutor.class); + } + + } + + @Configuration(proxyBeanMethods = false) + static class CustomAsyncTaskExecutorConfigurer implements WebFluxConfigurer { + + private final AsyncTaskExecutor taskExecutor = mock(AsyncTaskExecutor.class); + + @Override + public void configureBlockingExecution(BlockingExecutionConfigurer configurer) { + configurer.setExecutor(this.taskExecutor); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerIntegrationTests.java index 430ea1e7f118..8aec51f70eb8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,9 +25,12 @@ import org.junit.jupiter.api.extension.ExtendWith; import reactor.core.publisher.Mono; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.autoconfigure.web.WebProperties; import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; @@ -36,12 +39,17 @@ import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.boot.web.error.ErrorAttributeOptions; +import org.springframework.boot.web.error.ErrorAttributeOptions.Include; import org.springframework.boot.web.reactive.error.DefaultErrorAttributes; import org.springframework.boot.web.reactive.error.ErrorAttributes; +import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.test.web.reactive.server.HttpHandlerConnector.FailureAfterResponseCompletedException; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.bind.annotation.GetMapping; @@ -50,6 +58,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; @@ -573,6 +582,21 @@ void defaultErrorAttributesSubclassWithoutDelegation() { }); } + @Test + void customErrorWebExceptionHandlerWithoutStatus() { + this.contextRunner.withUserConfiguration(CustomErrorWebExceptionHandlerWithoutStatus.class).run((context) -> { + WebTestClient client = getWebClient(context); + client.get() + .uri("/badRequest") + .exchange() + .expectStatus() + .isBadRequest() + .expectBody() + .jsonPath("status") + .doesNotExist(); + }); + } + private String getErrorTemplatesLocation() { String packageName = getClass().getPackage().getName(); return "classpath:/" + packageName.replace('.', '/') + "/templates/"; @@ -675,4 +699,29 @@ public Map getErrorAttributes(ServerRequest request, ErrorAttrib } + static class CustomErrorWebExceptionHandlerWithoutStatus { + + @Bean + @Order(-1) + ErrorWebExceptionHandler errorWebExceptionHandler(ServerProperties serverProperties, + ErrorAttributes errorAttributes, WebProperties webProperties, + ObjectProvider viewResolvers, ServerCodecConfigurer serverCodecConfigurer, + ApplicationContext applicationContext) { + DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler(errorAttributes, + webProperties.getResources(), serverProperties.getError(), applicationContext) { + + @Override + protected ErrorAttributeOptions getErrorAttributeOptions(ServerRequest request, MediaType mediaType) { + return super.getErrorAttributeOptions(request, mediaType).excluding(Include.STATUS, Include.ERROR); + } + + }; + exceptionHandler.setViewResolvers(viewResolvers.orderedStream().toList()); + exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters()); + exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders()); + return exceptionHandler; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerTests.java index c61f09fba999..0d3683139470 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import java.util.Collections; import java.util.List; -import java.util.Map; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; @@ -33,12 +32,10 @@ import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.result.view.View; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.adapter.HttpWebHandlerAdapter; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -54,19 +51,10 @@ */ class DefaultErrorWebExceptionHandlerTests { - @Test - void disconnectedClientExceptionsMatchesFramework() { - Object errorHandlers = ReflectionTestUtils.getField(AbstractErrorWebExceptionHandler.class, - "DISCONNECTED_CLIENT_EXCEPTIONS"); - Object webHandlers = ReflectionTestUtils.getField(HttpWebHandlerAdapter.class, - "DISCONNECTED_CLIENT_EXCEPTIONS"); - assertThat(errorHandlers).isNotNull().isEqualTo(webHandlers); - } - @Test void nonStandardErrorStatusCodeShouldNotFail() { ErrorAttributes errorAttributes = mock(ErrorAttributes.class); - given(errorAttributes.getErrorAttributes(any(), any())).willReturn(getErrorAttributes()); + given(errorAttributes.getErrorAttributes(any(), any())).willReturn(Collections.singletonMap("status", 498)); Resources resourceProperties = new Resources(); ErrorProperties errorProperties = new ErrorProperties(); ApplicationContext context = new AnnotationConfigReactiveWebApplicationContext(); @@ -78,10 +66,6 @@ void nonStandardErrorStatusCodeShouldNotFail() { exceptionHandler.handle(exchange, new RuntimeException()).block(); } - private Map getErrorAttributes() { - return Collections.singletonMap("status", 498); - } - private void setupViewResolver(DefaultErrorWebExceptionHandler exceptionHandler) { View view = mock(View.class); given(view.render(any(), any(), any())).willReturn(Mono.empty()); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfigurationTests.java index 2a05496baf18..9c0d2ee1f2b3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfigurationTests.java @@ -17,7 +17,6 @@ package org.springframework.boot.autoconfigure.web.reactive.function.client; import org.apache.hc.client5.http.impl.async.HttpAsyncClients; -import org.eclipse.jetty.reactive.client.ReactiveRequest; import org.junit.jupiter.api.Test; import reactor.netty.http.client.HttpClient; @@ -28,9 +27,9 @@ import org.springframework.boot.web.reactive.function.client.WebClientCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ReactorResourceFactory; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.http.client.reactive.ReactorResourceFactory; import org.springframework.web.reactive.function.client.WebClient; import static org.assertj.core.api.Assertions.assertThat; @@ -62,36 +61,20 @@ void whenReactorIsAvailableThenReactorBeansAreDefined() { } @Test - void whenReactorIsUnavailableThenJettyBeansAreDefined() { + void whenReactorIsUnavailableThenHttpClientBeansAreDefined() { this.contextRunner.withClassLoader(new FilteredClassLoader(HttpClient.class)).run((context) -> { BeanDefinition customizerDefinition = context.getBeanFactory() .getBeanDefinition("webClientHttpConnectorCustomizer"); assertThat(customizerDefinition.isLazyInit()).isTrue(); BeanDefinition connectorDefinition = context.getBeanFactory().getBeanDefinition("webClientHttpConnector"); assertThat(connectorDefinition.isLazyInit()).isTrue(); - assertThat(context).hasBean("jettyClientResourceFactory"); - assertThat(context).hasBean("jettyClientHttpConnectorFactory"); + assertThat(context).hasBean("httpComponentsClientHttpConnectorFactory"); }); } @Test - void whenReactorAndJettyAreUnavailableThenHttpClientBeansAreDefined() { - this.contextRunner.withClassLoader(new FilteredClassLoader(HttpClient.class, ReactiveRequest.class)) - .run((context) -> { - BeanDefinition customizerDefinition = context.getBeanFactory() - .getBeanDefinition("webClientHttpConnectorCustomizer"); - assertThat(customizerDefinition.isLazyInit()).isTrue(); - BeanDefinition connectorDefinition = context.getBeanFactory() - .getBeanDefinition("webClientHttpConnector"); - assertThat(connectorDefinition.isLazyInit()).isTrue(); - assertThat(context).hasBean("httpComponentsClientHttpConnectorFactory"); - }); - } - - @Test - void whenReactorJettyAndHttpClientBeansAreUnavailableThenJdkClientBeansAreDefined() { - this.contextRunner - .withClassLoader(new FilteredClassLoader(HttpClient.class, ReactiveRequest.class, HttpAsyncClients.class)) + void whenReactorAndHttpClientBeansAreUnavailableThenJdkClientBeansAreDefined() { + this.contextRunner.withClassLoader(new FilteredClassLoader(HttpClient.class, HttpAsyncClients.class)) .run((context) -> { BeanDefinition customizerDefinition = context.getBeanFactory() .getBeanDefinition("webClientHttpConnectorCustomizer"); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorFactoryConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorFactoryConfigurationTests.java index 79991a079b64..5d7fd0fab66e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorFactoryConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorFactoryConfigurationTests.java @@ -16,11 +16,6 @@ package org.springframework.boot.autoconfigure.web.reactive.function.client; -import java.util.concurrent.Executor; - -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.io.ByteBufferPool; -import org.eclipse.jetty.util.thread.Scheduler; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -32,13 +27,9 @@ import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.http.client.reactive.HttpComponentsClientHttpConnector; -import org.springframework.http.client.reactive.JettyClientHttpConnector; -import org.springframework.http.client.reactive.JettyResourceFactory; -import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.then; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; /** @@ -50,40 +41,6 @@ */ class ClientHttpConnectorFactoryConfigurationTests { - @Test - void jettyClientHttpConnectorAppliesJettyResourceFactory() { - Executor executor = mock(Executor.class); - ByteBufferPool byteBufferPool = mock(ByteBufferPool.class); - Scheduler scheduler = mock(Scheduler.class); - JettyResourceFactory jettyResourceFactory = new JettyResourceFactory(); - jettyResourceFactory.setExecutor(executor); - jettyResourceFactory.setByteBufferPool(byteBufferPool); - jettyResourceFactory.setScheduler(scheduler); - JettyClientHttpConnectorFactory connectorFactory = getJettyClientHttpConnectorFactory(jettyResourceFactory); - JettyClientHttpConnector connector = connectorFactory.createClientHttpConnector(); - HttpClient httpClient = (HttpClient) ReflectionTestUtils.getField(connector, "httpClient"); - assertThat(httpClient.getExecutor()).isSameAs(executor); - assertThat(httpClient.getByteBufferPool()).isSameAs(byteBufferPool); - assertThat(httpClient.getScheduler()).isSameAs(scheduler); - } - - @Test - void JettyResourceFactoryHasSslContextFactory() { - // gh-16810 - JettyResourceFactory jettyResourceFactory = new JettyResourceFactory(); - JettyClientHttpConnectorFactory connectorFactory = getJettyClientHttpConnectorFactory(jettyResourceFactory); - JettyClientHttpConnector connector = connectorFactory.createClientHttpConnector(); - HttpClient httpClient = (HttpClient) ReflectionTestUtils.getField(connector, "httpClient"); - assertThat(httpClient.getSslContextFactory()).isNotNull(); - } - - private JettyClientHttpConnectorFactory getJettyClientHttpConnectorFactory( - JettyResourceFactory jettyResourceFactory) { - ClientHttpConnectorFactoryConfiguration.JettyClient jettyClient = new ClientHttpConnectorFactoryConfiguration.JettyClient(); - // We shouldn't usually call this method directly since it's on a non-proxy config - return ReflectionTestUtils.invokeMethod(jettyClient, "jettyClientHttpConnectorFactory", jettyResourceFactory); - } - @Test void shouldApplyHttpClientMapper() { JksSslStoreDetails storeDetails = JksSslStoreDetails.forLocation("classpath:test.jks"); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/JettyClientHttpConnectorFactoryTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/JettyClientHttpConnectorFactoryTests.java deleted file mode 100644 index ad99f85a778a..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/JettyClientHttpConnectorFactoryTests.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.web.reactive.function.client; - -import org.springframework.http.client.reactive.JettyResourceFactory; - -/** - * Tests for {@link JettyClientHttpConnectorFactory}. - * - * @author Phillip Webb - */ -class JettyClientHttpConnectorFactoryTests extends AbstractClientHttpConnectorFactoryTests { - - @Override - protected ClientHttpConnectorFactory getFactory() { - JettyResourceFactory resourceFactory = new JettyResourceFactory(); - return new JettyClientHttpConnectorFactory(resourceFactory); - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ReactorClientHttpConnectorFactoryTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ReactorClientHttpConnectorFactoryTests.java index 632d8f707636..951941d02446 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ReactorClientHttpConnectorFactoryTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ReactorClientHttpConnectorFactoryTests.java @@ -19,7 +19,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.springframework.http.client.reactive.ReactorResourceFactory; +import org.springframework.http.client.ReactorResourceFactory; /** * Tests for {@link ReactorClientHttpConnectorFactory}. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfigurationTests.java index 0dbf0eaf0ad6..ca0594937057 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfigurationTests.java @@ -141,7 +141,6 @@ void renamesMultipartResolver() { void dispatcherServletDefaultConfig() { this.contextRunner.run((context) -> { DispatcherServlet dispatcherServlet = context.getBean(DispatcherServlet.class); - assertThat(dispatcherServlet).extracting("throwExceptionIfNoHandlerFound").isEqualTo(false); assertThat(dispatcherServlet).extracting("dispatchOptionsRequest").isEqualTo(true); assertThat(dispatcherServlet).extracting("dispatchTraceRequest").isEqualTo(false); assertThat(dispatcherServlet).extracting("enableLoggingRequestDetails").isEqualTo(false); @@ -151,15 +150,22 @@ void dispatcherServletDefaultConfig() { }); } + @Test + @Deprecated(since = "3.2.0", forRemoval = true) + void dispatcherServletThrowExceptionIfNoHandlerFoundDefaultConfig() { + this.contextRunner.run((context) -> { + DispatcherServlet dispatcherServlet = context.getBean(DispatcherServlet.class); + assertThat(dispatcherServlet).extracting("throwExceptionIfNoHandlerFound").isEqualTo(true); + }); + } + @Test void dispatcherServletCustomConfig() { this.contextRunner - .withPropertyValues("spring.mvc.throw-exception-if-no-handler-found:true", - "spring.mvc.dispatch-options-request:false", "spring.mvc.dispatch-trace-request:true", + .withPropertyValues("spring.mvc.dispatch-options-request:false", "spring.mvc.dispatch-trace-request:true", "spring.mvc.publish-request-handled-events:false", "spring.mvc.servlet.load-on-startup=5") .run((context) -> { DispatcherServlet dispatcherServlet = context.getBean(DispatcherServlet.class); - assertThat(dispatcherServlet).extracting("throwExceptionIfNoHandlerFound").isEqualTo(true); assertThat(dispatcherServlet).extracting("dispatchOptionsRequest").isEqualTo(false); assertThat(dispatcherServlet).extracting("dispatchTraceRequest").isEqualTo(true); assertThat(dispatcherServlet).extracting("publishEvents").isEqualTo(false); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/MultipartAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/MultipartAutoConfigurationTests.java index b1e1dd0926a0..edbc441d871f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/MultipartAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/MultipartAutoConfigurationTests.java @@ -31,7 +31,6 @@ import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.boot.testsupport.classpath.ForkedClassPath; import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories; -import org.springframework.boot.testsupport.web.servlet.Servlet5ClassPathOverrides; import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; @@ -65,6 +64,7 @@ * @author Josh Long * @author Ivan Sopov * @author Toshiaki Maki + * @author Yanming Zhou */ @DirtiesUrlFactories class MultipartAutoConfigurationTests { @@ -175,6 +175,17 @@ void configureResolveLazily() { assertThat(multipartResolver).hasFieldOrPropertyWithValue("resolveLazily", true); } + @Test + void configureStrictServletCompliance() { + this.context = new AnnotationConfigServletWebServerApplicationContext(); + TestPropertyValues.of("spring.servlet.multipart.strict-servlet-compliance=true").applyTo(this.context); + this.context.register(WebServerWithNothing.class, BaseConfiguration.class); + this.context.refresh(); + StandardServletMultipartResolver multipartResolver = this.context + .getBean(StandardServletMultipartResolver.class); + assertThat(multipartResolver).hasFieldOrPropertyWithValue("strictServletCompliance", true); + } + @Test void configureMultipartProperties() { this.context = new AnnotationConfigServletWebServerApplicationContext(); @@ -221,7 +232,6 @@ static class WebServerWithNothing { } - @Servlet5ClassPathOverrides @Configuration(proxyBeanMethods = false) static class WebServerWithNoMultipartJetty { @@ -282,7 +292,6 @@ WebController controller() { } - @Servlet5ClassPathOverrides @Configuration(proxyBeanMethods = false) static class WebServerWithEverythingJetty { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfigurationTests.java index 6555da75f8ff..a3211bd3417b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfigurationTests.java @@ -38,7 +38,6 @@ import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories; -import org.springframework.boot.testsupport.web.servlet.Servlet5ClassPathOverrides; import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer; import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; @@ -156,7 +155,6 @@ void initParametersAreConfiguredOnTheServletContext() { } @Test - @Servlet5ClassPathOverrides void jettyServerCustomizerBeanIsAddedToFactory() { WebApplicationContextRunner runner = new WebApplicationContextRunner( AnnotationConfigServletWebServerApplicationContext::new) @@ -171,7 +169,6 @@ void jettyServerCustomizerBeanIsAddedToFactory() { } @Test - @Servlet5ClassPathOverrides void jettyServerCustomizerRegisteredAsBeanAndViaFactoryIsOnlyCalledOnce() { WebApplicationContextRunner runner = new WebApplicationContextRunner( AnnotationConfigServletWebServerApplicationContext::new) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryCustomizerTests.java index 7cff13798441..66e0ae477108 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryCustomizerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,17 +22,19 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; +import org.springframework.boot.web.server.Cookie; +import org.springframework.boot.web.server.MimeMappings; import org.springframework.boot.web.server.Shutdown; import org.springframework.boot.web.server.Ssl; import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; import org.springframework.boot.web.servlet.server.Jsp; -import org.springframework.boot.web.servlet.server.Session.Cookie; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -45,6 +47,7 @@ * * @author Brian Clozel * @author Yunkun Huang + * @author Lasse Wulff */ class ServletWebServerFactoryCustomizerTests { @@ -72,6 +75,28 @@ void testCustomizeDisplayName() { then(factory).should().setDisplayName("TestName"); } + @Test + void withNoCustomMimeMappingsThenEmptyMimeMappingsIsAdded() { + ConfigurableServletWebServerFactory factory = mock(ConfigurableServletWebServerFactory.class); + this.customizer.customize(factory); + ArgumentCaptor mimeMappingsCaptor = ArgumentCaptor.forClass(MimeMappings.class); + then(factory).should().addMimeMappings(mimeMappingsCaptor.capture()); + MimeMappings mimeMappings = mimeMappingsCaptor.getValue(); + assertThat(mimeMappings.getAll()).isEmpty(); + } + + @Test + void withCustomMimeMappingsThenPopulatedMimeMappingsIsAdded() { + this.properties.getMimeMappings().add("a", "alpha"); + this.properties.getMimeMappings().add("b", "bravo"); + ConfigurableServletWebServerFactory factory = mock(ConfigurableServletWebServerFactory.class); + this.customizer.customize(factory); + ArgumentCaptor mimeMappingsCaptor = ArgumentCaptor.forClass(MimeMappings.class); + then(factory).should().addMimeMappings(mimeMappingsCaptor.capture()); + MimeMappings mimeMappings = mimeMappingsCaptor.getValue(); + assertThat(mimeMappings.getAll()).hasSize(2); + } + @Test void testCustomizeDefaultServlet() { ConfigurableServletWebServerFactory factory = mock(ConfigurableServletWebServerFactory.class); @@ -97,7 +122,6 @@ void testCustomizeJsp() { } @Test - @SuppressWarnings("removal") void customizeSessionProperties() { Map map = new HashMap<>(); map.put("server.servlet.session.timeout", "123"); @@ -105,7 +129,6 @@ void customizeSessionProperties() { map.put("server.servlet.session.cookie.name", "testname"); map.put("server.servlet.session.cookie.domain", "testdomain"); map.put("server.servlet.session.cookie.path", "/testpath"); - map.put("server.servlet.session.cookie.comment", "testcomment"); map.put("server.servlet.session.cookie.http-only", "true"); map.put("server.servlet.session.cookie.secure", "true"); map.put("server.servlet.session.cookie.max-age", "60"); @@ -118,7 +141,6 @@ void customizeSessionProperties() { assertThat(cookie.getName()).isEqualTo("testname"); assertThat(cookie.getDomain()).isEqualTo("testdomain"); assertThat(cookie.getPath()).isEqualTo("/testpath"); - assertThat(cookie.getComment()).isEqualTo("testcomment"); assertThat(cookie.getHttpOnly()).isTrue(); assertThat(cookie.getMaxAge()).hasSeconds(60); })); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerServletContextListenerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerServletContextListenerTests.java index d2ef77630aa8..ec52ceab9cbb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerServletContextListenerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerServletContextListenerTests.java @@ -26,7 +26,6 @@ import org.springframework.boot.testsupport.classpath.ForkedClassPath; import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories; -import org.springframework.boot.testsupport.web.servlet.Servlet5ClassPathOverrides; import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; @@ -90,7 +89,6 @@ ServletWebServerFactory webServerFactory() { } - @Servlet5ClassPathOverrides @Configuration(proxyBeanMethods = false) static class JettyConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java index 4b8737d0424c..6fb28fe2c90a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java @@ -39,6 +39,7 @@ import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.springframework.aop.support.AopUtils; @@ -51,6 +52,8 @@ import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.autoconfigure.validation.ValidatorAdapter; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfigurationTests.OrderedControllerAdviceBeansConfiguration.HighestOrderedControllerAdvice; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfigurationTests.OrderedControllerAdviceBeansConfiguration.LowestOrderedControllerAdvice; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; @@ -65,6 +68,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.core.convert.ConversionService; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; @@ -89,6 +94,7 @@ import org.springframework.web.filter.FormContentFilter; import org.springframework.web.filter.HiddenHttpMethodFilter; import org.springframework.web.filter.RequestContextFilter; +import org.springframework.web.method.ControllerAdviceBean; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.FlashMap; import org.springframework.web.servlet.FlashMapManager; @@ -96,6 +102,7 @@ import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.RequestToViewNameTranslator; import org.springframework.web.servlet.View; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; @@ -130,6 +137,7 @@ import org.springframework.web.servlet.support.SessionFlashMapManager; import org.springframework.web.servlet.view.AbstractView; import org.springframework.web.servlet.view.ContentNegotiatingViewResolver; +import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator; import org.springframework.web.util.UrlPathHelper; import static org.assertj.core.api.Assertions.assertThat; @@ -229,7 +237,7 @@ void resourceHandlerMappingOverrideAll() { @Test void resourceHandlerMappingDisabled() { this.contextRunner.withPropertyValues("spring.web.resources.add-mappings:false") - .run((context) -> assertThat(getResourceMappingLocations(context)).hasSize(0)); + .run((context) -> assertThat(getResourceMappingLocations(context)).isEmpty()); } @Test @@ -381,43 +389,40 @@ void customLocaleResolverWithDifferentNameDoesNotReplaceAutoConfiguredLocaleReso } @Test - @Deprecated(since = "3.0.0", forRemoval = true) - @SuppressWarnings("deprecation") - void customThemeResolverWithMatchingNameReplacesDefaultThemeResolver() { - this.contextRunner.withBean("themeResolver", CustomThemeResolver.class, CustomThemeResolver::new) + void customFlashMapManagerWithMatchingNameReplacesDefaultFlashMapManager() { + this.contextRunner.withBean("flashMapManager", CustomFlashMapManager.class, CustomFlashMapManager::new) .run((context) -> { - assertThat(context).hasSingleBean(org.springframework.web.servlet.ThemeResolver.class); - assertThat(context.getBean("themeResolver")).isInstanceOf(CustomThemeResolver.class); + assertThat(context).hasSingleBean(FlashMapManager.class); + assertThat(context.getBean("flashMapManager")).isInstanceOf(CustomFlashMapManager.class); }); } @Test - @Deprecated(since = "3.0.0", forRemoval = true) - @SuppressWarnings("deprecation") - void customThemeResolverWithDifferentNameDoesNotReplaceDefaultThemeResolver() { - this.contextRunner.withBean("customThemeResolver", CustomThemeResolver.class, CustomThemeResolver::new) + void customFlashMapManagerWithDifferentNameDoesNotReplaceDefaultFlashMapManager() { + this.contextRunner.withBean("customFlashMapManager", CustomFlashMapManager.class, CustomFlashMapManager::new) .run((context) -> { - assertThat(context.getBean("customThemeResolver")).isInstanceOf(CustomThemeResolver.class); - assertThat(context.getBean("themeResolver")) - .isInstanceOf(org.springframework.web.servlet.theme.FixedThemeResolver.class); + assertThat(context.getBean("customFlashMapManager")).isInstanceOf(CustomFlashMapManager.class); + assertThat(context.getBean("flashMapManager")).isInstanceOf(SessionFlashMapManager.class); }); } @Test - void customFlashMapManagerWithMatchingNameReplacesDefaultFlashMapManager() { - this.contextRunner.withBean("flashMapManager", CustomFlashMapManager.class, CustomFlashMapManager::new) + void customViewNameTranslatorWithMatchingNameReplacesDefaultViewNameTranslator() { + this.contextRunner.withBean("viewNameTranslator", CustomViewNameTranslator.class, CustomViewNameTranslator::new) .run((context) -> { - assertThat(context).hasSingleBean(FlashMapManager.class); - assertThat(context.getBean("flashMapManager")).isInstanceOf(CustomFlashMapManager.class); + assertThat(context).hasSingleBean(RequestToViewNameTranslator.class); + assertThat(context.getBean("viewNameTranslator")).isInstanceOf(CustomViewNameTranslator.class); }); } @Test - void customFlashMapManagerWithDifferentNameDoesNotReplaceDefaultFlashMapManager() { - this.contextRunner.withBean("customFlashMapManager", CustomFlashMapManager.class, CustomFlashMapManager::new) + void customViewNameTranslatorWithDifferentNameDoesNotReplaceDefaultViewNameTranslator() { + this.contextRunner + .withBean("customViewNameTranslator", CustomViewNameTranslator.class, CustomViewNameTranslator::new) .run((context) -> { - assertThat(context.getBean("customFlashMapManager")).isInstanceOf(CustomFlashMapManager.class); - assertThat(context.getBean("flashMapManager")).isInstanceOf(SessionFlashMapManager.class); + assertThat(context.getBean("customViewNameTranslator")).isInstanceOf(CustomViewNameTranslator.class); + assertThat(context.getBean("viewNameTranslator")) + .isInstanceOf(DefaultRequestToViewNameTranslator.class); }); } @@ -493,21 +498,6 @@ void overrideMessageCodesFormat() { .isNotNull()); } - @Test - void ignoreDefaultModelOnRedirectIsTrue() { - this.contextRunner.run((context) -> assertThat(context.getBean(RequestMappingHandlerAdapter.class)) - .extracting("ignoreDefaultModelOnRedirect") - .isEqualTo(true)); - } - - @Test - void overrideIgnoreDefaultModelOnRedirect() { - this.contextRunner.withPropertyValues("spring.mvc.ignore-default-model-on-redirect:false") - .run((context) -> assertThat(context.getBean(RequestMappingHandlerAdapter.class)) - .extracting("ignoreDefaultModelOnRedirect") - .isEqualTo(false)); - } - @Test void customViewResolver() { this.contextRunner.withUserConfiguration(CustomViewResolver.class) @@ -1010,6 +1000,17 @@ void problemDetailsBacksOffWhenExceptionHandler() { .hasSingleBean(CustomExceptionHandler.class)); } + @Test + void problemDetailsExceptionHandlerIsOrderedAt0() { + this.contextRunner.withPropertyValues("spring.mvc.problemdetails.enabled:true") + .withUserConfiguration(OrderedControllerAdviceBeansConfiguration.class) + .run((context) -> assertThat( + ControllerAdviceBean.findAnnotatedBeans(context).stream().map(ControllerAdviceBean::getBeanType)) + .asInstanceOf(InstanceOfAssertFactories.list(Class.class)) + .containsExactly(HighestOrderedControllerAdvice.class, ProblemDetailsExceptionHandler.class, + OrderedControllerAdviceBeansConfiguration.LowestOrderedControllerAdvice.class)); + } + private void assertResourceHttpRequestHandler(AssertableWebApplicationContext context, Consumer handlerConsumer) { Map handlerMap = getHandlerMap(context.getBean("resourceHandlerMapping", HandlerMapping.class)); @@ -1464,33 +1465,28 @@ public void setLocale(HttpServletRequest request, HttpServletResponse response, } - @Deprecated(since = "3.0.0", forRemoval = true) - static class CustomThemeResolver implements org.springframework.web.servlet.ThemeResolver { + static class CustomFlashMapManager extends AbstractFlashMapManager { @Override - public String resolveThemeName(HttpServletRequest request) { - return "custom"; + protected List retrieveFlashMaps(HttpServletRequest request) { + return null; } @Override - public void setThemeName(HttpServletRequest request, HttpServletResponse response, String themeName) { + protected void updateFlashMaps(List flashMaps, HttpServletRequest request, + HttpServletResponse response) { + } } - static class CustomFlashMapManager extends AbstractFlashMapManager { + static class CustomViewNameTranslator implements RequestToViewNameTranslator { @Override - protected List retrieveFlashMaps(HttpServletRequest request) { + public String getViewName(HttpServletRequest requestAttributes) { return null; } - @Override - protected void updateFlashMaps(List flashMaps, HttpServletRequest request, - HttpServletResponse response) { - - } - } @Configuration(proxyBeanMethods = false) @@ -1548,6 +1544,24 @@ CustomExceptionHandler customExceptionHandler() { } + @Configuration(proxyBeanMethods = false) + @Import({ LowestOrderedControllerAdvice.class, HighestOrderedControllerAdvice.class }) + static class OrderedControllerAdviceBeansConfiguration { + + @ControllerAdvice + @Order + static class LowestOrderedControllerAdvice { + + } + + @ControllerAdvice + @Order(Ordered.HIGHEST_PRECEDENCE) + static class HighestOrderedControllerAdvice { + + } + + } + @ControllerAdvice static class CustomExceptionHandler extends ResponseEntityExceptionHandler { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMappingTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMappingTests.java index 96462cf037b8..52397e4e4e5f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMappingTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.assertj.core.api.ThrowingConsumer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -30,6 +31,8 @@ import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider; import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; +import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; +import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; @@ -39,18 +42,14 @@ import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.view.AbstractView; import org.springframework.web.servlet.view.InternalResourceView; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link WelcomePageHandlerMapping}. @@ -76,50 +75,35 @@ void isOrderedAtLowPriority() { @Test void handlesRequestForStaticPageThatAcceptsTextHtml() { this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) - .run((context) -> MockMvcBuilders.webAppContextSetup(context) - .build() - .perform(get("/").accept(MediaType.TEXT_HTML)) - .andExpect(status().isOk()) - .andExpect(forwardedUrl("index.html"))); + .run(testWith((mvc) -> assertThat(mvc.get().uri("/").accept(MediaType.TEXT_HTML)).hasStatusOk() + .hasForwardedUrl("index.html"))); } @Test void handlesRequestForStaticPageThatAcceptsAll() { this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) - .run((context) -> MockMvcBuilders.webAppContextSetup(context) - .build() - .perform(get("/").accept(MediaType.ALL)) - .andExpect(status().isOk()) - .andExpect(forwardedUrl("index.html"))); + .run(testWith((mvc) -> assertThat(mvc.get().uri("/").accept(MediaType.ALL)).hasStatusOk() + .hasForwardedUrl("index.html"))); } @Test void doesNotHandleRequestThatDoesNotAcceptTextHtml() { this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) - .run((context) -> MockMvcBuilders.webAppContextSetup(context) - .build() - .perform(get("/").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound())); + .run(testWith((mvc) -> assertThat(mvc.get().uri("/").accept(MediaType.APPLICATION_JSON)) + .hasStatus(HttpStatus.NOT_FOUND))); } @Test void handlesRequestWithNoAcceptHeader() { this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) - .run((context) -> MockMvcBuilders.webAppContextSetup(context) - .build() - .perform(get("/")) - .andExpect(status().isOk()) - .andExpect(forwardedUrl("index.html"))); + .run(testWith((mvc) -> assertThat(mvc.get().uri("/")).hasStatusOk().hasForwardedUrl("index.html"))); } @Test void handlesRequestWithEmptyAcceptHeader() { this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) - .run((context) -> MockMvcBuilders.webAppContextSetup(context) - .build() - .perform(get("/").header(HttpHeaders.ACCEPT, "")) - .andExpect(status().isOk()) - .andExpect(forwardedUrl("index.html"))); + .run(testWith((mvc) -> assertThat(mvc.get().uri("/").header(HttpHeaders.ACCEPT, "")).hasStatusOk() + .hasForwardedUrl("index.html"))); } @Test @@ -131,54 +115,43 @@ void rootHandlerIsNotRegisteredWhenStaticPathPatternIsNotSlashStarStar() { @Test void producesNotFoundResponseWhenThereIsNoWelcomePage() { - this.contextRunner.run((context) -> MockMvcBuilders.webAppContextSetup(context) - .build() - .perform(get("/").accept(MediaType.TEXT_HTML)) - .andExpect(status().isNotFound())); + this.contextRunner.run(testWith( + (mvc) -> assertThat(mvc.get().uri("/").accept(MediaType.TEXT_HTML)).hasStatus(HttpStatus.NOT_FOUND))); } @Test void handlesRequestForTemplateThatAcceptsTextHtml() { - this.contextRunner.withUserConfiguration(TemplateConfiguration.class).run((context) -> { - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); - mockMvc.perform(get("/").accept(MediaType.TEXT_HTML)) - .andExpect(status().isOk()) - .andExpect(content().string("index template")); - }); + this.contextRunner.withUserConfiguration(TemplateConfiguration.class) + .run(testWith((mvc) -> assertThat(mvc.get().uri("/").accept(MediaType.TEXT_HTML)).hasStatusOk() + .hasBodyTextEqualTo("index template"))); } @Test void handlesRequestForTemplateThatAcceptsAll() { - this.contextRunner.withUserConfiguration(TemplateConfiguration.class).run((context) -> { - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); - mockMvc.perform(get("/").accept(MediaType.ALL)) - .andExpect(status().isOk()) - .andExpect(content().string("index template")); - }); + this.contextRunner.withUserConfiguration(TemplateConfiguration.class) + .run(testWith((mvc) -> assertThat(mvc.get().uri("/").accept(MediaType.ALL)).hasStatusOk() + .hasBodyTextEqualTo("index template"))); } @Test void prefersAStaticResourceToATemplate() { this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class, TemplateConfiguration.class) - .run((context) -> { - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); - mockMvc.perform(get("/").accept(MediaType.ALL)) - .andExpect(status().isOk()) - .andExpect(forwardedUrl("index.html")); - }); + .run(testWith((mvc) -> assertThat(mvc.get().uri("/").accept(MediaType.ALL)).hasStatusOk() + .hasForwardedUrl("index.html"))); } @Test void logsInvalidAcceptHeader(CapturedOutput output) { - this.contextRunner.withUserConfiguration(TemplateConfiguration.class).run((context) -> { - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); - mockMvc.perform(get("/").accept("*/*q=0.8")) - .andExpect(status().isOk()) - .andExpect(content().string("index template")); - }); + this.contextRunner.withUserConfiguration(TemplateConfiguration.class) + .run(testWith((mvc) -> assertThat(mvc.get().uri("/").accept("*/*q=0.8")).hasStatusOk() + .hasBodyTextEqualTo("index template"))); assertThat(output).contains("Received invalid Accept header. Assuming all media types are accepted"); } + private ContextConsumer testWith(ThrowingConsumer mvc) { + return (context) -> mvc.accept(MockMvcTester.from(context)); + } + @Configuration(proxyBeanMethods = false) static class HandlerMappingConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageNotAcceptableHandlerMappingTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageNotAcceptableHandlerMappingTests.java index 3011f29b4a90..ec3feab084cb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageNotAcceptableHandlerMappingTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageNotAcceptableHandlerMappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.web.servlet; +import org.assertj.core.api.ThrowingConsumer; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.ObjectProvider; @@ -23,6 +24,8 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; +import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; +import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -31,14 +34,13 @@ import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link WelcomePageNotAcceptableHandlerMapping}. @@ -66,37 +68,28 @@ void isOrderedAtLowPriorityButAboveResourceHandlerRegistry() { @Test void handlesRequestForStaticPageThatAcceptsTextHtml() { this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) - .run((context) -> MockMvcBuilders.webAppContextSetup(context) - .build() - .perform(get("/").accept(MediaType.TEXT_HTML)) - .andExpect(status().isNotAcceptable())); + .run(testWith((mvc) -> assertThat(mvc.get().uri("/").accept(MediaType.TEXT_HTML)) + .hasStatus(HttpStatus.NOT_ACCEPTABLE))); } @Test - void handlesRequestForStaticPagetThatDoesNotAcceptTextHtml() { + void handlesRequestForStaticPageThatDoesNotAcceptTextHtml() { this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) - .run((context) -> MockMvcBuilders.webAppContextSetup(context) - .build() - .perform(get("/").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotAcceptable())); + .run(testWith((mvc) -> assertThat(mvc.get().uri("/").accept(MediaType.APPLICATION_JSON)) + .hasStatus(HttpStatus.NOT_ACCEPTABLE))); } @Test void handlesRequestWithNoAcceptHeader() { this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) - .run((context) -> MockMvcBuilders.webAppContextSetup(context) - .build() - .perform(get("/")) - .andExpect(status().isNotAcceptable())); + .run(testWith((mvc) -> assertThat(mvc.get().uri("/")).hasStatus(HttpStatus.NOT_ACCEPTABLE))); } @Test void handlesRequestWithEmptyAcceptHeader() { this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) - .run((context) -> MockMvcBuilders.webAppContextSetup(context) - .build() - .perform(get("/").header(HttpHeaders.ACCEPT, "")) - .andExpect(status().isNotAcceptable())); + .run(testWith((mvc) -> assertThat(mvc.get().uri("/").header(HttpHeaders.ACCEPT, "")) + .hasStatus(HttpStatus.NOT_ACCEPTABLE))); } @Test @@ -109,10 +102,12 @@ void rootHandlerIsNotRegisteredWhenStaticPathPatternIsNotSlashStarStar() { @Test void producesNotFoundResponseWhenThereIsNoWelcomePage() { - this.contextRunner.run((context) -> MockMvcBuilders.webAppContextSetup(context) - .build() - .perform(get("/").accept(MediaType.TEXT_HTML)) - .andExpect(status().isNotFound())); + this.contextRunner.run(testWith( + (mvc) -> assertThat(mvc.get().uri("/").accept(MediaType.TEXT_HTML)).hasStatus(HttpStatus.NOT_FOUND))); + } + + private ContextConsumer testWith(ThrowingConsumer mvc) { + return (context) -> mvc.accept(MockMvcTester.from(context)); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerDirectMockMvcTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerDirectMockMvcTests.java index 4f4d67968f90..b09718bbea82 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerDirectMockMvcTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerDirectMockMvcTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,19 +42,14 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** - * Tests for {@link BasicErrorController} using {@link MockMvc} but not + * Tests for {@link BasicErrorController} using {@link MockMvcTester} but not * {@link org.springframework.test.context.junit.jupiter.SpringExtension}. * * @author Dave Syer @@ -64,7 +59,7 @@ class BasicErrorControllerDirectMockMvcTests { private ConfigurableWebApplicationContext wac; - private MockMvc mockMvc; + private MockMvcTester mvc; @AfterEach void close() { @@ -73,49 +68,44 @@ void close() { void setup(ConfigurableWebApplicationContext context) { this.wac = context; - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + this.mvc = MockMvcTester.from(this.wac); } @Test - void errorPageAvailableWithParentContext() throws Exception { + void errorPageAvailableWithParentContext() { setup((ConfigurableWebApplicationContext) new SpringApplicationBuilder(ParentConfiguration.class) .child(ChildConfiguration.class) .run("--server.port=0")); - MvcResult response = this.mockMvc.perform(get("/error").accept(MediaType.TEXT_HTML)) - .andExpect(status().is5xxServerError()) - .andReturn(); - String content = response.getResponse().getContentAsString(); - assertThat(content).contains("status=999"); + assertThat(this.mvc.get().uri("/error").accept(MediaType.TEXT_HTML)).hasStatus5xxServerError() + .bodyText() + .contains("status=999"); } @Test - void errorPageAvailableWithMvcIncluded() throws Exception { + void errorPageAvailableWithMvcIncluded() { setup((ConfigurableWebApplicationContext) new SpringApplication(WebMvcIncludedConfiguration.class) .run("--server.port=0")); - MvcResult response = this.mockMvc.perform(get("/error").accept(MediaType.TEXT_HTML)) - .andExpect(status().is5xxServerError()) - .andReturn(); - String content = response.getResponse().getContentAsString(); - assertThat(content).contains("status=999"); + assertThat(this.mvc.get().uri("/error").accept(MediaType.TEXT_HTML)).hasStatus5xxServerError() + .bodyText() + .contains("status=999"); } @Test void errorPageNotAvailableWithWhitelabelDisabled() { setup((ConfigurableWebApplicationContext) new SpringApplication(WebMvcIncludedConfiguration.class) .run("--server.port=0", "--server.error.whitelabel.enabled=false")); - assertThatExceptionOfType(ServletException.class) - .isThrownBy(() -> this.mockMvc.perform(get("/error").accept(MediaType.TEXT_HTML))); + assertThat(this.mvc.get().uri("/error").accept(MediaType.TEXT_HTML)).hasFailed() + .failure() + .isInstanceOf(ServletException.class); } @Test - void errorControllerWithAop() throws Exception { + void errorControllerWithAop() { setup((ConfigurableWebApplicationContext) new SpringApplication(WithAopConfiguration.class) .run("--server.port=0")); - MvcResult response = this.mockMvc.perform(get("/error").accept(MediaType.TEXT_HTML)) - .andExpect(status().is5xxServerError()) - .andReturn(); - String content = response.getResponse().getContentAsString(); - assertThat(content).contains("status=999"); + assertThat(this.mvc.get().uri("/error").accept(MediaType.TEXT_HTML)).hasStatus5xxServerError() + .bodyText() + .contains("status=999"); } @Target(ElementType.TYPE) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerIntegrationTests.java index 691afe085d6f..909e5908703d 100755 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerIntegrationTests.java @@ -35,15 +35,20 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.error.ErrorAttributeOptions; +import org.springframework.boot.web.error.ErrorAttributeOptions.Include; +import org.springframework.boot.web.servlet.error.ErrorAttributes; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -343,6 +348,18 @@ void testIncompatibleMediaType() { assertThat(entity.getBody()).isNull(); } + @Test + @SuppressWarnings({ "rawtypes", "unchecked" }) + void customErrorControllerWithoutStatusConfiguration() { + load(CustomErrorControllerWithoutStatusConfiguration.class); + RequestEntity request = RequestEntity.post(URI.create(createUrl("/bodyValidation"))) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .body("{}"); + ResponseEntity entity = new TestRestTemplate().exchange(request, Map.class); + assertThat(entity.getBody()).doesNotContainKey("status"); + } + private void assertErrorAttributes(Map content, String status, String error, Class exception, String message, String path) { assertThat(content.get("status")).as("Wrong status").hasToString(status); @@ -363,12 +380,16 @@ private String createUrl(String path) { } private void load(String... arguments) { + load(TestConfiguration.class, arguments); + } + + private void load(Class configuration, String... arguments) { List args = new ArrayList<>(); args.add("--server.port=0"); if (arguments != null) { args.addAll(Arrays.asList(arguments)); } - this.context = SpringApplication.run(TestConfiguration.class, StringUtils.toStringArray(args)); + this.context = SpringApplication.run(configuration, StringUtils.toStringArray(args)); } @Target(ElementType.TYPE) @@ -394,11 +415,13 @@ static void main(String[] args) { @Bean View error() { return new AbstractView() { + @Override protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { response.getWriter().write("ERROR_BEAN"); } + }; } @@ -498,4 +521,23 @@ void setContent(String content) { } + static class CustomErrorControllerWithoutStatusConfiguration extends TestConfiguration { + + @Bean + BasicErrorController basicErrorController(ServerProperties serverProperties, ErrorAttributes errorAttributes, + ObjectProvider errorViewResolvers) { + return new BasicErrorController(errorAttributes, serverProperties.getError(), + errorViewResolvers.orderedStream().toList()) { + + @Override + protected ErrorAttributeOptions getErrorAttributeOptions(HttpServletRequest request, + MediaType mediaType) { + return super.getErrorAttributeOptions(request, mediaType).excluding(Include.STATUS); + } + + }; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerMockMvcTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerMockMvcTests.java index 31eb0cb5fcfd..beda7a4a5352 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerMockMvcTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerMockMvcTests.java @@ -47,10 +47,10 @@ import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.RequestBuilder; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.web.servlet.assertj.MockMvcTester; +import org.springframework.test.web.servlet.assertj.MvcTestResult; import org.springframework.util.ReflectionUtils; import org.springframework.validation.BindException; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -63,11 +63,9 @@ import org.springframework.web.servlet.view.AbstractView; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** - * Tests for {@link BasicErrorController} using {@link MockMvc} and + * Tests for {@link BasicErrorController} using {@link MockMvcTester} and * {@link SpringBootTest @SpringBootTest}. * * @author Dave Syer @@ -80,60 +78,51 @@ class BasicErrorControllerMockMvcTests { @Autowired private WebApplicationContext wac; - private MockMvc mockMvc; + private MockMvcTester mvc; @BeforeEach void setup() { - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + this.mvc = MockMvcTester.from(this.wac); } @Test - void testDirectAccessForMachineClient() throws Exception { - MvcResult response = this.mockMvc.perform(get("/error")).andExpect(status().is5xxServerError()).andReturn(); - String content = response.getResponse().getContentAsString(); - assertThat(content).contains("999"); + void testDirectAccessForMachineClient() { + assertThat(this.mvc.get().uri("/error")).hasStatus5xxServerError().bodyText().contains("999"); } @Test - void testErrorWithNotFoundResponseStatus() throws Exception { - MvcResult result = this.mockMvc.perform(get("/bang")).andExpect(status().isNotFound()).andReturn(); - MvcResult response = this.mockMvc.perform(new ErrorDispatcher(result, "/error")).andReturn(); - String content = response.getResponse().getContentAsString(); - assertThat(content).contains("Expected!"); + void testErrorWithNotFoundResponseStatus() { + assertThat(this.mvc.get().uri("/bang")).hasStatus(HttpStatus.NOT_FOUND) + .satisfies((result) -> assertThat(this.mvc.perform(new ErrorDispatcher(result, "/error"))).bodyText() + .contains("Expected!")); + } @Test - void testErrorWithNoContentResponseStatus() throws Exception { - MvcResult result = this.mockMvc.perform(get("/noContent").accept("some/thing")) - .andExpect(status().isNoContent()) - .andReturn(); - MvcResult response = this.mockMvc.perform(new ErrorDispatcher(result, "/error")) - .andExpect(status().isNoContent()) - .andReturn(); - String content = response.getResponse().getContentAsString(); - assertThat(content).isEmpty(); + void testErrorWithNoContentResponseStatus() { + assertThat(this.mvc.get().uri("/noContent").accept("some/thing")).hasStatus(HttpStatus.NO_CONTENT) + .satisfies((result) -> assertThat(this.mvc.perform(new ErrorDispatcher(result, "/error"))) + .hasStatus(HttpStatus.NO_CONTENT) + .body() + .isEmpty()); } @Test - void testBindingExceptionForMachineClient() throws Exception { + void testBindingExceptionForMachineClient() { // In a real server the response is carried over into the error dispatcher, but - // in the mock a new one is created so we have to assert the status at this - // intermediate point - MvcResult result = this.mockMvc.perform(get("/bind")).andExpect(status().is4xxClientError()).andReturn(); - MvcResult response = this.mockMvc.perform(new ErrorDispatcher(result, "/error")).andReturn(); - // And the rendered status code is always wrong (but would be 400 in a real - // system) - String content = response.getResponse().getContentAsString(); - assertThat(content).contains("Validation failed"); + // in the mock a new one is created, so we have to assert the status at this + // intermediate point, and the rendered status code is always wrong (but would + // be 400 in a real system) + assertThat(this.mvc.get().uri("/bind")).hasStatus4xxClientError() + .satisfies((result) -> assertThat(this.mvc.perform(new ErrorDispatcher(result, "/error"))).bodyText() + .contains("Validation failed")); } @Test - void testDirectAccessForBrowserClient() throws Exception { - MvcResult response = this.mockMvc.perform(get("/error").accept(MediaType.TEXT_HTML)) - .andExpect(status().is5xxServerError()) - .andReturn(); - String content = response.getResponse().getContentAsString(); - assertThat(content).contains("ERROR_BEAN"); + void testDirectAccessForBrowserClient() { + assertThat(this.mvc.get().uri("/error").accept(MediaType.TEXT_HTML)).hasStatus5xxServerError() + .bodyText() + .contains("ERROR_BEAN"); } @Target(ElementType.TYPE) @@ -225,8 +214,8 @@ private class ErrorDispatcher implements RequestBuilder { private final String path; - ErrorDispatcher(MvcResult result, String path) { - this.result = result; + ErrorDispatcher(MvcTestResult mvcTestResult, String path) { + this.result = mvcTestResult.getMvcResult(); this.path = path; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/DefaultErrorViewIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/DefaultErrorViewIntegrationTests.java index 3732a81fe9b3..10212b89b31d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/DefaultErrorViewIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/DefaultErrorViewIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,14 +37,10 @@ import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import org.springframework.web.context.WebApplicationContext; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Integration tests for the default error view. @@ -59,49 +55,38 @@ class DefaultErrorViewIntegrationTests { @Autowired private WebApplicationContext wac; - private MockMvc mockMvc; + private MockMvcTester mvc; @BeforeEach void setup() { - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + this.mvc = MockMvcTester.from(this.wac); } @Test - void testErrorForBrowserClient() throws Exception { - MvcResult response = this.mockMvc.perform(get("/error").accept(MediaType.TEXT_HTML)) - .andExpect(status().is5xxServerError()) - .andReturn(); - String content = response.getResponse().getContentAsString(); - assertThat(content).contains(""); - assertThat(content).contains("999"); + void testErrorForBrowserClient() { + assertThat(this.mvc.get().uri("/error").accept(MediaType.TEXT_HTML)).hasStatus5xxServerError() + .bodyText() + .contains("", "999"); } @Test - void testErrorWithHtmlEscape() throws Exception { - MvcResult response = this.mockMvc - .perform( - get("/error") - .requestAttr("jakarta.servlet.error.exception", - new RuntimeException("")) - .accept(MediaType.TEXT_HTML)) - .andExpect(status().is5xxServerError()) - .andReturn(); - String content = response.getResponse().getContentAsString(); - assertThat(content).contains("<script>"); - assertThat(content).contains("Hello World"); - assertThat(content).contains("999"); + void testErrorWithHtmlEscape() { + assertThat(this.mvc.get() + .uri("/error") + .requestAttr("jakarta.servlet.error.exception", + new RuntimeException("")) + .accept(MediaType.TEXT_HTML)).hasStatus5xxServerError() + .bodyText() + .contains("<script>", "Hello World", "999"); } @Test - void testErrorWithSpelEscape() throws Exception { + void testErrorWithSpelEscape() { String spel = "${T(" + getClass().getName() + ").injectCall()}"; - MvcResult response = this.mockMvc - .perform(get("/error").requestAttr("jakarta.servlet.error.exception", new RuntimeException(spel)) - .accept(MediaType.TEXT_HTML)) - .andExpect(status().is5xxServerError()) - .andReturn(); - String content = response.getResponse().getContentAsString(); - assertThat(content).doesNotContain("injection"); + assertThat(this.mvc.get() + .uri("/error") + .requestAttr("jakarta.servlet.error.exception", new RuntimeException(spel)) + .accept(MediaType.TEXT_HTML)).hasStatus5xxServerError().bodyText().doesNotContain("injection"); } static String injectCall() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java index e4d211f54634..ced04e789bfc 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -112,8 +112,7 @@ private DispatcherServletWebRequest createWebRequest(Exception ex, boolean commi } private ErrorAttributeOptions withAllOptions() { - return ErrorAttributeOptions.of(Include.EXCEPTION, Include.STACK_TRACE, Include.MESSAGE, - Include.BINDING_ERRORS); + return ErrorAttributeOptions.of(Include.values()); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/websocket/reactive/WebSocketReactiveAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/websocket/reactive/WebSocketReactiveAutoConfigurationTests.java index e9505d09632c..e092a9262f2e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/websocket/reactive/WebSocketReactiveAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/websocket/reactive/WebSocketReactiveAutoConfigurationTests.java @@ -24,14 +24,13 @@ import org.apache.catalina.Container; import org.apache.catalina.Context; import org.apache.catalina.startup.Tomcat; -import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.springframework.boot.testsupport.classpath.ForkedClassPath; import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories; -import org.springframework.boot.testsupport.web.servlet.Servlet5ClassPathOverrides; import org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory; import org.springframework.boot.web.embedded.jetty.JettyWebServer; import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory; @@ -123,7 +122,6 @@ ReactiveWebServerFactory webServerFactory() { } - @Servlet5ClassPathOverrides @Configuration(proxyBeanMethods = false) static class JettyConfiguration extends CommonConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketMessagingAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketMessagingAutoConfigurationTests.java index f6700f991545..59c8046c66ac 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketMessagingAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketMessagingAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,10 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -35,6 +37,7 @@ import org.springframework.boot.LazyInitializationBeanFactoryPostProcessor; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -43,10 +46,14 @@ import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; import org.springframework.messaging.converter.CompositeMessageConverter; import org.springframework.messaging.converter.MessageConverter; import org.springframework.messaging.converter.SimpleMessageConverter; import org.springframework.messaging.simp.annotation.SubscribeMapping; +import org.springframework.messaging.simp.config.ChannelRegistration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.messaging.simp.stomp.StompCommand; import org.springframework.messaging.simp.stomp.StompFrameHandler; @@ -54,6 +61,7 @@ import org.springframework.messaging.simp.stomp.StompSession; import org.springframework.messaging.simp.stomp.StompSessionHandler; import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter; +import org.springframework.security.util.FieldUtils; import org.springframework.stereotype.Controller; import org.springframework.web.client.RestTemplate; import org.springframework.web.socket.client.standard.StandardWebSocketClient; @@ -75,6 +83,7 @@ * Tests for {@link WebSocketMessagingAutoConfiguration}. * * @author Andy Wilkinson + * @author Lasse Wulff */ class WebSocketMessagingAutoConfigurationTests { @@ -129,10 +138,34 @@ void customizedConverterTypesMatchDefaultConverterTypes() { } } + @Test + void predefinedThreadExecutorIsSelectedForInboundChannel() throws Throwable { + AsyncTaskExecutor expectedExecutor = new SimpleAsyncTaskExecutor(); + ChannelRegistration registration = new ChannelRegistration(); + WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration configuration = new WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration( + new ObjectMapper(), + Map.of(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME, expectedExecutor)); + configuration.configureClientInboundChannel(registration); + TaskExecutor executor = (TaskExecutor) FieldUtils.getFieldValue(registration, "executor"); + assertThat(executor).isEqualTo(expectedExecutor); + } + + @Test + void predefinedThreadExecutorIsSelectedForOutboundChannel() throws Throwable { + AsyncTaskExecutor expectedExecutor = new SimpleAsyncTaskExecutor(); + ChannelRegistration registration = new ChannelRegistration(); + WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration configuration = new WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration( + new ObjectMapper(), + Map.of(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME, expectedExecutor)); + configuration.configureClientOutboundChannel(registration); + TaskExecutor executor = (TaskExecutor) FieldUtils.getFieldValue(registration, "executor"); + assertThat(executor).isEqualTo(expectedExecutor); + } + private List getCustomizedConverters() { List customizedConverters = new ArrayList<>(); WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration configuration = new WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration( - new ObjectMapper()); + new ObjectMapper(), Collections.emptyMap()); configuration.configureMessageConverters(customizedConverters); return customizedConverters; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketServletAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketServletAutoConfigurationTests.java index c2112deeddd6..bd4d9213ba1d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketServletAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketServletAutoConfigurationTests.java @@ -30,7 +30,7 @@ import jakarta.websocket.DeploymentException; import jakarta.websocket.server.ServerContainer; import jakarta.websocket.server.ServerEndpoint; -import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter; +import org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -43,7 +43,6 @@ import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.testsupport.classpath.ForkedClassPath; import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories; -import org.springframework.boot.testsupport.web.servlet.Servlet5ClassPathOverrides; import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.server.WebServer; @@ -107,7 +106,6 @@ void webSocketUpgradeDoesNotPreventAFilterFromRejectingTheRequest(String server, } @Test - @Servlet5ClassPathOverrides void jettyWebSocketUpgradeFilterIsAddedToServletContextOfJettyServer() { try (AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext( JettyConfiguration.class, WebSocketServletAutoConfiguration.JettyWebSocketConfiguration.class)) { @@ -142,7 +140,6 @@ void jettyWebSocketUpgradeFilterIsNotExposedAsABean() { } @Test - @Servlet5ClassPathOverrides void jettyWebSocketUpgradeFilterServletContextInitializerBacksOffWhenBeanWithSameNameIsDefined() { try (AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext( JettyConfiguration.class, CustomWebSocketUpgradeFilterServletContextInitializerConfiguration.class, @@ -204,7 +201,6 @@ ServletWebServerFactory webServerFactory() { } - @Servlet5ClassPathOverrides @Configuration(proxyBeanMethods = false) static class JettyConfiguration extends CommonConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/graphql/schema.graphqls b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/graphql/schema.graphqls index ea9b003d91ed..dc80e899dd85 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/graphql/schema.graphqls +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/graphql/schema.graphqls @@ -2,4 +2,8 @@ type Query { greeting(name: String! = "Spring"): String! bookById(id: ID): Book books: BookConnection +} + +type Subscription { + booksOnSale(minPages: Int) : Book! } \ No newline at end of file diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/ssl/key1.crt b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/ssl/key1.crt new file mode 100644 index 000000000000..e381ab69b3d8 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/ssl/key1.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDJjCCAhECFHuJXZO0JDPtCSc1/r0llpyc/j9TMA0GCSqGSIb3DQEBCwUAME8x +CzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0Rl +ZmF1bHQgQ29tcGFueSBMdGQxCzAJBgNVBAMMAkNBMCAXDTIzMTAwNTA3Mjg0NVoY +DzIxMjMwOTExMDcyODQ1WjBRMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVs +dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMQ0wCwYDVQQDDARr +ZXkxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoYU2afupPq/b6PIy +6MWDOMRdJk5uW51lrw6oudXpWlUQMXKdsaZT4sqbgjGLggfo7WWsPeCzQN3kIX3T +OqBog5EMkXnlQhAfP2Htj0uXPFj97leZ+FqJrzgPnZY8wSqDXfy9/ycR3PgWjRsS +GZJb05hTNVGTU2vpNQDDo+XBKgybB0afGU8Nk/InWfs1xd/Jv0YcVADQiQEmg41w +g18B3LMIBZPWIJUQ1b7wMlhxWaCNXHfB1bUTIYCUAUOZyEaxPaOOiJo32xKmqOlU +TCLM8zgWCBCEgHtQwSD0GMLhUarLPNE5GP3yo5qHBYqOque7BBjP4e58r6wAyBoe +7kMYRQIDAQABMA0GCSqGSIb3DQEBCwUAA4H/AAMIYpTDxgQwpfk+U1IhkqJjb+Uh +hj6KlT5TEdpn/saGYLZQECZAO21MWrUDTsV2Pax2Ee8ezarCg8Cthu4YOtPauPaL +XpyrIagUOgrDcmXr6QxMKUqifiMurLRFaAS7mWXp0TAFNgzDg3WvF9zMJgkjUp/O +gNSG9U7kXuFfxpVtoalyC2C3g3UeieVXSek3a28h5c/0/DomHqLbyqZh5rYwAJ7C +q1bqA5TnZNVvV731SVueycj9+5PKHKG6eeRRh7roZ34l54O9adNEeDAF0Lqn4sbn +a/h4GPK/u6J6Y3nwrdajipZ2DmfiQwoimxprMGNQKuKA0lc025SGHNno +-----END CERTIFICATE----- diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/ssl/key1.pem b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/ssl/key1.pem new file mode 100644 index 000000000000..197eabb17264 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/ssl/key1.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQChhTZp+6k+r9vo +8jLoxYM4xF0mTm5bnWWvDqi51elaVRAxcp2xplPiypuCMYuCB+jtZaw94LNA3eQh +fdM6oGiDkQyReeVCEB8/Ye2PS5c8WP3uV5n4WomvOA+dljzBKoNd/L3/JxHc+BaN +GxIZklvTmFM1UZNTa+k1AMOj5cEqDJsHRp8ZTw2T8idZ+zXF38m/RhxUANCJASaD +jXCDXwHcswgFk9YglRDVvvAyWHFZoI1cd8HVtRMhgJQBQ5nIRrE9o46ImjfbEqao +6VRMIszzOBYIEISAe1DBIPQYwuFRqss80TkY/fKjmocFio6q57sEGM/h7nyvrADI +Gh7uQxhFAgMBAAECggEABUfEGCHkjgqUv2BPnsF6QTxWKT7uJ6uVG+x4Qp8GInBe +d6deFWUxH9xsygxRmb4ldMFaqKk0Yv3+C8Q/yA5fbFGtHgJkpsy9IMbUS9d2ScBF +COovO+nFz4cfJ5E2SkBYDBYLphBCar1ni1RjupdIzjmQGtGgZd1EwflU7AJCVtwG +S7ltIs2nSOqUFGTfjb9j0NiATZvWTDRtavNMhyrZplKK6M6VoH1ZcnmcvEfF7j5L +oSmXrNKYs4iKn1qKypykfCQoEFK0/EEjj5EdnPaSeI9EERrZK1QnHafB2qK38LSr +8cGaWH24mPW6c/26bDQnHkN3SqKLCODXZMBGhPlLDwKBgQDdMqOzRR3SpTx7KPqp +h+P0diBZb1e6c+Ob0lXD/rfJEtkAqyFLqpi8hN9xodxw++JYbhC69kJE7VWtQLIt +Lc+DG72KTS/cbpnvERL1+AoM0TRbO9Ds9aFP4+Zmm/VDxi9rR5yTgl9iAHJ46VrE +BhnG8JQPBm4n5JU5/wJ9qCQCywKBgQC67uWchaewzDHCiefhTVgwTm1BmHiV/OR4 +50Je2x3GPW6VJGFnBjVzlScKrNyFeOYwscvVS8pTmFP8c5laTbQMC3pVqiWs28Ip +6sy6cXfepVyc0njLFGbiek8ab0rjVYU27D0O9tucrxDx4pKOurilds1Gbm4HjfyE +R7pWn/AfLwKBgQC+5wJzKLaJYsQlAwP6pmYtSHm41ihfqb8Jb2lHwyD4r4SLWCZf +OHejVAXH+0rWU/1QFoXn5brh4/cqlIhyB3RtkdZucxlYZDgEJLc5g32g/Dj0eFZi ++8bhvS3O5tCxUm0AaIiQolcRrJMfGT6VqTI8CMuvf/w3/8ZujFCpBCE4KwKBgBiw +lQMnZA6l6ayYKlhHru4ybZvMV6D31fViFhIRPs2AL6rjMzo4R7cMbCusyTOX1E96 +LEHv0LlZ1T3yxr52pOEyYuYNowxBulNu/7tgYUS28pSD+BBakXw4S1pieLGuCfpH +GYlwcXEwbjyEgHb5konINzSmQUIeLswJ7UKjvUNhAoGAXmXvyHqdL04SD99G3B/5 ++azzzAVR1fvGYOvq+/hWZMG5PS0kx2V3txCVyY8E1/lCysp9BuUHtW+vOS8YGhAT +wkZ/X9igZteQvvdVw+E5CXS05b4EBI+7ZViL9ulXFZ4YC70lKcUE52bmaPM+onQJ +Y1s9JWTe2EAkxsuxm+hkjo0= +-----END PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/ssl/key2-chain.crt b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/ssl/key2-chain.crt new file mode 100644 index 000000000000..3b55b95a96ae --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/ssl/key2-chain.crt @@ -0,0 +1,38 @@ +-----BEGIN CERTIFICATE----- +MIIDJjCCAhECFFjLlXVdTxDdLlCifzrA0dTHHJ2mMA0GCSqGSIb3DQEBCwUAME8x +CzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0Rl +ZmF1bHQgQ29tcGFueSBMdGQxCzAJBgNVBAMMAkNBMCAXDTIzMTAwNTA3Mjg1MFoY +DzIxMjMwOTExMDcyODUwWjBRMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVs +dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMQ0wCwYDVQQDDARr +ZXkyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAspCMUdFGyKkgpMbW ++UwSg4fdKM4qLSH7voTdsdVM9aAvLvYjBQ4gpORxDZNfUz67R0Ua0/oJt9jD49Wp +qcq+tDOnp0dPtn2hFluV5PxM6d+MCSx/frPsfvyt9234okLL1zdLDNFYEbLhSPjA +ku3vHw/OwlJOxCRwTkPqcElIV4+IvIbzAgSffyokzm/wKVKEhoT6NcfeU+6wCkTu +al1X8loJ+27N6jN13oGZfH7EveBqgR8rPs55+54S/OcVG/uqL9ggOGRJiIZ3jUBk +m5cN27wKkaNg/CQwa1UjcU4qshVpknHw1dpgJ2Gbs/yUphwpEZl/FTsZFcK1KCHD +rOp3PQIDAQABMA0GCSqGSIb3DQEBCwUAA4H/AAFmEq86broBFxs0cpImaM884PBT +bvJBSsFhsOg6mi4Gt01G/lPSj/ExNtH3G5bytCYAPaRxNx/dCs7uON3p86ta4zL8 +2PxgyhX1oY/GG63ETwn5s3GKpRaGTNVDWvPIM9RX6+bvX/wOg8eYXVaQlG5XYadC +Ms9lWqHaM1C/iLGNmUTGcdbvhnmQDky2CwPNm+lXogSWbrsGpAmCkXJD1H+0Mx8I +wjDVtGLBwr/8oXI8WbhvISMnS9+dd7+GLm6mU+14Kswi5I7EmBmREvkswi2IVJ6M +GL7EY3qA6iqJWqsseYyLxiMr3nBT0SETphzoDanUQI1/jXQPrWIyjqvs +-----END CERTIFICATE----- +-----BEGIN TRUSTED CERTIFICATE----- +MIIDIDCCAgsCFH3lh1RXOEy2ESqUPyzb+9zxMYUnMA0GCSqGSIb3DQEBCwUAME8x +CzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0Rl +ZmF1bHQgQ29tcGFueSBMdGQxCzAJBgNVBAMMAkNBMCAXDTIzMTAwNTA3MjU1M1oY +DzIxMjMwOTExMDcyNTUzWjBPMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVs +dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMQswCQYDVQQDDAJD +QTCCAR4wDQYJKoZIhvcNAQEBBQADggELADCCAQYCgf4NNpc+6B3qvwKcRYgoXmJ4 +3wyWktBK7BdShz/YnW1OlFZ+R845ZiDw0KdzElZWkYqn+BYJus6lPIS5dfLcrGSf +a1e8IK02RpBiY/WJvupetnSk8gKA7emF94NlV4gXr4ICJAhXvXUFyBLpdEUE/lcg +lgCbVJzs5jWUnffEF9mrClzzo0+iXw34zwmyYyBTFmlOEr+QUEdAb6Lr/klpTVit +as2Ddg1QT4EaSIdTEpkVRZp2dyYVdqSxpaBq21xg0viDHsYQrP96IfacmUB7kFFn +HsnptDHFvJj2WSQDX+PRS7tLl4mmfizZg80eGfLD22ShNspRSGnbJc0OzegPiwID +AQABMA0GCSqGSIb3DQEBCwUAA4H/AAnC+FQqdeJaG5I7R+pNjgKplL2UsxW983kA +CVVkv/Dt0+4rbPC67o9/8Tr+g4eo/wUntMNo2ghF3oBItGr7pJE16zPiLwIvha9c +8BDhCEZWyhz3vkamZUi19lOnkm3zTmmDE/nX4WYH6CL4UWjxvniZYwW8AdVSnFXY +ncriuvfliLa3dw1SJ7FtxdcBn4yfzrZWcY+psYNHpftLGYRmQF/VCDSB9EAIEggr +yBcP749u2y8s44WvKAnnwfLcALIrylY25zN0pao/l2X8HI6qHUeA/QbbEBpDoQvR +du/rgaHCVvFFxATefhBJ0CUA1Nn5nrGwyRTKnZWtR080qwUp +-----END TRUSTED CERTIFICATE----- diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/ssl/key2.crt b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/ssl/key2.crt new file mode 100644 index 000000000000..127882627896 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/ssl/key2.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDJjCCAhECFFjLlXVdTxDdLlCifzrA0dTHHJ2mMA0GCSqGSIb3DQEBCwUAME8x +CzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0Rl +ZmF1bHQgQ29tcGFueSBMdGQxCzAJBgNVBAMMAkNBMCAXDTIzMTAwNTA3Mjg1MFoY +DzIxMjMwOTExMDcyODUwWjBRMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVs +dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMQ0wCwYDVQQDDARr +ZXkyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAspCMUdFGyKkgpMbW ++UwSg4fdKM4qLSH7voTdsdVM9aAvLvYjBQ4gpORxDZNfUz67R0Ua0/oJt9jD49Wp +qcq+tDOnp0dPtn2hFluV5PxM6d+MCSx/frPsfvyt9234okLL1zdLDNFYEbLhSPjA +ku3vHw/OwlJOxCRwTkPqcElIV4+IvIbzAgSffyokzm/wKVKEhoT6NcfeU+6wCkTu +al1X8loJ+27N6jN13oGZfH7EveBqgR8rPs55+54S/OcVG/uqL9ggOGRJiIZ3jUBk +m5cN27wKkaNg/CQwa1UjcU4qshVpknHw1dpgJ2Gbs/yUphwpEZl/FTsZFcK1KCHD +rOp3PQIDAQABMA0GCSqGSIb3DQEBCwUAA4H/AAFmEq86broBFxs0cpImaM884PBT +bvJBSsFhsOg6mi4Gt01G/lPSj/ExNtH3G5bytCYAPaRxNx/dCs7uON3p86ta4zL8 +2PxgyhX1oY/GG63ETwn5s3GKpRaGTNVDWvPIM9RX6+bvX/wOg8eYXVaQlG5XYadC +Ms9lWqHaM1C/iLGNmUTGcdbvhnmQDky2CwPNm+lXogSWbrsGpAmCkXJD1H+0Mx8I +wjDVtGLBwr/8oXI8WbhvISMnS9+dd7+GLm6mU+14Kswi5I7EmBmREvkswi2IVJ6M +GL7EY3qA6iqJWqsseYyLxiMr3nBT0SETphzoDanUQI1/jXQPrWIyjqvs +-----END CERTIFICATE----- diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/ssl/key2.pem b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/ssl/key2.pem new file mode 100644 index 000000000000..9e21a1c3f421 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/ssl/key2.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCykIxR0UbIqSCk +xtb5TBKDh90oziotIfu+hN2x1Uz1oC8u9iMFDiCk5HENk19TPrtHRRrT+gm32MPj +1ampyr60M6enR0+2faEWW5Xk/Ezp34wJLH9+s+x+/K33bfiiQsvXN0sM0VgRsuFI ++MCS7e8fD87CUk7EJHBOQ+pwSUhXj4i8hvMCBJ9/KiTOb/ApUoSGhPo1x95T7rAK +RO5qXVfyWgn7bs3qM3XegZl8fsS94GqBHys+znn7nhL85xUb+6ov2CA4ZEmIhneN +QGSblw3bvAqRo2D8JDBrVSNxTiqyFWmScfDV2mAnYZuz/JSmHCkRmX8VOxkVwrUo +IcOs6nc9AgMBAAECggEAPN9dDolG1aIeYD3uzCa8Sv2WjdIWe7NRlEXMI9MgvL1i +SGKdVpxV0ZCU37llLkY85tNujWP4SyXIxdMxVxIoR9syJKsBSCd0sl//bgP6nmHY +Zco3HnTswu+VyLtDHuGhhtkxKwn0uXffKBaw44XcVhz38bPIaUI4zN2HPscks8BG +j2MEl0N8P/TVrTkhgdjfoRi73VAisrEe+1wCg74BT7cmR8fEr7iNFrv955sdPGdw +UTmx8U26++wbeYQs1ZE1713SYnRQuCUFs5GGjzOhNFi27zuhI6TafoVm9PO4j+ZC +JUKTyUTBUsRMvm9z1IoHdjM8yInAv2g0J1bAeCTY+wKBgQDuMNMbNVoiXRKsSUry +22T3W6HVLfLNKiYMNxsAkJjOiyyJcC+yg9BErn/haIHSafD2WmuWbW5ASViyl6fn +D8qMluTwEaSrTgHXWI4ahWyapDShDQYp1s4dB75Aa/LVcFCay54YEtyCPzCPlj1K +jz5OBV14NEVVA2cf59fIc/LXCwKBgQC/6m3TefUp5jnN/QUOx2OtZo8Y1pVrsuMB +AuTtb21Khxn/86ZpVzySzg79/DkSNf9/sZhzj0IkviWNP5S8iAAaFC1q08CYhdCX +d7tVnHlzpZmmoHUhG6dlJZayr1duZrURp2rP18+wIsKiFRImAyjc6yswVRpZgAiG +gOkHCB231wKBgGlwXZMWy/6YOtLfYvkcm5ZQDtSCkY+2j78qiZ53Y91SiHWSntqk +NQaiRGOw0n8lfJBhOG0PphV5InV0YtQLDnurtE59UOqwDmqYfddJpujRtaZxUIAm +4XjCW7rCzm0jWdscNbCscMaLWGDHffxKaqc5AsZaRTK73eOmysOmaCI/AoGAf/yd +RZ1dzJWHE0Kb7uE2LlvpLo1clLh1/ySo+1eGMV+sDS+2WSYedWEKSoO8o9JzE/ui +Sd7OI6bTcEFotdqVBs9SAp45IP6Mv5bPziZOMLvNnnv/4RaKKkBJId0hl7TTKHTY +HMg176ce2eznb4ZH6BzFbrQyoGFsThcGUPQurX0CgYBYtkDTp21TI1nuak7xpMIY +BJQpqF5ahBf/+QYWtL0f3ca9MO2++zv5/XXitvt48cY1bCHNrVvSHgRzwSrOorZA +5u7a5zyvfXjY3LY3k0VHddaVjU0mHsjx/1ux0wO2v8wQjOVZpT7XweB3WlUEGV7C +5T/p+rmGg5Y5dTKUVCyvbQ== +-----END PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 7cebf6fe8e44..8c64b7587c05 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -9,19 +9,18 @@ description = "Spring Boot Dependencies" bom { effectiveBomArtifact() upgrade { - policy = "same-minor-version" + policy = "same-major-version" gitHub { issueLabels = ["type: dependency-upgrade"] } } - library("ActiveMQ", "5.18.4") { + library("ActiveMQ", "6.1.2") { group("org.apache.activemq") { modules = [ "activemq-amqp", "activemq-blueprint", "activemq-broker", "activemq-client", - "activemq-client-jakarta", "activemq-console" { exclude group: "commons-logging", module: "commons-logging" }, @@ -36,7 +35,6 @@ bom { "activemq-openwire-generator", "activemq-openwire-legacy", "activemq-osgi", - "activemq-partition", "activemq-pool", "activemq-ra", "activemq-run", @@ -49,8 +47,14 @@ bom { "activemq-web" ] } + links { + site("https://activemq.apache.org") + docs("https://activemq.apache.org/components/classic/documentation") + releaseNotes { version -> "https://activemq.apache.org/components/classic/download/classic-%02d-%02d-%02d" + .formatted(version.componentInts()) } + } } - library("Angus Mail", "1.1.0") { + library("Angus Mail", "2.0.3") { group("org.eclipse.angus") { modules = [ "angus-core", @@ -64,26 +68,23 @@ bom { "smtp" ] } + links { + site("https://github.com/eclipse-ee4j/angus-mail") + releaseNotes("https://github.com/eclipse-ee4j/angus-mail/releases/tag/{version}") + } } - library("Artemis", "2.28.0") { + library("Artemis", "2.35.0") { group("org.apache.activemq") { - modules = [ - "artemis-amqp-protocol", - "artemis-commons", - "artemis-core-client", - "artemis-jakarta-client", - "artemis-jakarta-server", - "artemis-jakarta-service-extensions", - "artemis-jdbc-store", - "artemis-journal", - "artemis-quorum-api", - "artemis-selector", - "artemis-server", - "artemis-service-extensions" + imports = [ + "artemis-bom" ] } + links { + site("https://activemq.apache.org/components/artemis") + releaseNotes("https://activemq.apache.org/components/artemis/download/release-notes-{version}") + } } - library("AspectJ", "1.9.22") { + library("AspectJ", "1.9.22.1") { group("org.aspectj") { modules = [ "aspectjrt", @@ -91,6 +92,10 @@ bom { "aspectjweaver" ] } + links { + site("https://eclipse.dev/aspectj") + releaseNotes("https://github.com/eclipse-aspectj/aspectj/blob/master/docs/release/README-{version}.adoc") + } } library("AssertJ", "${assertjVersion}") { group("org.assertj") { @@ -98,6 +103,10 @@ bom { "assertj-bom" ] } + links { + site("https://assertj.github.io/doc/") + releaseNotes("https://github.com/assertj/assertj/releases/tag/assertj-build-{version}") + } } library("Awaitility", "4.2.1") { group("org.awaitility") { @@ -108,28 +117,56 @@ bom { "awaitility-scala" ] } + links { + releaseNotes { version -> "https://github.com/awaitility/awaitility/wiki/ReleaseNotes%s.%s" + .formatted(version.major(), version.minor()) } + } + } + library("Zipkin Reporter", "3.4.0") { + group("io.zipkin.reporter2") { + imports = [ + "zipkin-reporter-bom" + ] + } + links { + site("https://github.com/openzipkin/zipkin-reporter-java") + releaseNotes("https://github.com/openzipkin/zipkin-reporter-java/releases/tag/{version}") + } } - library("Brave", "5.15.1") { + library("Brave", "6.0.3") { group("io.zipkin.brave") { imports = [ "brave-bom" ] } + links { + site("https://github.com/openzipkin/brave") + releaseNotes("https://github.com/openzipkin/brave/releases/tag/{version}") + } } - library("Build Helper Maven Plugin", "3.3.0") { + library("Build Helper Maven Plugin", "3.6.0") { group("org.codehaus.mojo") { plugins = [ "build-helper-maven-plugin" ] } + links { + site("https://www.mojohaus.org/build-helper-maven-plugin") + releaseNotes("https://github.com/mojohaus/build-helper-maven-plugin/releases/tag/{version}") + } } - library("Byte Buddy", "1.14.16") { + library("Byte Buddy", "1.14.18") { group("net.bytebuddy") { modules = [ "byte-buddy", "byte-buddy-agent" ] } + links { + site("https://bytebuddy.net/") + docs("https://bytebuddy.net/#/tutorial") + releaseNotes("https://github.com/raphw/byte-buddy/releases/tag/byte-buddy-{version}") + } } library("cache2k", "2.6.1.Final") { group("org.cache2k") { @@ -142,6 +179,10 @@ bom { "cache2k-spring" ] } + links { + site("https://cache2k.org") + releaseNotes("https://github.com/cache2k/cache2k/releases/tag/v{version}") + } } library("Caffeine", "3.1.8") { group("com.github.ben-manes.caffeine") { @@ -152,9 +193,14 @@ bom { "simulator" ] } + links { + site("https://github.com/ben-manes/caffeine") + docs("https://github.com/ben-manes/caffeine/wiki") + releaseNotes("https://github.com/ben-manes/caffeine/releases/tag/v{version}") + } } - library("Cassandra Driver", "4.15.0") { - group("com.datastax.oss") { + library("Cassandra Driver", "4.18.1") { + group("org.apache.cassandra") { imports = [ "java-driver-bom" ] @@ -162,13 +208,22 @@ bom { "java-driver-core" ] } + links { + site { version -> "https://docs.datastax.com/en/developer/java-driver/%s.%s/" + .formatted(version.major(), version.minor()) } + releaseNotes { version -> "https://docs.datastax.com/en/developer/java-driver/%s.%s/changelog/#%s" + .formatted(version.major(), version.minor(), version.toString("-")) } + } } - library("Classmate", "1.5.1") { + library("Classmate", "1.7.0") { group("com.fasterxml") { modules = [ "classmate" ] } + links { + site("https://github.com/FasterXML/java-classmate") + } } library("Commons Codec", "${commonsCodecVersion}") { group("commons-codec") { @@ -176,8 +231,12 @@ bom { "commons-codec" ] } + links { + site("https://commons.apache.org/proper/commons-codec") + releaseNotes("https://commons.apache.org/proper/commons-codec/changes-report.html#a{version}") + } } - library("Commons DBCP2", "2.9.0") { + library("Commons DBCP2", "2.12.0") { group("org.apache.commons") { modules = [ "commons-dbcp2" { @@ -185,13 +244,19 @@ bom { } ] } + links { + site("https://commons.apache.org/proper/commons-dbcp") + } } - library("Commons Lang3", "3.12.0") { + library("Commons Lang3", "3.14.0") { group("org.apache.commons") { modules = [ "commons-lang3" ] } + links { + site("https://commons.apache.org/proper/commons-lang") + } } library("Commons Pool", "1.6") { group("commons-pool") { @@ -200,19 +265,39 @@ bom { ] } } - library("Commons Pool2", "2.11.1") { + library("Commons Pool2", "2.12.0") { group("org.apache.commons") { modules = [ "commons-pool2" ] } + links { + site("https://commons.apache.org/proper/commons-pool") + } } - library("Couchbase Client", "3.4.11") { + library("Couchbase Client", "3.6.2") { group("com.couchbase.client") { modules = [ "java-client" ] } + links { + site("https://docs.couchbase.com/java-sdk/current/hello-world/overview.html") + } + } + library("Crac", "1.5.0") { + group("org.crac") { + modules = [ + "crac" + ] + } + } + library("CycloneDX Maven Plugin", "2.8.0") { + group("org.cyclonedx") { + plugins = [ + "cyclonedx-maven-plugin" + ] + } } library("DB2 JDBC", "11.5.9.0") { group("com.ibm.db2") { @@ -221,14 +306,23 @@ bom { ] } } - library("Dependency Management Plugin", "1.1.5") { + library("Dependency Management Plugin", "1.1.6") { group("io.spring.gradle") { modules = [ "dependency-management-plugin" ] } + links { + site("https://github.com/spring-gradle-plugins/dependency-management-plugin") + docs("https://docs.spring.io/dependency-management-plugin/docs/{version}/reference/html/") + releaseNotes("https://github.com/spring-gradle-plugins/dependency-management-plugin/releases/tag/v{version}") + } } library("Derby", "10.16.1.1") { + prohibit { + versionRange "[10.17.1.0,)" + because "it requires Java 21" + } group("org.apache.derby") { modules = [ "derby", @@ -240,13 +334,6 @@ bom { ] } } - library("Dropwizard Metrics", "4.2.25") { - group("io.dropwizard.metrics") { - imports = [ - "metrics-bom" - ] - } - } library("Ehcache3", "3.10.8") { group("org.ehcache") { modules = [ @@ -261,8 +348,12 @@ bom { } ] } + links { + site("https://www.ehcache.org/") + releaseNotes("https://github.com/ehcache/ehcache3/releases/tag/v{version}") + } } - library("Elasticsearch Client", "8.7.1") { + library("Elasticsearch Client", "8.13.4") { group("org.elasticsearch.client") { modules = [ "elasticsearch-rest-client" { @@ -278,33 +369,63 @@ bom { "elasticsearch-java" ] } + links { + releaseNotes("https://www.elastic.co/guide/en/elasticsearch/reference/current/release-notes-{version}.html") + } } - library("Flyway", "9.16.3") { + library("Flyway", "10.15.2") { group("org.flywaydb") { modules = [ + "flyway-commandline", "flyway-core", + "flyway-database-db2", + "flyway-database-derby", + "flyway-database-hsqldb", + "flyway-database-informix", + "flyway-database-mongodb", + "flyway-database-oracle", + "flyway-database-postgresql", + "flyway-database-redshift", + "flyway-database-saphana", + "flyway-database-snowflake", + "flyway-database-sybasease", "flyway-firebird", + "flyway-gcp-bigquery", + "flyway-gcp-spanner", "flyway-mysql", + "flyway-singlestore", "flyway-sqlserver" ] plugins = [ "flyway-maven-plugin" ] } + links { + site("https://documentation.red-gate.com/flyway") + } } - library("FreeMarker", "2.3.32") { + library("FreeMarker", "2.3.33") { group("org.freemarker") { modules = [ "freemarker" ] } + links { + site("https://freemarker.apache.org") + releaseNotes { version -> "https://freemarker.apache.org/docs/versions_%s.html" + .formatted(version.toString("_")) } + } } - library("Git Commit ID Maven Plugin", "5.0.1") { + library("Git Commit ID Maven Plugin", "9.0.1") { group("io.github.git-commit-id") { plugins = [ "git-commit-id-maven-plugin" ] } + links { + site("https://github.com/git-commit-id/git-commit-id-maven-plugin") + releaseNotes("https://github.com/git-commit-id/git-commit-id-maven-plugin/releases/tag/v{version}") + } } library("Glassfish JAXB", "4.0.5") { group("org.glassfish.jaxb") { @@ -320,33 +441,57 @@ bom { ] } } - library("GraphQL Java", "20.8") { + library("GraphQL Java", "22.1") { + prohibit { + startsWith(["2018-", "2019-", "2020-", "2021-", "230521-"]) + because "These are snapshots that we don't want to see" + } + alignWith { + version { + from "org.springframework.graphql:spring-graphql" + } + } group("com.graphql-java") { modules = [ "graphql-java" ] } + links { + site("https://www.graphql-java.com/") + releaseNotes("https://github.com/graphql-java/graphql-java/releases/tag/v{version}") + } } - library("Groovy", "4.0.21") { + library("Groovy", "4.0.22") { group("org.apache.groovy") { imports = [ "groovy-bom" ] } + links { + site("https://groovy-lang.org") + } } - library("Gson", "2.10.1") { + library("Gson", "2.11.0") { group("com.google.code.gson") { modules = [ "gson" ] } + links { + site("https://github.com/google/gson") + releaseNotes("https://github.com/google/gson/releases/tag/gson-parent-{version}") + } } - library("H2", "2.1.214") { + library("H2", "2.3.230") { group("com.h2database") { modules = [ "h2" ] } + links { + site("https://www.h2database.com") + releaseNotes("https://github.com/h2database/h2database/releases/tag/version-{version}") + } } library("Hamcrest", "${hamcrestVersion}") { group("org.hamcrest") { @@ -357,15 +502,19 @@ bom { ] } } - library("Hazelcast", "5.2.5") { + library("Hazelcast", "5.4.0") { group("com.hazelcast") { modules = [ "hazelcast", "hazelcast-spring" ] } + links { + site("https://hazelcast.com") + releaseNotes("https://github.com/hazelcast/hazelcast/releases/tag/v{version}") + } } - library("Hibernate", "6.2.25.Final") { + library("Hibernate", "6.5.2.Final") { group("org.hibernate.orm") { modules = [ "hibernate-agroal", @@ -385,6 +534,17 @@ bom { "hibernate-vibur" ] } + links { + site("https://hibernate.org/orm") + javadoc { version -> "https://docs.jboss.org/hibernate/orm/%s.%s/javadocs" + .formatted(version.major(), version.minor()) } + docs { version -> "https://hibernate.org/orm/documentation/%s.%s" + .formatted(version.major(), version.minor()) } + releaseNotes { version -> "https://github.com/hibernate/hibernate-orm/releases/tag/%s" + .formatted(version.toString().replace(".Final", "")) } + add("userguide") { version -> "https://docs.jboss.org/hibernate/orm/%s.%s/userguide/html_single/Hibernate_User_Guide.html" + .formatted(version.major(), version.minor()) } + } } library("Hibernate Validator", "8.0.1.Final") { group("org.hibernate.validator") { @@ -394,28 +554,32 @@ bom { ] } } - library("HikariCP", "5.0.1") { + library("HikariCP", "5.1.0") { group("com.zaxxer") { modules = [ "HikariCP" ] } } - library("HSQLDB", "2.7.2") { + library("HSQLDB", "2.7.3") { group("org.hsqldb") { modules = [ "hsqldb" ] } } - library("HtmlUnit", "2.70.0") { - group("net.sourceforge.htmlunit") { + library("HtmlUnit", "4.3.0") { + group("org.htmlunit") { modules = [ "htmlunit" { exclude group: "commons-logging", module: "commons-logging" } ] } + links { + site("https://www.htmlunit.org") + releaseNotes("https://github.com/HtmlUnit/htmlunit/releases/tag/{version}") + } } library("HttpAsyncClient", "4.1.5") { group("org.apache.httpcomponents") { @@ -426,13 +590,12 @@ bom { ] } } - library("HttpClient5", "5.2.3") { + library("HttpClient5", "5.3.1") { group("org.apache.httpcomponents.client5") { modules = [ "httpclient5", "httpclient5-cache", - "httpclient5-fluent", - "httpclient5-win", + "httpclient5-fluent" ] } } @@ -444,7 +607,7 @@ bom { ] } } - library("HttpCore5", "5.2.4") { + library("HttpCore5", "5.2.5") { group("org.apache.httpcomponents.core5") { modules = [ "httpcore5", @@ -453,19 +616,27 @@ bom { ] } } - library("Infinispan", "14.0.28.Final") { + library("Infinispan", "15.0.5.Final") { group("org.infinispan") { imports = [ "infinispan-bom" ] } + links { + site("https://infinispan.org/") + releaseNotes("https://github.com/infinispan/infinispan/releases/tag/{version}") + } } - library("InfluxDB Java", "2.23") { + library("InfluxDB Java", "2.24") { group("org.influxdb") { modules = [ "influxdb-java" ] } + links { + site("https://github.com/influxdata/influxdb-java") + releaseNotes("https://github.com/influxdata/influxdb-java/releases/tag/influxdb-java-{version}") + } } library("Jackson Bom", "${jacksonVersion}") { group("com.fasterxml.jackson") { @@ -480,6 +651,10 @@ bom { "jakarta.activation-api" ] } + links { + site("https://github.com/jakartaee/jaf-api") + releaseNotes("https://github.com/jakartaee/jaf-api/releases/tag/{version}") + } } library("Jakarta Annotation", "2.1.1") { group("jakarta.annotation") { @@ -488,6 +663,13 @@ bom { ] } } + library("Jakarta Inject", "2.0.1") { + group("jakarta.inject") { + modules = [ + "jakarta.inject-api" + ] + } + } library("Jakarta JMS", "3.1.0") { group("jakarta.jms") { modules = [ @@ -515,6 +697,10 @@ bom { "jakarta.mail-api" ] } + links { + site("https://github.com/jakartaee/mail-api") + releaseNotes("https://github.com/jakartaee/mail-api/releases/tag/{version}") + } } library("Jakarta Management", "1.1.4") { group("jakarta.management.j2ee") { @@ -524,6 +710,10 @@ bom { } } library("Jakarta Persistence", "3.1.0") { + prohibit { + versionRange "[3.2.0-B01,3.2.0]" + because "it's part of Jakarta EE 11" + } group("jakarta.persistence") { modules = [ "jakarta.persistence-api" @@ -531,6 +721,10 @@ bom { } } library("Jakarta Servlet", "6.0.0") { + prohibit { + versionRange "[6.1.0-M1,6.1.0]" + because "it's part of Jakarta EE 11" + } group("jakarta.servlet") { modules = [ "jakarta.servlet-api" @@ -552,6 +746,10 @@ bom { } } library("Jakarta Validation", "3.0.2") { + prohibit { + versionRange "[3.1.0-M1,3.1.0]" + because "it's part of Jakarta EE 11" + } group("jakarta.validation") { modules = [ "jakarta.validation-api" @@ -559,6 +757,10 @@ bom { } } library("Jakarta WebSocket", "2.1.1") { + prohibit { + versionRange "[2.2.0-M1,2.2.0]" + because "it's part of Jakarta EE 11" + } group("jakarta.websocket") { modules = [ "jakarta.websocket-api", @@ -624,14 +826,18 @@ bom { ] } } - library("Jaybird", "5.0.4.java11") { + library("Jaybird", "5.0.5.java11") { + prohibit { + endsWith ".java8" + because "we use the .java11 version" + } group("org.firebirdsql.jdbc") { modules = [ "jaybird" ] } } - library("JBoss Logging", "3.5.3.Final") { + library("JBoss Logging", "3.6.0.Final") { group("org.jboss.logging") { modules = [ "jboss-logging" @@ -645,42 +851,59 @@ bom { ] } } - library("Jedis", "4.3.2") { + library("Jedis", "5.0.2") { group("redis.clients") { modules = [ "jedis" ] } + links { + site("https://github.com/redis/jedis") + releaseNotes("https://github.com/redis/jedis/releases/tag/v{version}") + } } - library("Jersey", "3.1.6") { + library("Jersey", "3.1.7") { group("org.glassfish.jersey") { imports = [ "jersey-bom" ] } + links { + site("https://github.com/eclipse-ee4j/jersey") + releaseNotes("https://github.com/eclipse-ee4j/jersey/releases/tag/{version}") + } } - library("Jetty Reactive HTTPClient", "3.0.13") { + library("Jetty Reactive HTTPClient", "4.0.5") { group("org.eclipse.jetty") { modules = [ "jetty-reactive-httpclient" ] } } - library("Jetty", "11.0.21") { + library("Jetty", "12.0.11") { + group("org.eclipse.jetty.ee10") { + imports = [ + "jetty-ee10-bom" + ] + } group("org.eclipse.jetty") { imports = [ "jetty-bom" ] } + links { + site("https://eclipse.dev/jetty") + releaseNotes("https://github.com/jetty/jetty.project/releases/tag/jetty-{version}") + } } - library("JMustache", "1.15") { + library("JMustache", "1.16") { group("com.samskivert") { modules = [ "jmustache" ] } } - library("jOOQ", "3.18.15") { + library("jOOQ", "3.19.10") { group("org.jooq") { modules = [ "jooq", @@ -692,6 +915,11 @@ bom { "jooq-codegen-maven" ] } + links { + site("https://www.jooq.org") + docs("https://www.jooq.org/doc/{version}/manual-single-page") + releaseNotes("https://github.com/jOOQ/jOOQ/releases/tag/version-{version}") + } } library("Json Path", "2.9.0") { group("com.jayway.jsonpath") { @@ -700,20 +928,32 @@ bom { "json-path-assert" ] } + links { + site("https://github.com/json-path/JsonPath") + releaseNotes("https://github.com/json-path/JsonPath/releases/tag/json-path-{version}") + } } - library("Json-smart", "2.4.11") { + library("Json-smart", "2.5.1") { group("net.minidev") { modules = [ "json-smart" ] } + links { + site("https://github.com/netplex/json-smart-v2") + releaseNotes("https://github.com/netplex/json-smart-v2/releases/tag/{version}") + } } - library("JsonAssert", "1.5.1") { + library("JsonAssert", "1.5.3") { group("org.skyscreamer") { modules = [ "jsonassert" ] } + links { + site("https://github.com/skyscreamer/JSONassert") + releaseNotes("https://github.com/skyscreamer/JSONassert/releases/tag/jsonassert-{version}") + } } library("JTDS", "1.3.1") { group("net.sourceforge.jtds") { @@ -735,8 +975,14 @@ bom { "junit-bom" ] } + links { + site("https://junit.org/junit5") + javadoc("https://junit.org/junit5/docs/{version}/api") + docs("https://junit.org/junit5/docs/{version}/user-guide") + releaseNotes("https://junit.org/junit5/docs/{version}/release-notes") + } } - library("Kafka", "3.4.1") { + library("Kafka", "3.7.1") { group("org.apache.kafka") { modules = [ "connect", @@ -779,6 +1025,10 @@ bom { "trogdor" ] } + links { + site("https://kafka.apache.org") + releaseNotes("https://downloads.apache.org/kafka/{version}/RELEASE_NOTES.html") + } } library("Kotlin", "${kotlinVersion}") { group("org.jetbrains.kotlin") { @@ -789,26 +1039,47 @@ bom { "kotlin-maven-plugin" ] } + links { + site("https://kotlinlang.org/") + docs("https://kotlinlang.org/docs/reference") + releaseNotes("https://github.com/JetBrains/kotlin/releases/tag/v{version}") + } } - library("Kotlin Coroutines", "1.6.4") { + library("Kotlin Coroutines", "1.8.1") { group("org.jetbrains.kotlinx") { imports = [ "kotlinx-coroutines-bom" ] } + links { + site("https://github.com/Kotlin/kotlinx.coroutines") + releaseNotes("https://github.com/Kotlin/kotlinx.coroutines/releases/tag/{version}") + } + } + library("Kotlin Serialization", "1.6.3") { + group("org.jetbrains.kotlinx") { + imports = [ + "kotlinx-serialization-bom" + ] + } + links { + site("https://github.com/Kotlin/kotlinx.serialization") + releaseNotes("https://github.com/Kotlin/kotlinx.serialization/releases/tag/v{version}") + } } - library("Lettuce", "6.2.7.RELEASE") { + library("Lettuce", "6.3.2.RELEASE") { group("io.lettuce") { modules = [ "lettuce-core" ] } - } - library("Liquibase", "4.20.0") { - prohibit { - versionRange "[4.21.0,4.21.2)" - because "https://github.com/liquibase/liquibase/issues/4135" + links { + site("https://github.com/lettuce-io/lettuce-core") + docs("https://lettuce.io/core/{version}/reference/index.html") + releaseNotes("https://github.com/lettuce-io/lettuce-core/releases/tag/{version}") } + } + library("Liquibase", "4.28.0") { group("org.liquibase") { modules = [ "liquibase-cdi", @@ -818,36 +1089,54 @@ bom { "liquibase-maven-plugin" ] } + links { + site("https://www.liquibase.com") + releaseNotes("https://github.com/liquibase/liquibase/releases/tag/v{version}") + } } - library("Log4j2", "2.20.0") { + library("Log4j2", "2.23.1") { group("org.apache.logging.log4j") { imports = [ "log4j-bom" ] } + links { + site("https://logging.apache.org/log4j") + releaseNotes("https://github.com/apache/logging-log4j2/releases/tag/rel%2F{version}") + } } - library("Logback", "1.4.14") { + library("Logback", "1.5.6") { group("ch.qos.logback") { modules = [ - "logback-access", "logback-classic", "logback-core" ] } + links { + site("https://logback.qos.ch") + } } - library("Lombok", "1.18.32") { + library("Lombok", "1.18.34") { group("org.projectlombok") { modules = [ "lombok" ] } + links { + site("https://projectlombok.org") + } } - library("MariaDB", "3.1.4") { + library("MariaDB", "3.4.0") { group("org.mariadb.jdbc") { modules = [ "mariadb-java-client" ] } + links { + site("https://mariadb.com/kb/en/mariadb-connector-j/") + releaseNotes { version -> "https://mariadb.com/kb/en/mariadb-connector-j-%s-release-notes/" + .formatted(version.toString("-")) } + } } library("Maven AntRun Plugin", "3.1.0") { group("org.apache.maven.plugins") { @@ -856,28 +1145,28 @@ bom { ] } } - library("Maven Assembly Plugin", "3.5.0") { + library("Maven Assembly Plugin", "3.7.1") { group("org.apache.maven.plugins") { plugins = [ "maven-assembly-plugin" ] } } - library("Maven Clean Plugin", "3.2.0") { + library("Maven Clean Plugin", "3.4.0") { group("org.apache.maven.plugins") { plugins = [ "maven-clean-plugin" ] } } - library("Maven Compiler Plugin", "3.11.0") { + library("Maven Compiler Plugin", "3.13.0") { group("org.apache.maven.plugins") { plugins = [ "maven-compiler-plugin" ] } } - library("Maven Dependency Plugin", "3.5.0") { + library("Maven Dependency Plugin", "3.7.1") { group("org.apache.maven.plugins") { plugins = [ "maven-dependency-plugin" @@ -891,21 +1180,21 @@ bom { ] } } - library("Maven Enforcer Plugin", "3.3.0") { + library("Maven Enforcer Plugin", "3.5.0") { group("org.apache.maven.plugins") { plugins = [ "maven-enforcer-plugin" ] } } - library("Maven Failsafe Plugin", "3.0.0") { + library("Maven Failsafe Plugin", "3.3.1") { group("org.apache.maven.plugins") { plugins = [ "maven-failsafe-plugin" ] } } - library("Maven Help Plugin", "3.4.0") { + library("Maven Help Plugin", "3.4.1") { group("org.apache.maven.plugins") { plugins = [ "maven-help-plugin" @@ -919,21 +1208,21 @@ bom { ] } } - library("Maven Invoker Plugin", "3.5.1") { + library("Maven Invoker Plugin", "3.7.0") { group("org.apache.maven.plugins") { plugins = [ "maven-invoker-plugin" ] } } - library("Maven Jar Plugin", "3.3.0") { + library("Maven Jar Plugin", "3.4.2") { group("org.apache.maven.plugins") { plugins = [ "maven-jar-plugin" ] } } - library("Maven Javadoc Plugin", "3.5.0") { + library("Maven Javadoc Plugin", "3.7.0") { group("org.apache.maven.plugins") { plugins = [ "maven-javadoc-plugin" @@ -947,35 +1236,35 @@ bom { ] } } - library("Maven Shade Plugin", "3.4.1") { + library("Maven Shade Plugin", "3.6.0") { group("org.apache.maven.plugins") { plugins = [ "maven-shade-plugin" ] } } - library("Maven Source Plugin", "3.2.1") { + library("Maven Source Plugin", "3.3.1") { group("org.apache.maven.plugins") { plugins = [ "maven-source-plugin" ] } } - library("Maven Surefire Plugin", "3.0.0") { + library("Maven Surefire Plugin", "3.3.1") { group("org.apache.maven.plugins") { plugins = [ "maven-surefire-plugin" ] } } - library("Maven War Plugin", "3.3.2") { + library("Maven War Plugin", "3.4.0") { group("org.apache.maven.plugins") { plugins = [ "maven-war-plugin" ] } } - library("Micrometer", "1.11.12") { + library("Micrometer", "1.14.0-M1") { considerSnapshots() group("io.micrometer") { modules = [ @@ -987,24 +1276,39 @@ bom { "micrometer-bom" ] } + links { + site("https://micrometer.io") + docs { version -> "https://docs.micrometer.io/micrometer/reference/%s.%s" + .formatted(version.major(), version.minor()) } + releaseNotes("https://github.com/micrometer-metrics/micrometer/releases/tag/v{version}") + } } - library("Micrometer Tracing", "1.1.13") { + library("Micrometer Tracing", "1.4.0-M1") { considerSnapshots() - calendarName = "Tracing" group("io.micrometer") { imports = [ "micrometer-tracing-bom" ] } + links { + site("https://micrometer.io") + docs { version -> "https://docs.micrometer.io/tracing/reference/%s.%s" + .formatted(version.major(), version.minor()) } + releaseNotes("https://github.com/micrometer-metrics/tracing/releases/tag/v{version}") + } } - library("Mockito", "5.3.1") { + library("Mockito", "5.12.0") { group("org.mockito") { imports = [ "mockito-bom" ] } + links { + site("https://site.mockito.org/") + releaseNotes("https://github.com/mockito/mockito/releases/tag/v{version}") + } } - library("MongoDB", "4.9.1") { + library("MongoDB", "5.0.1") { group("org.mongodb") { modules = [ "bson", @@ -1015,19 +1319,36 @@ bom { "mongodb-driver-sync" ] } + links { + site("https://github.com/mongodb/mongo-java-driver") + releaseNotes("https://github.com/mongodb/mongo-java-driver/releases/tag/r{version}") + } } - library("MSSQL JDBC", "11.2.3.jre17") { + library("MSSQL JDBC", "12.6.3.jre11") { + prohibit { + endsWith(".jre8") + because "we want to use the jre11 version" + } + prohibit { + endsWith("-preview") + because "we only want to use non-preview releases" + } prohibit { - endsWith([".jre8", ".jre11", ".jre18"]) - because "we use the .jre17 version" + versionRange "[12.7.0,12.7.0]" + because "it's actually a preview release" } group("com.microsoft.sqlserver") { modules = [ "mssql-jdbc" ] } + links { + site("https://github.com/microsoft/mssql-jdbc") + releaseNotes { version -> "https://github.com/microsoft/mssql-jdbc/releases/tag/v%s" + .formatted(version.toString().replace(".jre11", "")) } + } } - library("MySQL", "8.0.33") { + library("MySQL", "9.0.0") { group("com.mysql") { modules = [ "mysql-connector-j" { @@ -1042,6 +1363,10 @@ bom { "native-maven-plugin" ] } + links { + site("https://github.com/graalvm/native-build-tools") + releaseNotes("https://github.com/graalvm/native-build-tools/releases/tag/{version}") + } } library("NekoHTML", "1.9.22") { group("net.sourceforge.nekohtml") { @@ -1050,7 +1375,7 @@ bom { ] } } - library("Neo4j Java Driver", "5.20.0") { + library("Neo4j Java Driver", "5.22.0") { alignWith { version { from "org.springframework.data:spring-data-neo4j" @@ -1062,29 +1387,40 @@ bom { "neo4j-java-driver" ] } + links { + site("https://github.com/neo4j/neo4j-java-driver") + releaseNotes("https://github.com/neo4j/neo4j-java-driver/releases/tag/{version}") + } } - library("Netty", "4.1.110.Final") { + library("Netty", "4.1.111.Final") { group("io.netty") { imports = [ "netty-bom" ] } + links { + site("https://netty.io") + } } - library("OkHttp", "4.10.0") { + library("OkHttp", "4.12.0") { group("com.squareup.okhttp3") { imports = [ "okhttp-bom" ] } } - library("OpenTelemetry", "1.25.0") { + library("OpenTelemetry", "1.39.0") { group("io.opentelemetry") { imports = [ "opentelemetry-bom" ] } + links { + site("https://github.com/open-telemetry/opentelemetry-java") + releaseNotes("https://github.com/open-telemetry/opentelemetry-java/releases/tag/v{version}") + } } - library("Oracle Database", "21.9.0.0") { + library("Oracle Database", "23.4.0.24.05") { alignWith { dependencyManagementDeclaredIn("com.oracle.database.jdbc:ojdbc-bom") } @@ -1094,18 +1430,6 @@ bom { "simplefan" ] } - group("com.oracle.database.jdbc.debug") { - modules = [ - "ojdbc11-debug", - "ojdbc11-observability-debug", - "ojdbc11_g", - "ojdbc11dms_g", - "ojdbc8-debug", - "ojdbc8-observability-debug", - "ojdbc8_g", - "ojdbc8dms_g" - ] - } group("com.oracle.database.jdbc") { modules = [ "ojdbc11", @@ -1122,20 +1446,10 @@ bom { "orai18n" ] } - group("com.oracle.database.observability") { - modules = [ - "dms", - "ojdbc11-observability", - "ojdbc11dms", - "ojdbc8-observability", - "ojdbc8dms" - ] - } group("com.oracle.database.security") { modules = [ - "oraclepki", - "osdt_cert", - "osdt_core" + "oraclepki" + ] } group("com.oracle.database.xml") { @@ -1145,7 +1459,7 @@ bom { ] } } - library("Oracle R2DBC", "1.1.1") { + library("Oracle R2DBC", "1.2.0") { group("com.oracle.database.r2dbc") { modules = [ "oracle-r2dbc" @@ -1159,19 +1473,66 @@ bom { ] } } - library("Postgresql", "42.6.2") { + library("Postgresql", "42.7.3") { group("org.postgresql") { modules = [ "postgresql" ] } + links { + site("https://github.com/pgjdbc/pgjdbc") + releaseNotes("https://github.com/pgjdbc/pgjdbc/releases/tag/REL{version}") + } } - library("Prometheus Client", "0.16.0") { + library("Prometheus Client", "1.3.1") { + group("io.prometheus") { + imports = [ + "prometheus-metrics-bom" + ] + } + links { + site("https://github.com/prometheus/client_java") + releaseNotes("https://github.com/prometheus/client_java/releases/tag/v{version}") + } + } + library("Prometheus Simpleclient", "0.16.0") { group("io.prometheus") { imports = [ "simpleclient_bom" ] } + links { + site("https://github.com/prometheus/client_java") + releaseNotes("https://github.com/prometheus/client_java/releases/tag/parent-{version}") + } + } + library("Pulsar", "3.2.3") { + group("org.apache.pulsar") { + imports = [ + "pulsar-bom" + ] + } + links { + site("https://pulsar.apache.org") + docs { version -> "https://pulsar.apache.org/docs/%s.%s.x" + .formatted(version.major(), version.minor()) } + releaseNotes("https://pulsar.apache.org/release-notes/versioned/pulsar-{version}") + } + } + library("Pulsar Reactive", "0.5.6") { + group("org.apache.pulsar") { + modules = [ + "pulsar-client-reactive-adapter", + "pulsar-client-reactive-api", + "pulsar-client-reactive-jackson", + "pulsar-client-reactive-producer-cache-caffeine-shaded", + "pulsar-client-reactive-producer-cache-caffeine" + ] + } + links { + site("https://github.com/apache/pulsar-client-reactive") + releaseNotes("https://github.com/apache/pulsar-client-reactive/releases/tag/v{version}") + } } library("Quartz", "2.3.2") { group("org.quartz-scheduler") { @@ -1183,13 +1544,21 @@ bom { "quartz-jobs" ] } + links { + site("https://github.com/quartz-scheduler/quartz") + } } - library("QueryDSL", "5.0.0") { + library("QueryDSL", "5.1.0") { group("com.querydsl") { imports = [ "querydsl-bom" ] } + links { + site("https://github.com/querydsl/querydsl") + releaseNotes { version -> "https://github.com/querydsl/querydsl/releases/tag/QUERYDSL_%s" + .formatted(version.toString("_")) } + } } library("R2DBC H2", "1.0.0.RELEASE") { considerSnapshots() @@ -1199,7 +1568,7 @@ bom { ] } } - library("R2DBC MariaDB", "1.1.4") { + library("R2DBC MariaDB", "1.2.1") { group("org.mariadb") { modules = [ "r2dbc-mariadb" @@ -1213,7 +1582,7 @@ bom { ] } } - library("R2DBC MySQL", "1.0.6") { + library("R2DBC MySQL", "1.1.3") { group("io.asyncer") { modules = [ "r2dbc-mysql" @@ -1227,6 +1596,10 @@ bom { "r2dbc-pool" ] } + links { + site("https://github.com/r2dbc/r2dbc-pool") + releaseNotes("https://github.com/r2dbc/r2dbc-pool/releases/tag/v{version}") + } } library("R2DBC Postgresql", "1.0.5.RELEASE") { considerSnapshots() @@ -1252,19 +1625,27 @@ bom { ] } } - library("Rabbit AMQP Client", "5.17.1") { + library("Rabbit AMQP Client", "5.21.0") { group("com.rabbitmq") { modules = [ "amqp-client" ] } + links { + site("https://github.com/rabbitmq/rabbitmq-java-client") + releaseNotes("https://github.com/rabbitmq/rabbitmq-java-client/releases/tag/v{version}") + } } - library("Rabbit Stream Client", "0.9.0") { + library("Rabbit Stream Client", "0.16.0") { group("com.rabbitmq") { modules = [ "stream-client" ] } + links { + site("https://github.com/rabbitmq/rabbitmq-stream-java-client") + releaseNotes("https://github.com/rabbitmq/rabbitmq-stream-java-client/releases/tag/v{version}") + } } library("Reactive Streams", "1.0.4") { group("org.reactivestreams") { @@ -1273,7 +1654,7 @@ bom { ] } } - library("Reactor Bom", "2022.0.19") { + library("Reactor Bom", "2024.0.0-M4") { considerSnapshots() calendarName = "Reactor" group("io.projectreactor") { @@ -1281,8 +1662,12 @@ bom { "reactor-bom" ] } + links { + site("https://projectreactor.io/") + releaseNotes("https://github.com/reactor/reactor/releases/tag/{version}") + } } - library("REST Assured", "5.3.2") { + library("REST Assured", "5.5.0") { group("io.rest-assured") { imports = [ "rest-assured-bom" @@ -1299,6 +1684,10 @@ bom { "rsocket-bom" ] } + links { + site("https://github.com/rsocket/rsocket-java") + releaseNotes("https://github.com/rsocket/rsocket-java/releases/tag/{version}") + } } library("RxJava3", "3.1.8") { group("io.reactivex.rxjava3") { @@ -1323,8 +1712,9 @@ bom { "spring-boot-configuration-processor", "spring-boot-devtools", "spring-boot-docker-compose", - "spring-boot-jarmode-layertools", + "spring-boot-jarmode-tools", "spring-boot-loader", + "spring-boot-loader-classic", "spring-boot-loader-tools", "spring-boot-properties-migrator", "spring-boot-starter", @@ -1367,6 +1757,8 @@ bom { "spring-boot-starter-oauth2-authorization-server", "spring-boot-starter-oauth2-client", "spring-boot-starter-oauth2-resource-server", + "spring-boot-starter-pulsar", + "spring-boot-starter-pulsar-reactive", "spring-boot-starter-quartz", "spring-boot-starter-reactor-netty", "spring-boot-starter-rsocket", @@ -1385,6 +1777,15 @@ bom { "spring-boot-maven-plugin" ] } + links { + site("https://spring.io/projects/spring-boot") + github("https://github.com/spring-projects/spring-boot") + javadoc("https://docs.spring.io/spring-boot/docs/{version}/api") + docs("https://docs.spring.io/spring-boot/docs/{version}/reference/htmlsingle") + releaseNotes("https://github.com/spring-projects/spring-boot/releases/tag/v{version}") + add("layers-xsd") { version -> "layers-xsd: https://www.springframework.org/schema/boot/layers/layers-%s.%s.xsd" + .formatted(version.major(), version.minor()) } + } } library("SAAJ Impl", "3.0.4") { group("com.sun.xml.messaging.saaj") { @@ -1393,26 +1794,38 @@ bom { ] } } - library("Selenium", "4.8.3") { + library("Selenium", "4.22.0") { group("org.seleniumhq.selenium") { imports = [ "selenium-bom" ] } + links { + site("https://www.selenium.dev") + releaseNotes("https://github.com/SeleniumHQ/selenium/releases/tag/selenium-{version}") + } } - library("Selenium HtmlUnit", "4.8.3") { + library("Selenium HtmlUnit", "4.22.0") { group("org.seleniumhq.selenium") { modules = [ - "htmlunit-driver" + "htmlunit3-driver" ] } + links { + site("https://github.com/SeleniumHQ/htmlunit-driver") + releaseNotes("https://github.com/SeleniumHQ/htmlunit-driver/releases/tag/htmlunit-driver-{version}") + } } - library("SendGrid", "4.9.3") { + library("SendGrid", "4.10.2") { group("com.sendgrid") { modules = [ "sendgrid-java" ] } + links { + site("https://github.com/sendgrid/sendgrid-java") + releaseNotes("https://github.com/sendgrid/sendgrid-java/releases/tag/{version}") + } } library("SLF4J", "2.0.13") { group("org.slf4j") { @@ -1431,38 +1844,61 @@ bom { ] } } - library("SnakeYAML", "1.33") { + library("SnakeYAML", "${snakeYamlVersion}") { group("org.yaml") { modules = [ "snakeyaml" ] } } - library("Spring AMQP", "3.0.14") { + library("Spring AMQP", "3.2.0-M1") { considerSnapshots() group("org.springframework.amqp") { imports = [ "spring-amqp-bom" ] } + links { + site("https://spring.io/projects/spring-amqp") + github("https://github.com/spring-projects/spring-amqp") + javadoc("https://docs.spring.io/spring-amqp/docs/{version}/api") + docs("https://docs.spring.io/spring-amqp/reference/{version}") + releaseNotes("https://github.com/spring-projects/spring-amqp/releases/tag/v{version}") + } } - library("Spring Authorization Server", "1.1.7") { + library("Spring Authorization Server", "1.3.1") { considerSnapshots() group("org.springframework.security") { modules = [ "spring-security-oauth2-authorization-server" ] } + links { + site("https://spring.io/projects/spring-authorization-server") + github("https://github.com/spring-projects/spring-authorization-server") + javadoc("https://docs.spring.io/spring-authorization-server/docs/{version}/api") + docs { version -> "https://docs.spring.io/spring-authorization-server/reference/%s" + .formatted(version.forAntora()) } + releaseNotes("https://github.com/spring-projects/spring-authorization-server/releases/tag/{version}") + } } - library("Spring Batch", "5.0.6") { + library("Spring Batch", "5.1.2") { considerSnapshots() group("org.springframework.batch") { imports = [ "spring-batch-bom" ] } + links { + site("https://spring.io/projects/spring-batch") + github("https://github.com/spring-projects/spring-batch") + javadoc("https://docs.spring.io/spring-batch/docs/{version}/api") + docs { version -> "https://docs.spring.io/spring-batch/reference/%s" + .formatted(version.forAntora()) } + releaseNotes("https://github.com/spring-projects/spring-batch/releases/tag/v{version}") + } } - library("Spring Data Bom", "2023.0.12") { + library("Spring Data Bom", "2024.0.2") { considerSnapshots() calendarName = "Spring Data Release" group("org.springframework.data") { @@ -1470,7 +1906,11 @@ bom { "spring-data-bom" ] } - + links("spring-data") { + site("https://spring.io/projects/spring-data") + github("https://github.com/spring-projects/spring-data-bom") + releaseNotes("https://github.com/spring-projects/spring-data-bom/releases/tag/{version}") + } } library("Spring Framework", "${springFrameworkVersion}") { considerSnapshots() @@ -1479,8 +1919,15 @@ bom { "spring-framework-bom" ] } + links { + site("https://spring.io/projects/spring-framework") + github("https://github.com/spring-projects/spring-framework") + javadoc("https://docs.spring.io/spring-framework/docs/{version}/javadoc-api") + docs("https://docs.spring.io/spring-framework/reference/{version}") + releaseNotes("https://github.com/spring-projects/spring-framework/releases/tag/v{version}") + } } - library("Spring GraphQL", "1.2.6") { + library("Spring GraphQL", "1.3.2") { considerSnapshots() group("org.springframework.graphql") { modules = [ @@ -1488,24 +1935,47 @@ bom { "spring-graphql-test" ] } + links { + site("https://spring.io/projects/spring-graphql") + github("https://github.com/spring-projects/spring-graphql") + javadoc("https://docs.spring.io/spring-graphql/docs/{version}/api") + docs { version -> "https://docs.spring.io/spring-graphql/reference/%s" + .formatted(version.forAntora()) } + releaseNotes("https://github.com/spring-projects/spring-graphql/releases/tag/v{version}") + } } - library("Spring HATEOAS", "2.1.5") { + library("Spring HATEOAS", "2.3.1") { considerSnapshots() group("org.springframework.hateoas") { modules = [ "spring-hateoas" ] } + links { + site("https://spring.io/projects/spring-hateoas") + github("https://github.com/spring-projects/spring-hateoas") + javadoc("https://docs.spring.io/spring-hateoas/docs/{version}/api") + docs("https://docs.spring.io/spring-hateoas/docs/{version}/reference/html") + releaseNotes("https://github.com/spring-projects/spring-hateoas/releases/tag/{version}") + } } - library("Spring Integration", "6.1.9") { + library("Spring Integration", "6.4.0-M1") { considerSnapshots() group("org.springframework.integration") { imports = [ "spring-integration-bom" ] } + links { + site("https://spring.io/projects/spring-integration") + github("https://github.com/spring-projects/spring-integration") + javadoc("https://docs.spring.io/spring-integration/docs/{version}/api") + docs { version -> "https://docs.spring.io/spring-integration/reference/%s" + .formatted(version.forAntora()) } + releaseNotes("https://github.com/spring-projects/spring-integration/releases/tag/v{version}") + } } - library("Spring Kafka", "3.0.17") { + library("Spring Kafka", "3.3.0-M1") { considerSnapshots() group("org.springframework.kafka") { modules = [ @@ -1513,8 +1983,16 @@ bom { "spring-kafka-test" ] } + links { + site("https://spring.io/projects/spring-kafka") + github("https://github.com/spring-projects/spring-kafka") + javadoc("https://docs.spring.io/spring-kafka/docs/{version}/api") + docs { version -> "https://docs.spring.io/spring-kafka/reference/%s" + .formatted(version.forAntora()) } + releaseNotes("https://github.com/spring-projects/spring-kafka/releases/tag/v{version}") + } } - library("Spring LDAP", "3.1.5") { + library("Spring LDAP", "3.2.4") { considerSnapshots() group("org.springframework.ldap") { modules = [ @@ -1524,6 +2002,28 @@ bom { "spring-ldap-test" ] } + links { + site("https://spring.io/projects/spring-ldap") + github("https://github.com/spring-projects/spring-ldap") + javadoc("https://docs.spring.io/spring-ldap/docs/{version}/api") + docs("https://docs.spring.io/spring-ldap/reference/{version}") + releaseNotes("https://github.com/spring-projects/spring-ldap/releases/tag/{version}") + } + } + library("Spring Pulsar", "1.1.2") { + considerSnapshots() + group("org.springframework.pulsar") { + imports = [ + "spring-pulsar-bom" + ] + } + links { + site("https://spring.io/projects/spring-pulsar") + github("https://github.com/spring-projects/spring-pulsar") + javadoc("https://docs.spring.io/spring-pulsar/docs/{version}/api/") + docs("https://docs.spring.io/spring-pulsar/docs/{version}/reference") + releaseNotes("https://github.com/spring-projects/spring-pulsar/releases/tag/v{version}") + } } library("Spring RESTDocs", "3.0.1") { considerSnapshots() @@ -1532,24 +2032,43 @@ bom { "spring-restdocs-bom" ] } + links { + site("https://spring.io/projects/spring-restdocs") + github("https://github.com/spring-projects/spring-restdocs") + javadoc("https://docs.spring.io/spring-restdocs/docs/{version}/api/") + docs("https://docs.spring.io/spring-restdocs/docs/{version}/reference/htmlsingle/") + releaseNotes("https://github.com/spring-projects/spring-restdocs/releases/tag/v{version}") + } } - library("Spring Retry", "2.0.6") { + library("Spring Retry", "2.0.7") { considerSnapshots() group("org.springframework.retry") { modules = [ "spring-retry" ] } + links { + site("https://github.com/spring-projects/spring-retry") + releaseNotes("https://github.com/spring-projects/spring-retry/releases/tag/v{version}") + } } - library("Spring Security", "6.1.9") { + library("Spring Security", "6.4.0-M1") { considerSnapshots() group("org.springframework.security") { imports = [ "spring-security-bom" ] } + links { + site("https://spring.io/projects/spring-security") + github("https://github.com/spring-projects/spring-security") + javadoc("https://docs.spring.io/spring-security/site/docs/{version}/api") + docs { version -> "https://docs.spring.io/spring-security/reference/%s" + .formatted(version.forAntora()) } + releaseNotes("https://github.com/spring-projects/spring-security/releases/tag/{version}") + } } - library("Spring Session", "3.1.6") { + library("Spring Session", "3.4.0-M1") { considerSnapshots() prohibit { startsWith(["Apple-", "Bean-", "Corn-", "Dragonfruit-"]) @@ -1560,6 +2079,14 @@ bom { "spring-session-bom" ] } + links { + site("https://spring.io/projects/spring-session") + github("https://github.com/spring-projects/spring-session") + javadoc("https://docs.spring.io/spring-session/docs/{version}/api") + docs { version -> "https://docs.spring.io/spring-session/reference/%s" + .formatted(version.forAntora()) } + releaseNotes("https://github.com/spring-projects/spring-session/releases/tag/{version}") + } } library("Spring WS", "4.0.11") { considerSnapshots() @@ -1568,20 +2095,35 @@ bom { "spring-ws-bom" ] } + links("spring-webservices") { + site("https://spring.io/projects/spring-ws") + github("https://github.com/spring-projects/spring-ws") + javadoc("https://docs.spring.io/spring-ws/docs/{version}/api") + docs("https://docs.spring.io/spring-ws/docs/{version}/reference/html") + releaseNotes("https://github.com/spring-projects/spring-ws/releases/tag/v{version}") + } } - library("SQLite JDBC", "3.41.2.2") { + library("SQLite JDBC", "3.46.0.0") { group("org.xerial") { modules = [ "sqlite-jdbc" ] } + links { + site("https://github.com/xerial/sqlite-jdbc") + releaseNotes("https://github.com/xerial/sqlite-jdbc/releases/tag/{version}") + } } - library("Testcontainers", "1.18.3") { + library("Testcontainers", "1.19.8") { group("org.testcontainers") { imports = [ "testcontainers-bom" ] } + links { + site("https://java.testcontainers.org") + releaseNotes("https://github.com/testcontainers/testcontainers-java/releases/tag/{version}") + } } library("Thymeleaf", "3.1.2.RELEASE") { group("org.thymeleaf") { @@ -1590,6 +2132,10 @@ bom { "thymeleaf-spring6" ] } + links { + site("https://www.thymeleaf.org/") + releaseNotes("https://github.com/thymeleaf/thymeleaf/releases/tag/thymeleaf-{version}") + } } library("Thymeleaf Extras Data Attribute", "2.0.1") { group("com.github.mxab.thymeleaf.extras") { @@ -1605,7 +2151,7 @@ bom { ] } } - library("Thymeleaf Layout Dialect", "3.2.1") { + library("Thymeleaf Layout Dialect", "3.3.0") { group("nz.net.ultraq.thymeleaf") { modules = [ "thymeleaf-layout-dialect" @@ -1628,6 +2174,10 @@ bom { "tomcat-embed-websocket" ] } + links { + site("https://tomcat.apache.org") + docs { version -> "https://tomcat.apache.org/tomcat-%s.%s-doc".formatted(version.major(), version.minor()) } + } } library("UnboundID LDAPSDK", "6.0.11") { group("com.unboundid") { @@ -1637,6 +2187,10 @@ bom { } } library("Undertow", "2.3.13.Final") { + prohibit { + versionRange "[2.3.14.Final,2.3.15.Final]" + because "it contains a regression (https://issues.redhat.com/browse/UNDERTOW-2420)" + } group("io.undertow") { modules = [ "undertow-core", @@ -1645,14 +2199,21 @@ bom { ] } } - library("Versions Maven Plugin", "2.15.0") { + library("Versions Maven Plugin", "2.17.1") { group("org.codehaus.mojo") { plugins = [ "versions-maven-plugin" ] } } - library("WebJars Locator Core", "0.52") { + library("WebJars Locator Lite", "1.0.0") { + group("org.webjars") { + modules = [ + "webjars-locator-lite" + ] + } + } + library("WebJars Locator Core", "0.59") { group("org.webjars") { modules = [ "webjars-locator-core" @@ -1666,14 +2227,14 @@ bom { ] } } - library("XML Maven Plugin", "1.0.2") { + library("XML Maven Plugin", "1.1.0") { group("org.codehaus.mojo") { plugins = [ "xml-maven-plugin" ] } } - library("XmlUnit2", "2.9.1") { + library("XmlUnit2", "2.10.0") { group("org.xmlunit") { modules = [ "xmlunit-assertj", @@ -1685,6 +2246,10 @@ bom { "xmlunit-placeholders" ] } + links { + site("https://github.com/xmlunit/xmlunit") + releaseNotes("https://github.com/xmlunit/xmlunit/releases/tag/v{version}") + } } library("Yasson", "3.0.3") { group("org.eclipse") { @@ -1692,6 +2257,10 @@ bom { "yasson" ] } + links { + site("https://github.com/eclipse-ee4j/yasson") + releaseNotes("https://github.com/eclipse-ee4j/yasson/releases/tag/{version}") + } } } diff --git a/spring-boot-project/spring-boot-devtools/build.gradle b/spring-boot-project/spring-boot-devtools/build.gradle index 7e33304df021..b3047fd5d52d 100644 --- a/spring-boot-project/spring-boot-devtools/build.gradle +++ b/spring-boot-project/spring-boot-devtools/build.gradle @@ -65,9 +65,7 @@ dependencies { testImplementation("org.apache.tomcat.embed:tomcat-embed-jasper") testImplementation("org.assertj:assertj-core") testImplementation("org.awaitility:awaitility") - testImplementation("org.eclipse.jetty.websocket:websocket-jakarta-client") { - exclude group: "org.eclipse.jetty.toolchain", module: "jetty-jakarta-websocket-api" - } + testImplementation("org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-client") testImplementation("org.hamcrest:hamcrest-library") testImplementation("org.hsqldb:hsqldb") testImplementation("org.junit.jupiter:junit-jupiter") diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java index 08d496addc78..24f0a2f80bac 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsProperties.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsProperties.java index 2471f19136d1..aefcc8cee35b 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsProperties.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsProperties.java @@ -30,6 +30,7 @@ * * @author Phillip Webb * @author Stephane Nicoll + * @author Akshay Dubey * @since 1.3.0 */ @ConfigurationProperties(prefix = "spring.devtools") @@ -198,6 +199,22 @@ public static class Livereload { */ private int port = 35729; + /** + * Additional paths to watch for changes. + */ + private List additionalPaths = new ArrayList<>(); + + /** + * Amount of time to wait between polling for classpath changes. + */ + private Duration pollInterval = Duration.ofSeconds(1); + + /** + * Amount of quiet time required without any classpath changes before a reload is + * triggered. + */ + private Duration quietPeriod = Duration.ofMillis(400); + public boolean isEnabled() { return this.enabled; } @@ -214,6 +231,30 @@ public void setPort(int port) { this.port = port; } + public List getAdditionalPaths() { + return this.additionalPaths; + } + + public void setAdditionalPaths(List additionalPaths) { + this.additionalPaths = additionalPaths; + } + + public Duration getPollInterval() { + return this.pollInterval; + } + + public void setPollInterval(Duration pollInterval) { + this.pollInterval = pollInterval; + } + + public Duration getQuietPeriod() { + return this.quietPeriod; + } + + public void setQuietPeriod(Duration quietPeriod) { + this.quietPeriod = quietPeriod; + } + } } diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfiguration.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfiguration.java index 86043b6b6ce7..925273010ed2 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfiguration.java @@ -23,6 +23,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.DisposableBean; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -57,6 +58,7 @@ * @author Phillip Webb * @author Andy Wilkinson * @author Vladimir Tsanev + * @author Akshay Dubey * @since 1.3.0 */ @AutoConfiguration @@ -89,6 +91,21 @@ LiveReloadServerEventListener liveReloadServerEventListener(OptionalLiveReloadSe return new LiveReloadServerEventListener(liveReloadServer); } + @Bean + LiveReloadForAdditionalPaths liveReloadForAdditionalPaths(LiveReloadServer liveReloadServer, + DevToolsProperties properties, FileSystemWatcher fileSystemWatcher) { + return new LiveReloadForAdditionalPaths(liveReloadServer, + properties.getLivereload().getAdditionalPaths(),fileSystemWatcher); + } + + @Bean + FileSystemWatcher fileSystemWatcher(DevToolsProperties properties) { + return new FileSystemWatcher(true, + properties.getLivereload().getPollInterval(), + properties.getLivereload().getQuietPeriod() + ); + } + } /** @@ -216,4 +233,27 @@ public void onApplicationEvent(ClassPathChangedEvent event) { } + static class LiveReloadForAdditionalPaths implements DisposableBean { + + private final FileSystemWatcher fileSystemWatcher; + + @Override + public void destroy() throws Exception { + if(this.fileSystemWatcher!=null) + this.fileSystemWatcher.stop(); + } + + public LiveReloadForAdditionalPaths( LiveReloadServer liveReloadServer, List staticLocations, FileSystemWatcher fileSystemWatcher) { + this.fileSystemWatcher = fileSystemWatcher; + + for (File path : staticLocations) { + this.fileSystemWatcher.addSourceDirectory(path.getAbsoluteFile()); + } + + this.fileSystemWatcher.addListener(__ -> liveReloadServer.triggerReload()); + + this.fileSystemWatcher.start(); + } + } + } diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/RemoteDevtoolsSecurityConfiguration.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/RemoteDevtoolsSecurityConfiguration.java index eba98a6b0280..841fe0e1af1d 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/RemoteDevtoolsSecurityConfiguration.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/RemoteDevtoolsSecurityConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -49,7 +50,7 @@ class RemoteDevtoolsSecurityConfiguration { SecurityFilterChain devtoolsSecurityFilterChain(HttpSecurity http) throws Exception { http.securityMatcher(new AntPathRequestMatcher(this.url)); http.authorizeHttpRequests((requests) -> requests.anyRequest().anonymous()); - http.csrf((csrf) -> csrf.disable()); + http.csrf(CsrfConfigurer::disable); return http.build(); } diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/filewatch/FileSystemWatcher.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/filewatch/FileSystemWatcher.java index 99ef112ea3b5..35a950c6e3b1 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/filewatch/FileSystemWatcher.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/filewatch/FileSystemWatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -158,9 +158,7 @@ public void setTriggerFilter(FileFilter triggerFilter) { } private void checkNotStarted() { - synchronized (this.monitor) { - Assert.state(this.watchThread == null, "FileSystemWatcher already started"); - } + Assert.state(this.watchThread == null, "FileSystemWatcher already started"); } /** diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/remote/client/ClassPathChangeUploader.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/remote/client/ClassPathChangeUploader.java index 8cfe3144db9f..44bf7d24d58d 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/remote/client/ClassPathChangeUploader.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/remote/client/ClassPathChangeUploader.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -92,15 +92,14 @@ public void onApplicationEvent(ClassPathChangedEvent event) { try { ClassLoaderFiles classLoaderFiles = getClassLoaderFiles(event); byte[] bytes = serialize(classLoaderFiles); - performUpload(classLoaderFiles, bytes, event); + performUpload(bytes, event); } catch (IOException ex) { throw new IllegalStateException(ex); } } - private void performUpload(ClassLoaderFiles classLoaderFiles, byte[] bytes, ClassPathChangedEvent event) - throws IOException { + private void performUpload(byte[] bytes, ClassPathChangedEvent event) throws IOException { try { while (true) { try { diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/MainMethod.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/MainMethod.java index a2435453783c..9f3767ae6b94 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/MainMethod.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/MainMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,7 @@ private Method getMainMethod(Thread thread) { StackTraceElement[] stackTrace = thread.getStackTrace(); for (int i = stackTrace.length - 1; i >= 0; i--) { StackTraceElement element = stackTrace[i]; - if ("main".equals(element.getMethodName())) { + if ("main".equals(element.getMethodName()) && !isLoaderClass(element.getClassName())) { Method method = getMainMethod(element); if (method != null) { return method; @@ -53,6 +53,10 @@ private Method getMainMethod(Thread thread) { throw new IllegalStateException("Unable to find main method"); } + private boolean isLoaderClass(String className) { + return className.startsWith("org.springframework.boot.loader."); + } + private Method getMainMethod(StackTraceElement element) { try { Class elementClass = Class.forName(element.getClassName()); diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java index 071395a21e4e..284056061943 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java @@ -22,7 +22,6 @@ import java.net.URL; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; @@ -30,6 +29,7 @@ import java.util.Set; import java.util.concurrent.BlockingDeque; import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadFactory; @@ -68,9 +68,9 @@ * {@link #initialize(String[])} directly if your SpringApplication arguments are not * identical to your main method arguments. *

- * By default, applications running in an IDE (i.e. those not packaged as "fat jars") will - * automatically detect URLs that can change. It's also possible to manually configure - * URLs or class file updates for remote restart scenarios. + * By default, applications running in an IDE (i.e. those not packaged as "uber jars") + * will automatically detect URLs that can change. It's also possible to manually + * configure URLs or class file updates for remote restart scenarios. * * @author Phillip Webb * @author Andy Wilkinson @@ -92,7 +92,7 @@ public class Restarter { private final ClassLoaderFiles classLoaderFiles = new ClassLoaderFiles(); - private final Map attributes = new HashMap<>(); + private final Map attributes = new ConcurrentHashMap<>(); private final BlockingDeque leakSafeThreads = new LinkedBlockingDeque<>(); @@ -440,18 +440,11 @@ private LeakSafeThread getLeakSafeThread() { } public Object getOrAddAttribute(String name, final ObjectFactory objectFactory) { - synchronized (this.attributes) { - if (!this.attributes.containsKey(name)) { - this.attributes.put(name, objectFactory.getObject()); - } - return this.attributes.get(name); - } + return this.attributes.computeIfAbsent(name, (ignore) -> objectFactory.getObject()); } public Object removeAttribute(String name) { - synchronized (this.attributes) { - return this.attributes.remove(name); - } + return this.attributes.remove(name); } /** diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/classloader/ClassLoaderFile.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/classloader/ClassLoaderFile.java index d39c569e5ebd..46df7df0d325 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/classloader/ClassLoaderFile.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/classloader/ClassLoaderFile.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,8 +55,12 @@ public ClassLoaderFile(Kind kind, byte[] contents) { */ public ClassLoaderFile(Kind kind, long lastModified, byte[] contents) { Assert.notNull(kind, "Kind must not be null"); - Assert.isTrue((kind != Kind.DELETED) ? contents != null : contents == null, - () -> "Contents must " + ((kind != Kind.DELETED) ? "not " : "") + "be null"); + if (kind == Kind.DELETED) { + Assert.isTrue(contents == null, "Contents must be null"); + } + else { + Assert.isTrue(contents != null, "Contents must not be null"); + } this.kind = kind; this.lastModified = lastModified; this.contents = contents; diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/classloader/ClassLoaderFiles.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/classloader/ClassLoaderFiles.java index e611e5f39827..06e7d8f3f97b 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/classloader/ClassLoaderFiles.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/classloader/ClassLoaderFiles.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -108,12 +108,7 @@ private void removeAll(String name) { * @return an existing or newly added {@link SourceDirectory} */ protected final SourceDirectory getOrCreateSourceDirectory(String name) { - SourceDirectory sourceDirectory = this.sourceDirectories.get(name); - if (sourceDirectory == null) { - sourceDirectory = new SourceDirectory(name); - this.sourceDirectories.put(name, sourceDirectory); - } - return sourceDirectory; + return this.sourceDirectories.computeIfAbsent(name, (key) -> new SourceDirectory(name)); } /** diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfigurationTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfigurationTests.java index 478e6ff28388..99ed063ce14e 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfigurationTests.java @@ -37,6 +37,7 @@ import org.springframework.boot.autoconfigure.web.WebProperties; import org.springframework.boot.autoconfigure.web.WebProperties.Resources; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; +import org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration.LiveReloadForAdditionalPaths; import org.springframework.boot.devtools.classpath.ClassPathChangedEvent; import org.springframework.boot.devtools.classpath.ClassPathFileSystemWatcher; import org.springframework.boot.devtools.livereload.LiveReloadServer; @@ -70,6 +71,7 @@ * @author Phillip Webb * @author Andy Wilkinson * @author Vladimir Tsanev + * @author Akshay Dubey */ @ExtendWith(MockRestarter.class) class LocalDevToolsAutoConfigurationTests { @@ -213,6 +215,20 @@ void watchingAdditionalPaths() throws Exception { .containsKey(new File("src/test/java").getAbsoluteFile()); } + @Test + void watchingAdditionalPathsForReload() throws Exception { + Map properties = new HashMap<>(); + properties.put("spring.devtools.livereload.additional-paths", "src/main/java,src/test/java"); + this.context = getContext(() -> initializeAndRun(Config.class, properties)); + LiveReloadForAdditionalPaths liveReloadForAdditionalPaths = this.context.getBean(LiveReloadForAdditionalPaths.class); + Object watcher = ReflectionTestUtils.getField(liveReloadForAdditionalPaths, "fileSystemWatcher"); + @SuppressWarnings("unchecked") + Map directories = (Map) ReflectionTestUtils.getField(watcher, "directories"); + assertThat(directories).hasSize(2) + .containsKey(new File("src/main/java").getAbsoluteFile()) + .containsKey(new File("src/test/java").getAbsoluteFile()); + } + @Test void devToolsSwitchesJspServletToDevelopmentMode() throws Exception { this.context = getContext(() -> initializeAndRun(Config.class)); diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/RemoteDevToolsAutoConfigurationTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/RemoteDevToolsAutoConfigurationTests.java index 895adead505a..73744602f303 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/RemoteDevToolsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/RemoteDevToolsAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,7 @@ import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.mock.web.MockFilterChain; @@ -44,15 +44,12 @@ import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockServletContext; import org.springframework.security.config.BeanIds; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.Mockito.mock; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link RemoteDevToolsAutoConfiguration}. @@ -151,14 +148,10 @@ void invokeRestartWithCustomServerContextPath() throws Exception { void securityConfigurationShouldAllowAccess() throws Exception { this.context = getContext(() -> loadContext("spring.devtools.remote.secret:supersecret")); DispatcherFilter filter = this.context.getBean(DispatcherFilter.class); - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(springSecurity()) - .addFilter(filter) - .build(); - mockMvc - .perform(MockMvcRequestBuilders.get(DEFAULT_CONTEXT_PATH + "/restart") - .header(DEFAULT_SECRET_HEADER_NAME, "supersecret")) - .andExpect(status().isOk()); + MockMvcTester mvc = MockMvcTester.from(this.context, + (builder) -> builder.apply(springSecurity()).addFilter(filter).build()); + assertThat(mvc.get().uri(DEFAULT_CONTEXT_PATH + "/restart").header(DEFAULT_SECRET_HEADER_NAME, "supersecret")) + .hasStatusOk(); assertRestartInvoked(true); assertThat(this.context.containsBean("devtoolsSecurityFilterChain")).isTrue(); } @@ -168,14 +161,10 @@ void securityConfigurationShouldAllowAccessToCustomPath() throws Exception { this.context = getContext(() -> loadContext("spring.devtools.remote.secret:supersecret", "server.servlet.context-path:/test", "spring.devtools.remote.context-path:/custom")); DispatcherFilter filter = this.context.getBean(DispatcherFilter.class); - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(springSecurity()) - .addFilter(filter) - .build(); - mockMvc - .perform(MockMvcRequestBuilders.get("/test/custom/restart") - .header(DEFAULT_SECRET_HEADER_NAME, "supersecret")) - .andExpect(status().isOk()); + MockMvcTester mvc = MockMvcTester.from(this.context, + (builder) -> builder.apply(springSecurity()).addFilter(filter).build()); + assertThat(mvc.get().uri("/test/custom/restart").header(DEFAULT_SECRET_HEADER_NAME, "supersecret")) + .hasStatusOk(); assertRestartInvoked(true); } @@ -184,11 +173,9 @@ void securityConfigurationDoesNotAffectOtherPaths() throws Exception { this.context = getContext(() -> loadContext("spring.devtools.remote.secret:supersecret")); DispatcherFilter filter = this.context.getBean(DispatcherFilter.class); Filter securityFilterChain = this.context.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN, Filter.class); - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .addFilter(securityFilterChain) - .addFilter(filter) - .build(); - mockMvc.perform(MockMvcRequestBuilders.get("/my-path")).andExpect(status().isUnauthorized()); + MockMvcTester mvc = MockMvcTester.from(this.context, + (builder) -> builder.addFilter(securityFilterChain).addFilter(filter).build()); + assertThat(mvc.get().uri("/my-path")).hasStatus(HttpStatus.UNAUTHORIZED); } @Test @@ -241,14 +228,14 @@ private void assertRestartInvoked(boolean value) { private AnnotationConfigServletWebApplicationContext loadContext(String... properties) { AnnotationConfigServletWebApplicationContext context = new AnnotationConfigServletWebApplicationContext(); context.setServletContext(new MockServletContext()); - context.register(Config.class, PropertyPlaceholderAutoConfiguration.class); + context.register(Config.class, SecurityAutoConfiguration.class, RemoteDevToolsAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); TestPropertyValues.of(properties).applyTo(context); context.refresh(); return context; } @Configuration(proxyBeanMethods = false) - @Import({ SecurityAutoConfiguration.class, RemoteDevToolsAutoConfiguration.class }) static class Config { @Bean diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/filewatch/FileSystemWatcherTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/filewatch/FileSystemWatcherTests.java index b9b4928f5ea2..612850f90f64 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/filewatch/FileSystemWatcherTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/filewatch/FileSystemWatcherTests.java @@ -297,7 +297,7 @@ private void setupWatcher(long pollingInterval, long quietPeriod) { private void setupWatcher(long pollingInterval, long quietPeriod, SnapshotStateRepository snapshotStateRepository) { this.watcher = new FileSystemWatcher(false, Duration.ofMillis(pollingInterval), Duration.ofMillis(quietPeriod), snapshotStateRepository); - this.watcher.addListener((changeSet) -> FileSystemWatcherTests.this.changes.add(changeSet)); + this.watcher.addListener(FileSystemWatcherTests.this.changes::add); } private File startWithNewDirectory() { diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ChangeableUrlsTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ChangeableUrlsTests.java index 354fd1d7e301..35504c892648 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ChangeableUrlsTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ChangeableUrlsTests.java @@ -48,7 +48,7 @@ class ChangeableUrlsTests { @Test void directoryUrl() throws Exception { URL url = makeUrl("myproject"); - assertThat(ChangeableUrls.fromUrls(url).size()).isOne(); + assertThat(ChangeableUrls.fromUrls(url)).hasSize(1); } @Test diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/MainMethodTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/MainMethodTests.java index 593735aff578..b8fce3b1a317 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/MainMethodTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/MainMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.boot.loader.launch.FakeJarLauncher; import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -64,6 +65,15 @@ void nestedMainMethod() throws Exception { assertThat(method.getDeclaringClassName()).isEqualTo(nestedMain.getDeclaringClass().getName()); } + @Test // gh-39733 + void vaiJarLauncher() throws Exception { + FakeJarLauncher.action = (args) -> Valid.main(args); + MainMethod method = new TestThread(FakeJarLauncher::main).test(); + Method expectedMain = Valid.class.getMethod("main", String[].class); + assertThat(method.getMethod()).isEqualTo(expectedMain); + assertThat(method.getDeclaringClassName()).isEqualTo(expectedMain.getDeclaringClass().getName()); + } + @Test void missingArgsMainMethod() { assertThatIllegalStateException().isThrownBy(() -> new TestThread(MissingArgs::main).test()) diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/loader/launch/FakeJarLauncher.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/loader/launch/FakeJarLauncher.java new file mode 100644 index 000000000000..fb522a2da49e --- /dev/null +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/loader/launch/FakeJarLauncher.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.loader.launch; + +import java.util.function.Consumer; + +/** + * Fake launcher in the {@code org.springframework.boot.loader.launch} package used in + * {@code MainMethodTests}. + * + * @author Phillip Webb + */ +public final class FakeJarLauncher { + + public static Consumer action; + + private FakeJarLauncher() { + } + + public static void main(String... args) { + action.accept(args); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/build.gradle b/spring-boot-project/spring-boot-docker-compose/build.gradle index b0429632ffac..48aea480e6f9 100644 --- a/spring-boot-project/spring-boot-docker-compose/build.gradle +++ b/spring-boot-project/spring-boot-docker-compose/build.gradle @@ -1,9 +1,10 @@ plugins { - id "java-library" + id "java-library" id "org.springframework.boot.configuration-properties" id "org.springframework.boot.conventions" id "org.springframework.boot.deployed" - id "org.springframework.boot.optional-dependencies" + id "org.springframework.boot.docker-test" + id "org.springframework.boot.optional-dependencies" } description = "Spring Boot Docker Compose Support" @@ -11,6 +12,18 @@ description = "Spring Boot Docker Compose Support" dependencies { api(project(":spring-boot-project:spring-boot")) + dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker")) + dockerTestImplementation("org.assertj:assertj-core") + dockerTestImplementation("org.awaitility:awaitility") + dockerTestImplementation("org.junit.jupiter:junit-jupiter") + dockerTestImplementation("org.testcontainers:testcontainers") + + dockerTestRuntimeOnly("com.microsoft.sqlserver:mssql-jdbc") + dockerTestRuntimeOnly("com.oracle.database.r2dbc:oracle-r2dbc") + dockerTestRuntimeOnly("io.r2dbc:r2dbc-mssql") + dockerTestRuntimeOnly("org.postgresql:postgresql") + dockerTestRuntimeOnly("org.postgresql:r2dbc-postgresql") + implementation("com.fasterxml.jackson.core:jackson-databind") implementation("com.fasterxml.jackson.module:jackson-module-parameter-names") @@ -18,20 +31,15 @@ dependencies { optional(project(":spring-boot-project:spring-boot-actuator-autoconfigure")) optional("io.r2dbc:r2dbc-spi") optional("org.mongodb:mongodb-driver-core") + optional("org.neo4j.driver:neo4j-java-driver") optional("org.springframework.data:spring-data-r2dbc") testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation(project(":spring-boot-project:spring-boot-test")) testImplementation("ch.qos.logback:logback-classic") testImplementation("org.assertj:assertj-core") - testImplementation("org.awaitility:awaitility") testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.mockito:mockito-core") testImplementation("org.springframework:spring-core-test") testImplementation("org.springframework:spring-test") - testImplementation("org.testcontainers:testcontainers") - - testRuntimeOnly("com.microsoft.sqlserver:mssql-jdbc") - testRuntimeOnly("com.oracle.database.r2dbc:oracle-r2dbc") - testRuntimeOnly("io.r2dbc:r2dbc-mssql") } diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerCliIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/core/DockerCliIntegrationTests.java similarity index 87% rename from spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerCliIntegrationTests.java rename to spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/core/DockerCliIntegrationTests.java index fe00279095b9..c7bdc9896365 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerCliIntegrationTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/core/DockerCliIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,9 +36,9 @@ import org.springframework.boot.docker.compose.core.DockerCliCommand.ComposeUp; import org.springframework.boot.docker.compose.core.DockerCliCommand.Inspect; import org.springframework.boot.logging.LogLevel; +import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.boot.testsupport.process.DisabledIfProcessUnavailable; -import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.core.io.ClassPathResource; import org.springframework.util.FileCopyUtils; @@ -77,7 +77,7 @@ void runLifecycle() throws IOException { DockerCliComposeConfigResponse config = cli.run(new ComposeConfig()); assertThat(config.services()).containsOnlyKeys("redis"); // Run up - cli.run(new ComposeUp(LogLevel.INFO)); + cli.run(new ComposeUp(LogLevel.INFO, Collections.emptyList())); // Run ps and use id to run inspect on the id ps = cli.run(new ComposePs()); assertThat(ps).hasSize(1); @@ -86,14 +86,14 @@ void runLifecycle() throws IOException { assertThat(inspect).isNotEmpty(); assertThat(inspect.get(0).id()).startsWith(id); // Run stop, then run ps and verify the services are stopped - cli.run(new ComposeStop(Duration.ofSeconds(10))); + cli.run(new ComposeStop(Duration.ofSeconds(10), Collections.emptyList())); ps = cli.run(new ComposePs()); assertThat(ps).isEmpty(); // Run start, verify service is there, then run down and verify they are gone - cli.run(new ComposeStart(LogLevel.INFO)); + cli.run(new ComposeStart(LogLevel.INFO, Collections.emptyList())); ps = cli.run(new ComposePs()); assertThat(ps).hasSize(1); - cli.run(new ComposeDown(Duration.ofSeconds(10))); + cli.run(new ComposeDown(Duration.ofSeconds(10), Collections.emptyList())); ps = cli.run(new ComposePs()); assertThat(ps).isEmpty(); } @@ -105,7 +105,7 @@ void runLifecycle() throws IOException { private static void quietComposeDown(DockerCli cli) { try { - cli.run(new ComposeDown(Duration.ZERO)); + cli.run(new ComposeDown(Duration.ZERO, Collections.emptyList())); } catch (RuntimeException ex) { // Ignore @@ -116,8 +116,7 @@ private static File createComposeFile() throws IOException { File composeFile = new ClassPathResource("redis-compose.yaml", DockerCliIntegrationTests.class).getFile(); File tempComposeFile = Path.of(tempDir.toString(), composeFile.getName()).toFile(); String composeFileContent = FileCopyUtils.copyToString(new FileReader(composeFile)); - composeFileContent = composeFileContent.replace("{imageName}", - DockerImageNames.redis().asCanonicalNameString()); + composeFileContent = composeFileContent.replace("{imageName}", TestImage.REDIS.toString()); FileCopyUtils.copy(composeFileContent, new FileWriter(tempComposeFile)); return tempComposeFile; } diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQClassicDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQClassicDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..deff2d359eb6 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQClassicDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.activemq; + +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link ActiveMQClassicDockerComposeConnectionDetailsFactory}. + * + * @author Stephane Nicoll + * @author Eddú Meléndez + */ +class ActiveMQClassicDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "activemq-classic-compose.yaml", image = TestImage.ACTIVE_MQ_CLASSIC) + void runCreatesConnectionDetails(ActiveMQConnectionDetails connectionDetails) { + assertThat(connectionDetails.getBrokerUrl()).isNotNull().startsWith("tcp://"); + assertThat(connectionDetails.getUser()).isEqualTo("root"); + assertThat(connectionDetails.getPassword()).isEqualTo("secret"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..916e2413e8e2 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.activemq; + +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link ActiveMQDockerComposeConnectionDetailsFactory}. + * + * @author Stephane Nicoll + */ +class ActiveMQDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "activemq-compose.yaml", image = TestImage.ACTIVE_MQ) + void runCreatesConnectionDetails(ActiveMQConnectionDetails connectionDetails) { + assertThat(connectionDetails.getBrokerUrl()).isNotNull().startsWith("tcp://"); + assertThat(connectionDetails.getUser()).isEqualTo("root"); + assertThat(connectionDetails.getPassword()).isEqualTo("secret"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..b1174135fbf3 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.activemq; + +import org.springframework.boot.autoconfigure.jms.artemis.ArtemisConnectionDetails; +import org.springframework.boot.autoconfigure.jms.artemis.ArtemisMode; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link ArtemisDockerComposeConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +class ArtemisDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "artemis-compose.yaml", image = TestImage.ARTEMIS) + void runCreatesConnectionDetails(ArtemisConnectionDetails connectionDetails) { + assertThat(connectionDetails.getMode()).isEqualTo(ArtemisMode.NATIVE); + assertThat(connectionDetails.getBrokerUrl()).isNotNull().startsWith("tcp://"); + assertThat(connectionDetails.getUser()).isEqualTo("root"); + assertThat(connectionDetails.getPassword()).isEqualTo("secret"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/cassandra/CassandraDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/cassandra/CassandraDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..90111fc51f5a --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/cassandra/CassandraDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.cassandra; + +import java.util.List; + +import org.springframework.boot.autoconfigure.cassandra.CassandraConnectionDetails; +import org.springframework.boot.autoconfigure.cassandra.CassandraConnectionDetails.Node; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration test for {@link CassandraDockerComposeConnectionDetailsFactory}. + * + * @author Scott Frederick + */ +class CassandraDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "cassandra-compose.yaml", image = TestImage.CASSANDRA) + void runCreatesConnectionDetails(CassandraConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + @DockerComposeTest(composeFile = "cassandra-bitnami-compose.yaml", image = TestImage.BITNAMI_CASSANDRA) + void runWithBitnamiImageCreatesConnectionDetails(CassandraConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + private void assertConnectionDetails(CassandraConnectionDetails connectionDetails) { + List contactPoints = connectionDetails.getContactPoints(); + assertThat(contactPoints).hasSize(1); + Node node = contactPoints.get(0); + assertThat(node.host()).isNotNull(); + assertThat(node.port()).isGreaterThan(0); + assertThat(connectionDetails.getUsername()).isNull(); + assertThat(connectionDetails.getPassword()).isNull(); + assertThat(connectionDetails.getLocalDatacenter()).isEqualTo("testdc1"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/elasticsearch/ElasticsearchDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/elasticsearch/ElasticsearchDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..48f1c0d6b6d4 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/elasticsearch/ElasticsearchDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.elasticsearch; + +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails.Node; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails.Node.Protocol; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link ElasticsearchDockerComposeConnectionDetailsFactory}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + * @author Scott Frederick + */ +class ElasticsearchDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "elasticsearch-compose.yaml", image = TestImage.ELASTICSEARCH_8) + void runCreatesConnectionDetails(ElasticsearchConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + @DockerComposeTest(composeFile = "elasticsearch-bitnami-compose.yaml", image = TestImage.BITNAMI_ELASTICSEARCH) + void runWithBitnamiImageCreatesConnectionDetails(ElasticsearchConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + private void assertConnectionDetails(ElasticsearchConnectionDetails connectionDetails) { + assertThat(connectionDetails.getUsername()).isEqualTo("elastic"); + assertThat(connectionDetails.getPassword()).isEqualTo("secret"); + assertThat(connectionDetails.getPathPrefix()).isNull(); + assertThat(connectionDetails.getNodes()).hasSize(1); + Node node = connectionDetails.getNodes().get(0); + assertThat(node.hostname()).isNotNull(); + assertThat(node.port()).isGreaterThan(0); + assertThat(node.protocol()).isEqualTo(Protocol.HTTP); + assertThat(node.username()).isEqualTo("elastic"); + assertThat(node.password()).isEqualTo("secret"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/flyway/JdbcAdaptingFlywayConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/flyway/JdbcAdaptingFlywayConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..c2059f3c6439 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/flyway/JdbcAdaptingFlywayConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.flyway; + +import org.springframework.boot.autoconfigure.flyway.FlywayConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link JdbcAdaptingFlywayConnectionDetailsFactory}. + * + * @author Andy Wilkinson + */ +class JdbcAdaptingFlywayConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "flyway-compose.yaml", image = TestImage.POSTGRESQL) + void runCreatesConnectionDetails(FlywayConnectionDetails connectionDetails) { + assertThat(connectionDetails.getUsername()).isEqualTo("myuser"); + assertThat(connectionDetails.getPassword()).isEqualTo("secret"); + assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:postgresql://").endsWith("/mydatabase"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/ldap/OpenLdapDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/ldap/OpenLdapDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..0060a11995fb --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/ldap/OpenLdapDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.ldap; + +import org.springframework.boot.autoconfigure.ldap.LdapConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link OpenLdapDockerComposeConnectionDetailsFactory}. + * + * @author Philipp Kessler + */ +class OpenLdapDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "ldap-compose.yaml", image = TestImage.OPEN_LDAP) + void runCreatesConnectionDetails(LdapConnectionDetails connectionDetails) { + assertThat(connectionDetails.getUsername()).isEqualTo("cn=admin,dc=ldap,dc=example,dc=org"); + assertThat(connectionDetails.getPassword()).isEqualTo("somepassword"); + assertThat(connectionDetails.getBase()).isEqualTo("dc=ldap,dc=example,dc=org"); + assertThat(connectionDetails.getUrls()).hasSize(1); + assertThat(connectionDetails.getUrls()[0]).startsWith("ldaps://"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/liquibase/JdbcAdaptingLiquibaseConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/liquibase/JdbcAdaptingLiquibaseConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..d112e578ab2a --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/liquibase/JdbcAdaptingLiquibaseConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.liquibase; + +import org.springframework.boot.autoconfigure.liquibase.LiquibaseConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link JdbcAdaptingLiquibaseConnectionDetailsFactory}. + * + * @author Andy Wilkinson + */ +class JdbcAdaptingLiquibaseConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "liquibase-compose.yaml", image = TestImage.POSTGRESQL) + void runCreatesConnectionDetails(LiquibaseConnectionDetails connectionDetails) { + assertThat(connectionDetails.getUsername()).isEqualTo("myuser"); + assertThat(connectionDetails.getPassword()).isEqualTo("secret"); + assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:postgresql://").endsWith("/mydatabase"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..70425eb30e77 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.mariadb; + +import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link MariaDbJdbcDockerComposeConnectionDetailsFactory}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + * @author Scott Frederick + */ +class MariaDbJdbcDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "mariadb-compose.yaml", image = TestImage.MARIADB) + void runCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + @DockerComposeTest(composeFile = "mariadb-bitnami-compose.yaml", image = TestImage.BITNAMI_MARIADB) + void runWithBitnamiImageCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + private void assertConnectionDetails(JdbcConnectionDetails connectionDetails) { + assertThat(connectionDetails.getUsername()).isEqualTo("myuser"); + assertThat(connectionDetails.getPassword()).isEqualTo("secret"); + assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:mariadb://").endsWith("/mydatabase"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..5e1c85488cb0 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.mariadb; + +import io.r2dbc.spi.ConnectionFactoryOptions; + +import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link MariaDbR2dbcDockerComposeConnectionDetailsFactory}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + * @author Scott Frederick + */ +class MariaDbR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "mariadb-compose.yaml", image = TestImage.MARIADB) + void runCreatesConnectionDetails(R2dbcConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + @DockerComposeTest(composeFile = "mariadb-bitnami-compose.yaml", image = TestImage.BITNAMI_MARIADB) + void runWithBitnamiImageCreatesConnectionDetails(R2dbcConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + private void assertConnectionDetails(R2dbcConnectionDetails connectionDetails) { + ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions(); + assertThat(connectionFactoryOptions.toString()).contains("database=mydatabase", "driver=mariadb", + "password=REDACTED", "user=myuser"); + assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.PASSWORD)).isEqualTo("secret"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/mongo/MongoDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/mongo/MongoDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..928637f6dfd5 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/mongo/MongoDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.mongo; + +import com.mongodb.ConnectionString; +import org.junit.jupiter.api.condition.OS; + +import org.springframework.boot.autoconfigure.mongo.MongoConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.boot.testsupport.junit.DisabledOnOs; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link MongoDockerComposeConnectionDetailsFactory}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + * @author Scott Frederick + */ +class MongoDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "mongo-compose.yaml", image = TestImage.MONGODB) + void runCreatesConnectionDetails(MongoConnectionDetails connectionDetails) { + assertConnectionDetailsWithDatabase(connectionDetails, "mydatabase"); + } + + @DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", disabledReason = "The image has no ARM support") + @DockerComposeTest(composeFile = "mongo-bitnami-compose.yaml", image = TestImage.BITNAMI_MONGODB) + void runWithBitnamiImageCreatesConnectionDetails(MongoConnectionDetails connectionDetails) { + assertConnectionDetailsWithDatabase(connectionDetails, "testdb"); + } + + private void assertConnectionDetailsWithDatabase(MongoConnectionDetails connectionDetails, String database) { + ConnectionString connectionString = connectionDetails.getConnectionString(); + assertThat(connectionString.getCredential().getUserName()).isEqualTo("root"); + assertThat(connectionString.getCredential().getPassword()).isEqualTo("secret".toCharArray()); + assertThat(connectionString.getCredential().getSource()).isEqualTo("admin"); + assertThat(connectionString.getDatabase()).isEqualTo(database); + assertThat(connectionDetails.getGridFs()).isNull(); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..fb8e3aa51382 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.mysql; + +import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link MySqlJdbcDockerComposeConnectionDetailsFactory}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + * @author Scott Frederick + */ +class MySqlJdbcDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "mysql-compose.yaml", image = TestImage.MYSQL) + void runCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + @DockerComposeTest(composeFile = "mysql-bitnami-compose.yaml", image = TestImage.BITNAMI_MYSQL) + void runWithBitnamiImageCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + private void assertConnectionDetails(JdbcConnectionDetails connectionDetails) { + assertThat(connectionDetails.getUsername()).isEqualTo("myuser"); + assertThat(connectionDetails.getPassword()).isEqualTo("secret"); + assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:mysql://").endsWith("/mydatabase"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..536d2759baf1 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.mysql; + +import io.r2dbc.spi.ConnectionFactoryOptions; + +import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link MySqlR2dbcDockerComposeConnectionDetailsFactory}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + */ +class MySqlR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "mysql-compose.yaml", image = TestImage.MYSQL) + void runCreatesConnectionDetails(R2dbcConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + @DockerComposeTest(composeFile = "mysql-bitnami-compose.yaml", image = TestImage.BITNAMI_MYSQL) + void runWithBitnamiImageCreatesConnectionDetails(R2dbcConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + private void assertConnectionDetails(R2dbcConnectionDetails connectionDetails) { + ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions(); + assertThat(connectionFactoryOptions.toString()).contains("database=mydatabase", "driver=mysql", + "password=REDACTED", "user=myuser"); + assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.PASSWORD)).isEqualTo("secret"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..6e86f9f22d8f --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.neo4j; + +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; + +import org.springframework.boot.autoconfigure.neo4j.Neo4jConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; + +/** + * Integration tests for {@link Neo4jDockerComposeConnectionDetailsFactory}. + * + * @author Andy Wilkinson + * @author Scott Frederick + */ +class Neo4jDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "neo4j-compose.yaml", image = TestImage.NEO4J) + void runCreatesConnectionDetailsThatCanAccessNeo4j(Neo4jConnectionDetails connectionDetails) { + assertConnectionDetailsWithPassword(connectionDetails, "secret"); + } + + @DockerComposeTest(composeFile = "neo4j-bitnami-compose.yaml", image = TestImage.BITNAMI_NEO4J) + void runWithBitnamiImageCreatesConnectionDetailsThatCanAccessNeo4j(Neo4jConnectionDetails connectionDetails) { + assertConnectionDetailsWithPassword(connectionDetails, "bitnami2"); + } + + private void assertConnectionDetailsWithPassword(Neo4jConnectionDetails connectionDetails, String password) { + assertThat(connectionDetails.getAuthToken()).isEqualTo(AuthTokens.basic("neo4j", password)); + try (Driver driver = GraphDatabase.driver(connectionDetails.getUri(), connectionDetails.getAuthToken())) { + assertThatNoException().isThrownBy(driver::verifyConnectivity); + } + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleFreeJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleFreeJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..1ce414201304 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleFreeJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.oracle; + +import java.sql.Driver; +import java.time.Duration; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.condition.OS; + +import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.jdbc.DatabaseDriver; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.boot.testsupport.junit.DisabledOnOs; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.util.ClassUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link OracleFreeJdbcDockerComposeConnectionDetailsFactory}. + * + * @author Andy Wilkinson + */ +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "The Oracle image has no ARM support") +class OracleFreeJdbcDockerComposeConnectionDetailsFactoryIntegrationTests { + + @SuppressWarnings("unchecked") + @DockerComposeTest(composeFile = "oracle-compose.yaml", image = TestImage.ORACLE_FREE) + void runCreatesConnectionDetailsThatCanBeUsedToAccessDatabase(JdbcConnectionDetails connectionDetails) + throws Exception { + assertThat(connectionDetails.getUsername()).isEqualTo("app_user"); + assertThat(connectionDetails.getPassword()).isEqualTo("app_user_secret"); + assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:oracle:thin:@").endsWith("/freepdb1"); + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(); + dataSource.setUrl(connectionDetails.getJdbcUrl()); + dataSource.setUsername(connectionDetails.getUsername()); + dataSource.setPassword(connectionDetails.getPassword()); + dataSource.setDriverClass((Class) ClassUtils.forName(connectionDetails.getDriverClassName(), + getClass().getClassLoader())); + Awaitility.await().atMost(Duration.ofMinutes(1)).ignoreExceptions().untilAsserted(() -> { + JdbcTemplate template = new JdbcTemplate(dataSource); + assertThat(template.queryForObject(DatabaseDriver.ORACLE.getValidationQuery(), String.class)) + .isEqualTo("Hello"); + }); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleFreeR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleFreeR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..3f9e80cf6559 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleFreeR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.oracle; + +import java.time.Duration; + +import io.r2dbc.spi.ConnectionFactories; +import io.r2dbc.spi.ConnectionFactoryOptions; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.condition.OS; + +import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.jdbc.DatabaseDriver; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.boot.testsupport.junit.DisabledOnOs; +import org.springframework.r2dbc.core.DatabaseClient; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link OracleFreeR2dbcDockerComposeConnectionDetailsFactory}. + * + * @author Andy Wilkinson + */ +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "The Oracle image has no ARM support") +class OracleFreeR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "oracle-compose.yaml", image = TestImage.ORACLE_FREE) + void runCreatesConnectionDetailsThatCanBeUsedToAccessDatabase(R2dbcConnectionDetails connectionDetails) { + ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions(); + assertThat(connectionFactoryOptions.toString()).contains("database=freepdb1", "driver=oracle", + "password=REDACTED", "user=app_user"); + assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.PASSWORD)) + .isEqualTo("app_user_secret"); + Awaitility.await().atMost(Duration.ofMinutes(1)).ignoreExceptions().untilAsserted(() -> { + Object result = DatabaseClient.create(ConnectionFactories.get(connectionFactoryOptions)) + .sql(DatabaseDriver.ORACLE.getValidationQuery()) + .map((row, metadata) -> row.get(0)) + .first() + .block(Duration.ofSeconds(30)); + assertThat(result).isEqualTo("Hello"); + }); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleXeJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleXeJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..f9e96d747129 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleXeJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.oracle; + +import java.sql.Driver; +import java.time.Duration; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.condition.OS; + +import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.jdbc.DatabaseDriver; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.boot.testsupport.junit.DisabledOnOs; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.util.ClassUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link OracleXeJdbcDockerComposeConnectionDetailsFactory}. + * + * @author Andy Wilkinson + */ +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "The Oracle image has no ARM support") +class OracleXeJdbcDockerComposeConnectionDetailsFactoryIntegrationTests { + + @SuppressWarnings("unchecked") + @DockerComposeTest(composeFile = "oracle-compose.yaml", image = TestImage.ORACLE_XE) + void runCreatesConnectionDetailsThatCanBeUsedToAccessDatabase(JdbcConnectionDetails connectionDetails) + throws Exception { + assertThat(connectionDetails.getUsername()).isEqualTo("app_user"); + assertThat(connectionDetails.getPassword()).isEqualTo("app_user_secret"); + assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:oracle:thin:@").endsWith("/xepdb1"); + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(); + dataSource.setUrl(connectionDetails.getJdbcUrl()); + dataSource.setUsername(connectionDetails.getUsername()); + dataSource.setPassword(connectionDetails.getPassword()); + dataSource.setDriverClass((Class) ClassUtils.forName(connectionDetails.getDriverClassName(), + getClass().getClassLoader())); + Awaitility.await().atMost(Duration.ofMinutes(1)).ignoreExceptions().untilAsserted(() -> { + JdbcTemplate template = new JdbcTemplate(dataSource); + assertThat(template.queryForObject(DatabaseDriver.ORACLE.getValidationQuery(), String.class)) + .isEqualTo("Hello"); + }); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleXeR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleXeR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..88a80c887b5d --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleXeR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.oracle; + +import java.time.Duration; + +import io.r2dbc.spi.ConnectionFactories; +import io.r2dbc.spi.ConnectionFactoryOptions; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.condition.OS; + +import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.jdbc.DatabaseDriver; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.boot.testsupport.junit.DisabledOnOs; +import org.springframework.r2dbc.core.DatabaseClient; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link OracleXeR2dbcDockerComposeConnectionDetailsFactory}. + * + * @author Andy Wilkinson + */ +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "The Oracle image has no ARM support") +class OracleXeR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "oracle-compose.yaml", image = TestImage.ORACLE_XE) + void runCreatesConnectionDetailsThatCanBeUsedToAccessDatabase(R2dbcConnectionDetails connectionDetails) { + ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions(); + assertThat(connectionFactoryOptions.toString()).contains("database=xepdb1", "driver=oracle", + "password=REDACTED", "user=app_user"); + assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.PASSWORD)) + .isEqualTo("app_user_secret"); + Awaitility.await().atMost(Duration.ofMinutes(1)).ignoreExceptions().untilAsserted(() -> { + Object result = DatabaseClient.create(ConnectionFactories.get(connectionFactoryOptions)) + .sql(DatabaseDriver.ORACLE.getValidationQuery()) + .map((row, metadata) -> row.get(0)) + .first() + .block(Duration.ofSeconds(30)); + assertThat(result).isEqualTo("Hello"); + }); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryMetricsDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryMetricsDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..3ec07184e9e9 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryMetricsDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.otlp; + +import org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for + * {@link OpenTelemetryMetricsDockerComposeConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +class OpenTelemetryMetricsDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "otlp-compose.yaml", image = TestImage.OPENTELEMETRY) + void runCreatesConnectionDetails(OtlpMetricsConnectionDetails connectionDetails) { + assertThat(connectionDetails.getUrl()).startsWith("http://").endsWith("/v1/metrics"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryTracingDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryTracingDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..97fc794c628b --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryTracingDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.otlp; + +import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for + * {@link OpenTelemetryTracingDockerComposeConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +class OpenTelemetryTracingDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "otlp-compose.yaml", image = TestImage.OPENTELEMETRY) + void runCreatesConnectionDetails(OtlpTracingConnectionDetails connectionDetails) { + assertThat(connectionDetails.getUrl()).startsWith("http://").endsWith("/v1/traces"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..be35ec0d5a08 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.postgres; + +import java.sql.Driver; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.jdbc.DatabaseDriver; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.util.ClassUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link PostgresJdbcDockerComposeConnectionDetailsFactory}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + * @author Scott Frederick + */ +class PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "postgres-compose.yaml", image = TestImage.POSTGRESQL) + void runCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + @DockerComposeTest(composeFile = "postgres-with-trust-host-auth-method-compose.yaml", image = TestImage.POSTGRESQL) + void runCreatesConnectionDetailsThatCanAccessDatabaseWhenHostAuthMethodIsTrust( + JdbcConnectionDetails connectionDetails) throws ClassNotFoundException { + assertThat(connectionDetails.getUsername()).isEqualTo("myuser"); + assertThat(connectionDetails.getPassword()).isNull(); + assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:postgresql://").endsWith("/mydatabase"); + checkDatabaseAccess(connectionDetails); + } + + @Test + @DockerComposeTest(composeFile = "postgres-bitnami-compose.yaml", image = TestImage.BITNAMI_POSTGRESQL) + void runWithBitnamiImageCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + private void assertConnectionDetails(JdbcConnectionDetails connectionDetails) { + assertThat(connectionDetails.getUsername()).isEqualTo("myuser"); + assertThat(connectionDetails.getPassword()).isEqualTo("secret"); + assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:postgresql://").endsWith("/mydatabase"); + } + + @SuppressWarnings("unchecked") + private void checkDatabaseAccess(JdbcConnectionDetails connectionDetails) throws ClassNotFoundException { + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(); + dataSource.setUrl(connectionDetails.getJdbcUrl()); + dataSource.setUsername(connectionDetails.getUsername()); + dataSource.setPassword(connectionDetails.getPassword()); + dataSource.setDriverClass((Class) ClassUtils.forName(connectionDetails.getDriverClassName(), + getClass().getClassLoader())); + JdbcTemplate template = new JdbcTemplate(dataSource); + assertThat(template.queryForObject(DatabaseDriver.POSTGRESQL.getValidationQuery(), Integer.class)).isEqualTo(1); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..dab0edc7cf3e --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.postgres; + +import java.time.Duration; + +import io.r2dbc.spi.ConnectionFactories; +import io.r2dbc.spi.ConnectionFactoryOptions; + +import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.jdbc.DatabaseDriver; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.r2dbc.core.DatabaseClient; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link PostgresR2dbcDockerComposeConnectionDetailsFactory}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + * @author Scott Frederick + */ +class PostgresR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "postgres-compose.yaml", image = TestImage.POSTGRESQL) + void runCreatesConnectionDetails(R2dbcConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + @DockerComposeTest(composeFile = "postgres-with-trust-host-auth-method-compose.yaml", image = TestImage.POSTGRESQL) + void runCreatesConnectionDetailsThatCanAccessDatabaseWhenHostAuthMethodIsTrust( + R2dbcConnectionDetails connectionDetails) { + ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions(); + assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.USER)).isEqualTo("myuser"); + assertThat(connectionFactoryOptions.getValue(ConnectionFactoryOptions.PASSWORD)).isNull(); + assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.DATABASE)) + .isEqualTo("mydatabase"); + checkDatabaseAccess(connectionDetails); + } + + @DockerComposeTest(composeFile = "postgres-bitnami-compose.yaml", image = TestImage.BITNAMI_POSTGRESQL) + void runWithBitnamiImageCreatesConnectionDetails(R2dbcConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + private void assertConnectionDetails(R2dbcConnectionDetails connectionDetails) { + ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions(); + assertThat(connectionFactoryOptions.toString()).contains("database=mydatabase", "driver=postgresql", + "password=REDACTED", "user=myuser"); + assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.PASSWORD)).isEqualTo("secret"); + } + + private void checkDatabaseAccess(R2dbcConnectionDetails connectionDetails) { + ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions(); + Object result = DatabaseClient.create(ConnectionFactories.get(connectionFactoryOptions)) + .sql(DatabaseDriver.POSTGRESQL.getValidationQuery()) + .map((row, metadata) -> row.get(0)) + .first() + .block(Duration.ofSeconds(30)); + assertThat(result).isEqualTo(1); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/pulsar/PulsarDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/pulsar/PulsarDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..bbecc6c772ed --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/pulsar/PulsarDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.pulsar; + +import org.springframework.boot.autoconfigure.pulsar.PulsarConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration test for {@link PulsarDockerComposeConnectionDetailsFactory}. + * + * @author Chris Bono + */ +class PulsarDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "pulsar-compose.yaml", image = TestImage.PULSAR) + void runCreatesConnectionDetails(PulsarConnectionDetails connectionDetails) { + assertThat(connectionDetails).isNotNull(); + assertThat(connectionDetails.getBrokerUrl()).matches("^pulsar://\\S+:\\d+"); + assertThat(connectionDetails.getAdminUrl()).matches("^http://\\S+:\\d+"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..b6d6e8e7a840 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.rabbit; + +import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails; +import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails.Address; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link RabbitDockerComposeConnectionDetailsFactory}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + * @author Scott Frederick + */ +class RabbitDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "rabbit-compose.yaml", image = TestImage.RABBITMQ) + void runCreatesConnectionDetails(RabbitConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + @DockerComposeTest(composeFile = "rabbit-bitnami-compose.yaml", image = TestImage.BITNAMI_RABBITMQ) + void runWithBitnamiImageCreatesConnectionDetails(RabbitConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + private void assertConnectionDetails(RabbitConnectionDetails connectionDetails) { + assertThat(connectionDetails.getUsername()).isEqualTo("myuser"); + assertThat(connectionDetails.getPassword()).isEqualTo("secret"); + assertThat(connectionDetails.getVirtualHost()).isEqualTo("/"); + assertThat(connectionDetails.getAddresses()).hasSize(1); + Address address = connectionDetails.getFirstAddress(); + assertThat(address.host()).isNotNull(); + assertThat(address.port()).isGreaterThan(0); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..8b6b3d46f5ac --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.redis; + +import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails; +import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails.Standalone; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration test for {@link RedisDockerComposeConnectionDetailsFactory}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + * @author Scott Frederick + * @author Eddú Meléndez + */ +class RedisDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "redis-compose.yaml", image = TestImage.REDIS) + void runCreatesConnectionDetails(RedisConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + @DockerComposeTest(composeFile = "redis-bitnami-compose.yaml", image = TestImage.BITNAMI_REDIS) + void runWithBitnamiImageCreatesConnectionDetails(RedisConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + @DockerComposeTest(composeFile = "redis-compose.yaml", image = TestImage.REDIS_STACK) + void runWithRedisStackCreatesConnectionDetails(RedisConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + @DockerComposeTest(composeFile = "redis-compose.yaml", image = TestImage.REDIS_STACK_SERVER) + void runWithRedisStackServerCreatesConnectionDetails(RedisConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + } + + private void assertConnectionDetails(RedisConnectionDetails connectionDetails) { + assertThat(connectionDetails.getUsername()).isNull(); + assertThat(connectionDetails.getPassword()).isNull(); + assertThat(connectionDetails.getCluster()).isNull(); + assertThat(connectionDetails.getSentinel()).isNull(); + Standalone standalone = connectionDetails.getStandalone(); + assertThat(standalone).isNotNull(); + assertThat(standalone.getDatabase()).isZero(); + assertThat(standalone.getPort()).isGreaterThan(0); + assertThat(standalone.getHost()).isNotNull(); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/sqlserver/SqlServerJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/sqlserver/SqlServerJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..51215181fbde --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/sqlserver/SqlServerJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.sqlserver; + +import java.sql.Driver; + +import org.junit.jupiter.api.condition.OS; + +import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.jdbc.DatabaseDriver; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.boot.testsupport.junit.DisabledOnOs; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.util.ClassUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link SqlServerJdbcDockerComposeConnectionDetailsFactory}. + * + * @author Andy Wilkinson + */ +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "The SQL server image has no ARM support") +class SqlServerJdbcDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "mssqlserver-compose.yaml", image = TestImage.SQL_SERVER) + void runCreatesConnectionDetailsThatCanBeUsedToAccessDatabase(JdbcConnectionDetails connectionDetails) + throws ClassNotFoundException, LinkageError { + assertThat(connectionDetails.getUsername()).isEqualTo("SA"); + assertThat(connectionDetails.getPassword()).isEqualTo("verYs3cret"); + assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:sqlserver://"); + checkDatabaseAccess(connectionDetails); + } + + @DockerComposeTest(composeFile = "mssqlserver-with-jdbc-parameters-compose.yaml", image = TestImage.SQL_SERVER) + void runWithJdbcParametersCreatesConnectionDetailsThatCanBeUsedToAccessDatabase( + JdbcConnectionDetails connectionDetails) throws ClassNotFoundException { + assertThat(connectionDetails.getUsername()).isEqualTo("SA"); + assertThat(connectionDetails.getPassword()).isEqualTo("verYs3cret"); + assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:sqlserver://") + .contains(";sendStringParametersAsUnicode=false;"); + checkDatabaseAccess(connectionDetails); + } + + @SuppressWarnings("unchecked") + private void checkDatabaseAccess(JdbcConnectionDetails connectionDetails) throws ClassNotFoundException { + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(); + dataSource.setUrl(connectionDetails.getJdbcUrl()); + dataSource.setUsername(connectionDetails.getUsername()); + dataSource.setPassword(connectionDetails.getPassword()); + dataSource.setDriverClass((Class) ClassUtils.forName(connectionDetails.getDriverClassName(), + getClass().getClassLoader())); + JdbcTemplate template = new JdbcTemplate(dataSource); + assertThat(template.queryForObject(DatabaseDriver.SQLSERVER.getValidationQuery(), Integer.class)).isEqualTo(1); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/sqlserver/SqlServerR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/sqlserver/SqlServerR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java similarity index 77% rename from spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/sqlserver/SqlServerR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java rename to spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/sqlserver/SqlServerR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java index 91bf322bd1a9..1e297c48b73e 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/sqlserver/SqlServerR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/sqlserver/SqlServerR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,35 +20,28 @@ import io.r2dbc.spi.ConnectionFactories; import io.r2dbc.spi.ConnectionFactoryOptions; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.OS; import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; import org.springframework.boot.jdbc.DatabaseDriver; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.boot.testsupport.junit.DisabledOnOs; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.r2dbc.core.DatabaseClient; import static org.assertj.core.api.Assertions.assertThat; /** - * Integration tests for {@link SqlServerR2dbcDockerComposeConnectionDetailsFactory} + * Integration tests for {@link SqlServerR2dbcDockerComposeConnectionDetailsFactory}. * * @author Andy Wilkinson */ @DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", disabledReason = "The SQL server image has no ARM support") -class SqlServerR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests - extends AbstractDockerComposeIntegrationTests { +class SqlServerR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests { - SqlServerR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests() { - super("mssqlserver-compose.yaml", DockerImageNames.sqlserver()); - } - - @Test - void runCreatesConnectionDetailsThatCanBeUsedToAccessDatabase() { - R2dbcConnectionDetails connectionDetails = run(R2dbcConnectionDetails.class); + @DockerComposeTest(composeFile = "mssqlserver-compose.yaml", image = TestImage.SQL_SERVER) + void runCreatesConnectionDetailsThatCanBeUsedToAccessDatabase(R2dbcConnectionDetails connectionDetails) { ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions(); assertThat(connectionFactoryOptions.toString()).contains("driver=mssql", "password=REDACTED", "user=SA"); assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.PASSWORD)) diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/test/DockerComposeTest.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/test/DockerComposeTest.java new file mode 100644 index 000000000000..23a013691d34 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/test/DockerComposeTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.test; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; +import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.boot.testsupport.process.DisabledIfProcessUnavailable; + +/** + * A {@link Test test} that exercises Spring Boot's Docker Compose support. + *

+ * Before the test is executed, a {@link SpringApplication} that is configured to use the + * specified Docker Compose file is started. Any bean that exists in the resulting + * application context can be injected as a parameter into the test method. Typically, + * this will be a {@link ConnectionDetails} implementation. + *

+ * Once the test has executed, the {@link SpringApplication} is tidied up such that the + * Docker Compose services are stopped and destroyed and the application context is + * closed. + * + * @author Andy Wilkinson + */ +@Test +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ExtendWith(DockerComposeTestExtension.class) +@DisabledIfDockerUnavailable +@DisabledIfProcessUnavailable({ "docker", "compose" }) +public @interface DockerComposeTest { + + /** + * The name of the compose file to use. Loaded as a classpath resource relative to the + * test class. The image name in the compose file can be parameterized using + * {image} and it will be replaced using the specified {@link #image} + * reference. + * @return the compose file + */ + String composeFile(); + + /** + * The Docker image reference. + * @return the Docker image reference + * @see TestImage + */ + TestImage image(); + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/test/DockerComposeTestExtension.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/test/DockerComposeTestExtension.java new file mode 100644 index 000000000000..c0ee2a7cf09b --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/test/DockerComposeTestExtension.java @@ -0,0 +1,148 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringApplicationShutdownHandlers; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import static org.assertj.core.api.Assertions.fail; + +/** + * {@link Extension} for {@link DockerComposeTest @DockerComposeTest}. + * + * @author Andy Wilkinson + */ +class DockerComposeTestExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback, ParameterResolver { + + private static final Namespace NAMESPACE = Namespace.create(DockerComposeTestExtension.class); + + private static final String STORE_KEY_COMPOSE_FILE = "compose-file"; + + private static final String STORE_KEY_APPLICATION_CONTEXT = "application-context"; + + @Override + public void beforeTestExecution(ExtensionContext context) throws Exception { + Path transformedComposeFile = prepareComposeFile(context); + Store store = context.getStore(NAMESPACE); + store.put(STORE_KEY_COMPOSE_FILE, transformedComposeFile); + try { + SpringApplication application = prepareApplication(transformedComposeFile); + store.put(STORE_KEY_APPLICATION_CONTEXT, application.run()); + } + catch (Exception ex) { + cleanUp(context); + throw ex; + } + } + + private Path prepareComposeFile(ExtensionContext context) { + DockerComposeTest dockerComposeTest = context.getRequiredTestMethod().getAnnotation(DockerComposeTest.class); + TestImage image = dockerComposeTest.image(); + Resource composeResource = new ClassPathResource(dockerComposeTest.composeFile(), + context.getRequiredTestClass()); + return transformedComposeFile(composeResource, image); + } + + private Path transformedComposeFile(Resource composeFileResource, TestImage image) { + try { + Path composeFile = composeFileResource.getFile().toPath(); + Path transformedComposeFile = Files.createTempFile("", "-" + composeFile.getFileName().toString()); + String transformedContent = Files.readString(composeFile).replace("{imageName}", image.toString()); + Files.writeString(transformedComposeFile, transformedContent); + return transformedComposeFile; + } + catch (IOException ex) { + fail("Error transforming Docker compose file '" + composeFileResource + "': " + ex.getMessage()); + } + return null; + } + + private SpringApplication prepareApplication(Path transformedComposeFile) { + SpringApplication application = new SpringApplication(Config.class); + Map properties = new LinkedHashMap<>(); + properties.put("spring.docker.compose.skip.in-tests", "false"); + properties.put("spring.docker.compose.file", transformedComposeFile); + properties.put("spring.docker.compose.stop.command", "down"); + application.setDefaultProperties(properties); + return application; + } + + @Override + public void afterTestExecution(ExtensionContext context) throws Exception { + cleanUp(context); + } + + private void cleanUp(ExtensionContext context) throws Exception { + Store store = context.getStore(NAMESPACE); + runShutdownHandlers(); + deleteComposeFile(store); + } + + private void runShutdownHandlers() { + SpringApplicationShutdownHandlers shutdownHandlers = SpringApplication.getShutdownHandlers(); + ((Runnable) shutdownHandlers).run(); + } + + private void deleteComposeFile(Store store) throws IOException { + Path composeFile = store.get(STORE_KEY_COMPOSE_FILE, Path.class); + if (composeFile != null) { + Files.delete(composeFile); + } + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return true; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + ConfigurableApplicationContext applicationContext = extensionContext.getStore(NAMESPACE) + .get(STORE_KEY_APPLICATION_CONTEXT, ConfigurableApplicationContext.class); + return (applicationContext != null) ? applicationContext.getBean(parameterContext.getParameter().getType()) + : null; + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/zipkin/ZipkinDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/zipkin/ZipkinDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..1c8540cffd4b --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/zipkin/ZipkinDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.zipkin; + +import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link ZipkinDockerComposeConnectionDetailsFactory}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + */ +class ZipkinDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "zipkin-compose.yaml", image = TestImage.ZIPKIN) + void runCreatesConnectionDetails(ZipkinConnectionDetails connectionDetails) { + assertThat(connectionDetails.getSpanEndpoint()).startsWith("http://").endsWith("/api/v2/spans"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/core/redis-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/core/redis-compose.yaml similarity index 100% rename from spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/core/redis-compose.yaml rename to spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/core/redis-compose.yaml diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/activemq/activemq-classic-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/activemq/activemq-classic-compose.yaml new file mode 100644 index 000000000000..2bdef98e5aa7 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/activemq/activemq-classic-compose.yaml @@ -0,0 +1,8 @@ +services: + activemq: + image: '{imageName}' + ports: + - '61616' + environment: + ACTIVEMQ_CONNECTION_USER: 'root' + ACTIVEMQ_CONNECTION_PASSWORD: 'secret' diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/activemq/activemq-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/activemq/activemq-compose.yaml new file mode 100644 index 000000000000..9ae6911655e9 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/activemq/activemq-compose.yaml @@ -0,0 +1,8 @@ +services: + activemq: + image: '{imageName}' + ports: + - '61616' + environment: + ACTIVEMQ_USERNAME: 'root' + ACTIVEMQ_PASSWORD: 'secret' diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/activemq/artemis-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/activemq/artemis-compose.yaml new file mode 100644 index 000000000000..c9ea82fbadd4 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/activemq/artemis-compose.yaml @@ -0,0 +1,8 @@ +services: + artemis: + image: '{imageName}' + ports: + - '61616' + environment: + ARTEMIS_USER: 'root' + ARTEMIS_PASSWORD: 'secret' diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/cassandra/cassandra-bitnami-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/cassandra/cassandra-bitnami-compose.yaml new file mode 100644 index 000000000000..fc9c3d6d8ff2 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/cassandra/cassandra-bitnami-compose.yaml @@ -0,0 +1,8 @@ +services: + cassandra: + image: '{imageName}' + ports: + - '9042' + environment: + - 'CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch' + - 'CASSANDRA_DATACENTER=testdc1' diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/cassandra/cassandra-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/cassandra/cassandra-compose.yaml similarity index 92% rename from spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/cassandra/cassandra-compose.yaml rename to spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/cassandra/cassandra-compose.yaml index b8d5ffd528e4..946fd4fd3590 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/cassandra/cassandra-compose.yaml +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/cassandra/cassandra-compose.yaml @@ -9,4 +9,4 @@ services: - 'HEAP_NEWSIZE=128M' - 'MAX_HEAP_SIZE=1024M' - 'CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch' - - 'CASSANDRA_DC=dc1' + - 'CASSANDRA_DC=testdc1' diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/elasticsearch/elasticsearch-bitnami-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/elasticsearch/elasticsearch-bitnami-compose.yaml new file mode 100644 index 000000000000..b68a393757fd --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/elasticsearch/elasticsearch-bitnami-compose.yaml @@ -0,0 +1,9 @@ +services: + elasticsearch: + image: '{imageName}' + environment: + - 'ELASTIC_PASSWORD=secret' + - 'ES_JAVA_OPTS=-Xmx1024m' + ports: + - '9200' + - '9300' diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/elasticsearch/elasticsearch-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/elasticsearch/elasticsearch-compose.yaml similarity index 100% rename from spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/elasticsearch/elasticsearch-compose.yaml rename to spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/elasticsearch/elasticsearch-compose.yaml diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/flyway/flyway-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/flyway/flyway-compose.yaml similarity index 100% rename from spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/flyway/flyway-compose.yaml rename to spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/flyway/flyway-compose.yaml diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/ldap/ldap-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/ldap/ldap-compose.yaml new file mode 100644 index 000000000000..a55e16be4358 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/ldap/ldap-compose.yaml @@ -0,0 +1,11 @@ +services: + ldap: + image: '{imageName}' + environment: + - 'LDAP_DOMAIN=ldap.example.org' + - 'LDAP_ADMIN_PASSWORD=somepassword' + - 'LDAP_TLS=true' + hostname: ldap + ports: + - "389" + - "636" diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/liquibase/liquibase-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/liquibase/liquibase-compose.yaml similarity index 100% rename from spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/liquibase/liquibase-compose.yaml rename to spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/liquibase/liquibase-compose.yaml diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/mariadb/mariadb-bitnami-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/mariadb/mariadb-bitnami-compose.yaml new file mode 100644 index 000000000000..64406055b950 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/mariadb/mariadb-bitnami-compose.yaml @@ -0,0 +1,10 @@ +services: + database: + image: '{imageName}' + ports: + - '3306' + environment: + - 'MARIADB_ROOT_PASSWORD=verysecret' + - 'MARIADB_USER=myuser' + - 'MARIADB_PASSWORD=secret' + - 'MARIADB_DATABASE=mydatabase' diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/mariadb/mariadb-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/mariadb/mariadb-compose.yaml similarity index 100% rename from spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/mariadb/mariadb-compose.yaml rename to spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/mariadb/mariadb-compose.yaml diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/mongo/mongo-bitnami-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/mongo/mongo-bitnami-compose.yaml new file mode 100644 index 000000000000..1b2f92a8585c --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/mongo/mongo-bitnami-compose.yaml @@ -0,0 +1,9 @@ +services: + mongo: + image: '{imageName}' + ports: + - '27017' + environment: + - 'MONGODB_ROOT_USERNAME=root' + - 'MONGODB_ROOT_PASSWORD=secret' + - 'MONGODB_DATABASE=testdb' diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/mongo/mongo-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/mongo/mongo-compose.yaml similarity index 100% rename from spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/mongo/mongo-compose.yaml rename to spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/mongo/mongo-compose.yaml diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/mysql/mysql-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/mysql/mysql-bitnami-compose.yaml similarity index 100% rename from spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/mysql/mysql-compose.yaml rename to spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/mysql/mysql-bitnami-compose.yaml diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/mysql/mysql-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/mysql/mysql-compose.yaml new file mode 100644 index 000000000000..b0340ed3ed48 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/mysql/mysql-compose.yaml @@ -0,0 +1,10 @@ +services: + database: + image: '{imageName}' + ports: + - '3306' + environment: + - 'MYSQL_ROOT_PASSWORD=verysecret' + - 'MYSQL_USER=myuser' + - 'MYSQL_PASSWORD=secret' + - 'MYSQL_DATABASE=mydatabase' diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/neo4j/neo4j-bitnami-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/neo4j/neo4j-bitnami-compose.yaml new file mode 100644 index 000000000000..f60e53329a7c --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/neo4j/neo4j-bitnami-compose.yaml @@ -0,0 +1,7 @@ +services: + neo4j: + image: 'bitnami/neo4j:5.16.0' + ports: + - '7687' + environment: + - 'NEO4J_PASSWORD=bitnami2' diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/neo4j/neo4j-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/neo4j/neo4j-compose.yaml new file mode 100644 index 000000000000..313cce779274 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/neo4j/neo4j-compose.yaml @@ -0,0 +1,8 @@ +services: + neo4j: + image: '{imageName}' + ports: + - '7687' + environment: + - 'NEO4J_AUTH=neo4j/secret' + diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/oracle/oracle-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/oracle/oracle-compose.yaml similarity index 100% rename from spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/oracle/oracle-compose.yaml rename to spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/oracle/oracle-compose.yaml diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/otlp/otlp-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/otlp/otlp-compose.yaml new file mode 100644 index 000000000000..258e73e333ee --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/otlp/otlp-compose.yaml @@ -0,0 +1,5 @@ +services: + otlp: + image: '{imageName}' + ports: + - '4318' diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/postgres/postgres-bitnami-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/postgres/postgres-bitnami-compose.yaml new file mode 100644 index 000000000000..cb34245ec140 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/postgres/postgres-bitnami-compose.yaml @@ -0,0 +1,9 @@ +services: + database: + image: '{imageName}' + ports: + - '5432' + environment: + - 'POSTGRESQL_USER=myuser' + - 'POSTGRESQL_DB=mydatabase' + - 'POSTGRESQL_PASSWORD=secret' diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/postgres/postgres-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/postgres/postgres-compose.yaml similarity index 100% rename from spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/postgres/postgres-compose.yaml rename to spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/postgres/postgres-compose.yaml diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/postgres/postgres-with-trust-host-auth-method-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/postgres/postgres-with-trust-host-auth-method-compose.yaml new file mode 100644 index 000000000000..7a9607dcdcb0 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/postgres/postgres-with-trust-host-auth-method-compose.yaml @@ -0,0 +1,9 @@ +services: + database: + image: '{imageName}' + ports: + - '5432' + environment: + - 'POSTGRES_USER=myuser' + - 'POSTGRES_DB=mydatabase' + - 'POSTGRES_HOST_AUTH_METHOD=trust' diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/pulsar/pulsar-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/pulsar/pulsar-compose.yaml new file mode 100644 index 000000000000..76cdd274f431 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/pulsar/pulsar-compose.yaml @@ -0,0 +1,9 @@ +services: + pulsar: + image: '{imageName}' + ports: + - '8080' + - '6650' + command: bin/pulsar standalone + healthcheck: + test: curl http://127.0.0.1:8080/admin/v2/namespaces/public/default diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/rabbit/rabbit-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/rabbit/rabbit-bitnami-compose.yaml similarity index 100% rename from spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/rabbit/rabbit-compose.yaml rename to spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/rabbit/rabbit-bitnami-compose.yaml diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/rabbit/rabbit-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/rabbit/rabbit-compose.yaml new file mode 100644 index 000000000000..1951fba4bb08 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/rabbit/rabbit-compose.yaml @@ -0,0 +1,8 @@ +services: + rabbitmq: + image: '{imageName}' + environment: + - 'RABBITMQ_DEFAULT_USER=myuser' + - 'RABBITMQ_DEFAULT_PASS=secret' + ports: + - '5672' diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/redis-bitnami-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/redis-bitnami-compose.yaml new file mode 100644 index 000000000000..c4d6aeb291f8 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/redis-bitnami-compose.yaml @@ -0,0 +1,7 @@ +services: + redis: + image: '{imageName}' + ports: + - '6379' + environment: + - 'ALLOW_EMPTY_PASSWORD=yes' diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/redis/redis-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/redis-compose.yaml similarity index 100% rename from spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/redis/redis-compose.yaml rename to spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/redis-compose.yaml diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/sqlserver/mssqlserver-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/sqlserver/mssqlserver-compose.yaml similarity index 100% rename from spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/sqlserver/mssqlserver-compose.yaml rename to spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/sqlserver/mssqlserver-compose.yaml diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/sqlserver/mssqlserver-with-jdbc-parameters-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/sqlserver/mssqlserver-with-jdbc-parameters-compose.yaml new file mode 100644 index 000000000000..76dd4998aeea --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/sqlserver/mssqlserver-with-jdbc-parameters-compose.yaml @@ -0,0 +1,11 @@ +services: + database: + image: '{imageName}' + ports: + - '1433' + environment: + - 'MSSQL_PID=express' + - 'MSSQL_SA_PASSWORD=verYs3cret' + - 'ACCEPT_EULA=yes' + labels: + org.springframework.boot.jdbc.parameters: sendStringParametersAsUnicode=false \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/zipkin/zipkin-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/zipkin/zipkin-compose.yaml similarity index 100% rename from spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/zipkin/zipkin-compose.yaml rename to spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/zipkin/zipkin-compose.yaml diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DefaultDockerCompose.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DefaultDockerCompose.java index ff13762364ff..e50340605b14 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DefaultDockerCompose.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DefaultDockerCompose.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,22 +48,42 @@ class DefaultDockerCompose implements DockerCompose { @Override public void up(LogLevel logLevel) { - this.cli.run(new DockerCliCommand.ComposeUp(logLevel)); + up(logLevel, Collections.emptyList()); + } + + @Override + public void up(LogLevel logLevel, List arguments) { + this.cli.run(new DockerCliCommand.ComposeUp(logLevel, arguments)); } @Override public void down(Duration timeout) { - this.cli.run(new DockerCliCommand.ComposeDown(timeout)); + down(timeout, Collections.emptyList()); + } + + @Override + public void down(Duration timeout, List arguments) { + this.cli.run(new DockerCliCommand.ComposeDown(timeout, arguments)); } @Override public void start(LogLevel logLevel) { - this.cli.run(new DockerCliCommand.ComposeStart(logLevel)); + start(logLevel, Collections.emptyList()); + } + + @Override + public void start(LogLevel logLevel, List arguments) { + this.cli.run(new DockerCliCommand.ComposeStart(logLevel, arguments)); } @Override public void stop(Duration timeout) { - this.cli.run(new DockerCliCommand.ComposeStop(timeout)); + stop(timeout, Collections.emptyList()); + } + + @Override + public void stop(Duration timeout, List arguments) { + this.cli.run(new DockerCliCommand.ComposeStop(timeout, arguments)); } @Override diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliCommand.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliCommand.java index feb2d8dd6aa8..e5a52215137f 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliCommand.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -161,8 +161,18 @@ static final class ComposePs extends DockerCliCommand { - ComposeUp(LogLevel logLevel) { - super(Type.DOCKER_COMPOSE, logLevel, Void.class, false, "up", "--no-color", "--detach", "--wait"); + ComposeUp(LogLevel logLevel, List arguments) { + super(Type.DOCKER_COMPOSE, logLevel, Void.class, false, getCommand(arguments)); + } + + private static String[] getCommand(List arguments) { + List result = new ArrayList<>(); + result.add("up"); + result.add("--no-color"); + result.add("--detach"); + result.add("--wait"); + result.addAll(arguments); + return result.toArray(String[]::new); } } @@ -172,8 +182,17 @@ static final class ComposeUp extends DockerCliCommand { */ static final class ComposeDown extends DockerCliCommand { - ComposeDown(Duration timeout) { - super(Type.DOCKER_COMPOSE, Void.class, false, "down", "--timeout", Long.toString(timeout.toSeconds())); + ComposeDown(Duration timeout, List arguments) { + super(Type.DOCKER_COMPOSE, Void.class, false, getCommand(timeout, arguments)); + } + + private static String[] getCommand(Duration timeout, List arguments) { + List command = new ArrayList<>(); + command.add("down"); + command.add("--timeout"); + command.add(Long.toString(timeout.toSeconds())); + command.addAll(arguments); + return command.toArray(String[]::new); } } @@ -183,8 +202,15 @@ static final class ComposeDown extends DockerCliCommand { */ static final class ComposeStart extends DockerCliCommand { - ComposeStart(LogLevel logLevel) { - super(Type.DOCKER_COMPOSE, logLevel, Void.class, false, "start"); + ComposeStart(LogLevel logLevel, List arguments) { + super(Type.DOCKER_COMPOSE, logLevel, Void.class, false, getCommand(arguments)); + } + + private static String[] getCommand(List arguments) { + List command = new ArrayList<>(); + command.add("start"); + command.addAll(arguments); + return command.toArray(String[]::new); } } @@ -194,8 +220,17 @@ static final class ComposeStart extends DockerCliCommand { */ static final class ComposeStop extends DockerCliCommand { - ComposeStop(Duration timeout) { - super(Type.DOCKER_COMPOSE, Void.class, false, "stop", "--timeout", Long.toString(timeout.toSeconds())); + ComposeStop(Duration timeout, List arguments) { + super(Type.DOCKER_COMPOSE, Void.class, false, getCommand(timeout, arguments)); + } + + private static String[] getCommand(Duration timeout, List arguments) { + List command = new ArrayList<>(); + command.add("stop"); + command.add("--timeout"); + command.add(Long.toString(timeout.toSeconds())); + command.addAll(arguments); + return command.toArray(String[]::new); } } diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCompose.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCompose.java index de27b8298c79..dc85888e9656 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCompose.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCompose.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,6 +44,15 @@ public interface DockerCompose { */ void up(LogLevel logLevel); + /** + * Run {@code docker compose up} to create and start services. Waits until all + * contains are started and healthy. + * @param logLevel the log level used to report progress + * @param arguments the arguments to pass to the up command + * @since 3.4.0 + */ + void up(LogLevel logLevel, List arguments); + /** * Run {@code docker compose down} to stop and remove any running services. * @param timeout the amount of time to wait or {@link #FORCE_STOP} to stop without @@ -51,6 +60,15 @@ public interface DockerCompose { */ void down(Duration timeout); + /** + * Run {@code docker compose down} to stop and remove any running services. + * @param timeout the amount of time to wait or {@link #FORCE_STOP} to stop without + * waiting. + * @param arguments the arguments to pass to the down command + * @since 3.4.0 + */ + void down(Duration timeout, List arguments); + /** * Run {@code docker compose start} to start services. Waits until all containers are * started and healthy. @@ -58,6 +76,15 @@ public interface DockerCompose { */ void start(LogLevel logLevel); + /** + * Run {@code docker compose start} to start services. Waits until all containers are + * started and healthy. + * @param logLevel the log level used to report progress + * @param arguments the arguments to pass to the start command + * @since 3.4.0 + */ + void start(LogLevel logLevel, List arguments); + /** * Run {@code docker compose stop} to stop any running services. * @param timeout the amount of time to wait or {@link #FORCE_STOP} to stop without @@ -65,6 +92,15 @@ public interface DockerCompose { */ void stop(Duration timeout); + /** + * Run {@code docker compose stop} to stop any running services. + * @param timeout the amount of time to wait or {@link #FORCE_STOP} to stop without + * waiting. + * @param arguments the arguments to pass to the stop command + * @since 3.4.0 + */ + void stop(Duration timeout, List arguments); + /** * Return if services have been defined in the {@link DockerComposeFile} for the * active profiles. diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManager.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManager.java index 7499cf19433a..a93d5d4b4e65 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManager.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ import org.springframework.boot.docker.compose.core.RunningService; import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Readiness.Wait; import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Start; +import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Start.Skip; import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Stop; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; @@ -119,19 +120,21 @@ void start() { Wait wait = this.properties.getReadiness().getWait(); List runningServices = dockerCompose.getRunningServices(); if (lifecycleManagement.shouldStart()) { - if (runningServices.isEmpty()) { - start.getCommand().applyTo(dockerCompose, start.getLogLevel()); + Skip skip = this.properties.getStart().getSkip(); + if (skip.shouldSkip(runningServices)) { + logger.info(skip.getLogMessage()); + } + else { + start.getCommand().applyTo(dockerCompose, start.getLogLevel(), start.getArguments()); runningServices = dockerCompose.getRunningServices(); if (wait == Wait.ONLY_IF_STARTED) { wait = Wait.ALWAYS; } if (lifecycleManagement.shouldStop()) { - this.shutdownHandlers.add(() -> stop.getCommand().applyTo(dockerCompose, stop.getTimeout())); + this.shutdownHandlers + .add(() -> stop.getCommand().applyTo(dockerCompose, stop.getTimeout(), stop.getArguments())); } } - else { - logger.info("There are already Docker Compose services running, skipping startup"); - } } List relevantServices = new ArrayList<>(runningServices); relevantServices.removeIf(this::isIgnored); diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeProperties.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeProperties.java index a25f9f7a2a89..e7042b780287 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeProperties.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,11 +18,14 @@ import java.io.File; import java.time.Duration; +import java.util.ArrayList; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.docker.compose.core.RunningService; import org.springframework.boot.logging.LogLevel; /** @@ -148,6 +151,16 @@ public static class Start { */ private LogLevel logLevel = LogLevel.INFO; + /** + * Whether to skip executing the start command. + */ + private Skip skip = Skip.IF_RUNNING; + + /** + * Arguments to pass to the start command. + */ + private final List arguments = new ArrayList<>(); + public StartCommand getCommand() { return this.command; } @@ -164,6 +177,55 @@ public void setLogLevel(LogLevel logLevel) { this.logLevel = logLevel; } + public Skip getSkip() { + return this.skip; + } + + public void setSkip(Skip skip) { + this.skip = skip; + } + + public List getArguments() { + return this.arguments; + } + + /** + * Start command skip mode. + */ + public enum Skip { + + /** + * Never skip start. + */ + NEVER { + @Override + boolean shouldSkip(List runningServices) { + return false; + } + }, + /** + * Skip start if there are already services running. + */ + IF_RUNNING { + @Override + boolean shouldSkip(List runningServices) { + return !runningServices.isEmpty(); + } + + @Override + String getLogMessage() { + return "There are already Docker Compose services running, skipping startup"; + } + }; + + abstract boolean shouldSkip(List runningServices); + + String getLogMessage() { + return ""; + } + + } + } /** @@ -181,6 +243,11 @@ public static class Stop { */ private Duration timeout = Duration.ofSeconds(10); + /** + * Arguments to pass to the stop command. + */ + private final List arguments = new ArrayList<>(); + public StopCommand getCommand() { return this.command; } @@ -197,6 +264,10 @@ public void setTimeout(Duration timeout) { this.timeout = timeout; } + public List getArguments() { + return this.arguments; + } + } /** diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/StartCommand.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/StartCommand.java index 6f3c86daa724..b33bb370290f 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/StartCommand.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/StartCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package org.springframework.boot.docker.compose.lifecycle; -import java.util.function.BiConsumer; +import java.util.List; import org.springframework.boot.docker.compose.core.DockerCompose; import org.springframework.boot.logging.LogLevel; @@ -34,21 +34,23 @@ public enum StartCommand { /** * Start using {@code docker compose up}. */ - UP(DockerCompose::up), + UP { + @Override + void applyTo(DockerCompose dockerCompose, LogLevel logLevel, List arguments) { + dockerCompose.up(logLevel, arguments); + } + }, /** * Start using {@code docker compose start}. */ - START(DockerCompose::start); - - private final BiConsumer action; - - StartCommand(BiConsumer action) { - this.action = action; - } - - void applyTo(DockerCompose dockerCompose, LogLevel logLevel) { - this.action.accept(dockerCompose, logLevel); - } + START { + @Override + void applyTo(DockerCompose dockerCompose, LogLevel logLevel, List arguments) { + dockerCompose.start(logLevel, arguments); + } + }; + + abstract void applyTo(DockerCompose dockerCompose, LogLevel logLevel, List arguments); } diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/StopCommand.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/StopCommand.java index 942bab7b13e9..839b1dc23904 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/StopCommand.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/StopCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ package org.springframework.boot.docker.compose.lifecycle; import java.time.Duration; -import java.util.function.BiConsumer; +import java.util.List; import org.springframework.boot.docker.compose.core.DockerCompose; @@ -34,21 +34,23 @@ public enum StopCommand { /** * Stop using {@code docker compose down}. */ - DOWN(DockerCompose::down), + DOWN { + @Override + void applyTo(DockerCompose dockerCompose, Duration timeout, List arguments) { + dockerCompose.down(timeout, arguments); + } + }, /** * Stop using {@code docker compose stop}. */ - STOP(DockerCompose::stop); - - private final BiConsumer action; - - StopCommand(BiConsumer action) { - this.action = action; - } - - void applyTo(DockerCompose dockerCompose, Duration timeout) { - this.action.accept(dockerCompose, timeout); - } + STOP { + @Override + void applyTo(DockerCompose dockerCompose, Duration timeout, List arguments) { + dockerCompose.stop(timeout, arguments); + } + }; + + abstract void applyTo(DockerCompose dockerCompose, Duration timeout, List arguments); } diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/ConnectionNamePredicate.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/ConnectionNamePredicate.java index 626c3262bdaf..1990f9b27134 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/ConnectionNamePredicate.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/ConnectionNamePredicate.java @@ -16,28 +16,33 @@ package org.springframework.boot.docker.compose.service.connection; +import java.util.Arrays; +import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Collectors; import org.springframework.boot.docker.compose.core.ImageReference; import org.springframework.boot.docker.compose.core.RunningService; +import org.springframework.util.Assert; /** - * {@link Predicate} that matches against connection names. + * {@link Predicate} that matches against connection name. * * @author Phillip Webb */ class ConnectionNamePredicate implements Predicate { - private final String required; + private final Set required; - ConnectionNamePredicate(String required) { - this.required = asCanonicalName(required); + ConnectionNamePredicate(String... required) { + Assert.notEmpty(required, "Required must not be empty"); + this.required = Arrays.stream(required).map(this::asCanonicalName).collect(Collectors.toSet()); } @Override public boolean test(DockerComposeConnectionSource source) { String actual = getActual(source.getRunningService()); - return this.required.equals(actual); + return this.required.contains(actual); } private String getActual(RunningService service) { diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeConnectionDetailsFactory.java index 6d29c8bb0247..302a3ba35817 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeConnectionDetailsFactory.java @@ -54,6 +54,16 @@ protected DockerComposeConnectionDetailsFactory(String connectionName, String... this(new ConnectionNamePredicate(connectionName), requiredClassNames); } + /** + * Create a new {@link DockerComposeConnectionDetailsFactory} instance. + * @param connectionNames the required connection name + * @param requiredClassNames the names of classes that must be present + * @since 3.2.0 + */ + protected DockerComposeConnectionDetailsFactory(String[] connectionNames, String... requiredClassNames) { + this(new ConnectionNamePredicate(connectionNames), requiredClassNames); + } + /** * Create a new {@link DockerComposeConnectionDetailsFactory} instance. * @param predicate a predicate used to check when a service is accepted diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQClassicDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQClassicDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..fa3603d1d00b --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQClassicDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.activemq; + +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQConnectionDetails; +import org.springframework.boot.docker.compose.core.RunningService; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; + +/** + * {@link DockerComposeConnectionDetailsFactory} to create + * {@link ActiveMQConnectionDetails} for an {@code activemq} service. + * + * @author Stephane Nicoll + * @author Eddú Meléndez + */ +class ActiveMQClassicDockerComposeConnectionDetailsFactory + extends DockerComposeConnectionDetailsFactory { + + private static final int ACTIVEMQ_PORT = 61616; + + protected ActiveMQClassicDockerComposeConnectionDetailsFactory() { + super("apache/activemq-classic"); + } + + @Override + protected ActiveMQConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new ActiveMQDockerComposeConnectionDetails(source.getRunningService()); + } + + /** + * {@link ActiveMQConnectionDetails} backed by an {@code activemq} + * {@link RunningService}. + */ + static class ActiveMQDockerComposeConnectionDetails extends DockerComposeConnectionDetails + implements ActiveMQConnectionDetails { + + private final ActiveMQClassicEnvironment environment; + + private final String brokerUrl; + + protected ActiveMQDockerComposeConnectionDetails(RunningService service) { + super(service); + this.environment = new ActiveMQClassicEnvironment(service.env()); + this.brokerUrl = "tcp://" + service.host() + ":" + service.ports().get(ACTIVEMQ_PORT); + } + + @Override + public String getBrokerUrl() { + return this.brokerUrl; + } + + @Override + public String getUser() { + return this.environment.getUser(); + } + + @Override + public String getPassword() { + return this.environment.getPassword(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQClassicEnvironment.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQClassicEnvironment.java new file mode 100644 index 000000000000..dffede29238e --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQClassicEnvironment.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.activemq; + +import java.util.Map; + +/** + * ActiveMQ environment details. + * + * @author Stephane Nicoll + * @author Eddú Meléndez + */ +class ActiveMQClassicEnvironment { + + private final String user; + + private final String password; + + ActiveMQClassicEnvironment(Map env) { + this.user = env.get("ACTIVEMQ_CONNECTION_USER"); + this.password = env.get("ACTIVEMQ_CONNECTION_PASSWORD"); + } + + String getUser() { + return this.user; + } + + String getPassword() { + return this.password; + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..0a8c42143856 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.activemq; + +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQConnectionDetails; +import org.springframework.boot.docker.compose.core.RunningService; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; + +/** + * {@link DockerComposeConnectionDetailsFactory} to create + * {@link ActiveMQConnectionDetails} for an {@code activemq} service. + * + * @author Stephane Nicoll + */ +class ActiveMQDockerComposeConnectionDetailsFactory + extends DockerComposeConnectionDetailsFactory { + + private static final int ACTIVEMQ_PORT = 61616; + + protected ActiveMQDockerComposeConnectionDetailsFactory() { + super("symptoma/activemq"); + } + + @Override + protected ActiveMQConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new ActiveMQDockerComposeConnectionDetails(source.getRunningService()); + } + + /** + * {@link ActiveMQConnectionDetails} backed by an {@code activemq} + * {@link RunningService}. + */ + static class ActiveMQDockerComposeConnectionDetails extends DockerComposeConnectionDetails + implements ActiveMQConnectionDetails { + + private final ActiveMQEnvironment environment; + + private final String brokerUrl; + + protected ActiveMQDockerComposeConnectionDetails(RunningService service) { + super(service); + this.environment = new ActiveMQEnvironment(service.env()); + this.brokerUrl = "tcp://" + service.host() + ":" + service.ports().get(ACTIVEMQ_PORT); + } + + @Override + public String getBrokerUrl() { + return this.brokerUrl; + } + + @Override + public String getUser() { + return this.environment.getUser(); + } + + @Override + public String getPassword() { + return this.environment.getPassword(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQEnvironment.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQEnvironment.java new file mode 100644 index 000000000000..742389e80a7e --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQEnvironment.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.activemq; + +import java.util.Map; + +/** + * ActiveMQ environment details. + * + * @author Stephane Nicoll + */ +class ActiveMQEnvironment { + + private final String user; + + private final String password; + + ActiveMQEnvironment(Map env) { + this.user = env.get("ACTIVEMQ_USERNAME"); + this.password = env.get("ACTIVEMQ_PASSWORD"); + } + + String getUser() { + return this.user; + } + + String getPassword() { + return this.password; + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..4995ef24ca41 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.activemq; + +import org.springframework.boot.autoconfigure.jms.artemis.ArtemisConnectionDetails; +import org.springframework.boot.autoconfigure.jms.artemis.ArtemisMode; +import org.springframework.boot.docker.compose.core.RunningService; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; + +/** + * {@link DockerComposeConnectionDetailsFactory} to create + * {@link ArtemisConnectionDetails} for an {@code artemis} service. + * + * @author Eddú Meléndez + * @author Moritz Halbritter + */ +class ArtemisDockerComposeConnectionDetailsFactory + extends DockerComposeConnectionDetailsFactory { + + private static final int ACTIVEMQ_PORT = 61616; + + protected ArtemisDockerComposeConnectionDetailsFactory() { + super("apache/activemq-artemis"); + } + + @Override + protected ArtemisConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new ArtemisDockerComposeConnectionDetails(source.getRunningService()); + } + + /** + * {@link ArtemisConnectionDetails} backed by a {@code artemis} + * {@link RunningService}. + */ + static class ArtemisDockerComposeConnectionDetails extends DockerComposeConnectionDetails + implements ArtemisConnectionDetails { + + private final ArtemisEnvironment environment; + + private final String brokerUrl; + + protected ArtemisDockerComposeConnectionDetails(RunningService service) { + super(service); + this.environment = new ArtemisEnvironment(service.env()); + this.brokerUrl = "tcp://" + service.host() + ":" + service.ports().get(ACTIVEMQ_PORT); + } + + @Override + public ArtemisMode getMode() { + return ArtemisMode.NATIVE; + } + + @Override + public String getBrokerUrl() { + return this.brokerUrl; + } + + @Override + public String getUser() { + return this.environment.getUser(); + } + + @Override + public String getPassword() { + return this.environment.getPassword(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisEnvironment.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisEnvironment.java new file mode 100644 index 000000000000..a44dc69aa6da --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisEnvironment.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.activemq; + +import java.util.Map; + +/** + * Artemis environment details. + * + * @author Eddú Meléndez + * @author Moritz Halbritter + */ +class ArtemisEnvironment { + + private final String user; + + private final String password; + + ArtemisEnvironment(Map env) { + this.user = env.get("ARTEMIS_USER"); + this.password = env.get("ARTEMIS_PASSWORD"); + } + + String getUser() { + return this.user; + } + + String getPassword() { + return this.password; + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/package-info.java new file mode 100644 index 000000000000..5cb2e75cf5b4 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for docker compose ActiveMQ service connections. + */ +package org.springframework.boot.docker.compose.service.connection.activemq; diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/cassandra/CassandraDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/cassandra/CassandraDockerComposeConnectionDetailsFactory.java index aa5d86d8b899..7065297869e6 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/cassandra/CassandraDockerComposeConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/cassandra/CassandraDockerComposeConnectionDetailsFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,10 +32,12 @@ class CassandraDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { + private static final String[] CASSANDRA_CONTAINER_NAMES = { "cassandra", "bitnami/cassandra" }; + private static final int CASSANDRA_PORT = 9042; CassandraDockerComposeConnectionDetailsFactory() { - super("cassandra"); + super(CASSANDRA_CONTAINER_NAMES); } @Override diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/cassandra/CassandraEnvironment.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/cassandra/CassandraEnvironment.java index 84ed5f970444..e49f7c40e680 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/cassandra/CassandraEnvironment.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/cassandra/CassandraEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ class CassandraEnvironment { private final String datacenter; CassandraEnvironment(Map env) { - this.datacenter = env.getOrDefault("CASSANDRA_DC", "datacenter1"); + this.datacenter = env.getOrDefault("CASSANDRA_DC", env.getOrDefault("CASSANDRA_DATACENTER", "datacenter1")); } String getDatacenter() { diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/elasticsearch/ElasticsearchDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/elasticsearch/ElasticsearchDockerComposeConnectionDetailsFactory.java index 1304bef2b2c6..e2e27630be9d 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/elasticsearch/ElasticsearchDockerComposeConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/elasticsearch/ElasticsearchDockerComposeConnectionDetailsFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,14 +31,17 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick */ class ElasticsearchDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { + private static final String[] ELASTICSEARCH_CONTAINER_NAMES = { "elasticsearch", "bitnami/elasticsearch" }; + private static final int ELASTICSEARCH_PORT = 9200; protected ElasticsearchDockerComposeConnectionDetailsFactory() { - super("elasticsearch"); + super(ELASTICSEARCH_CONTAINER_NAMES); } @Override diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/jdbc/JdbcUrlBuilder.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/jdbc/JdbcUrlBuilder.java index 144686bd321e..da4815943c4a 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/jdbc/JdbcUrlBuilder.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/jdbc/JdbcUrlBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,20 +68,33 @@ public String build(RunningService service, String database) { private String urlFor(RunningService service, String database) { Assert.notNull(service, "Service must not be null"); - String parameters = getParameters(service); StringBuilder url = new StringBuilder("jdbc:%s://%s:%d".formatted(this.driverProtocol, service.host(), service.ports().get(this.containerPort))); if (StringUtils.hasLength(database)) { url.append("/"); url.append(database); } - url.append(parameters); + String parameters = getParameters(service); + if (StringUtils.hasLength(parameters)) { + appendParameters(url, parameters); + } return url.toString(); } + /** + * Appends to the given {@code url} the given {@code parameters}. + *

+ * The default implementation appends a {@code ?} followed by the {@code parameters}. + * @param url the url + * @param parameters the parameters + * @since 3.2.7 + */ + protected void appendParameters(StringBuilder url, String parameters) { + url.append("?").append(parameters); + } + private String getParameters(RunningService service) { - String parameters = service.labels().get(PARAMETERS_LABEL); - return (StringUtils.hasLength(parameters)) ? "?" + parameters : ""; + return service.labels().get(PARAMETERS_LABEL); } } diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/ldap/OpenLdapDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/ldap/OpenLdapDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..b5674f152ff4 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/ldap/OpenLdapDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,101 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.ldap; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.boot.autoconfigure.ldap.LdapConnectionDetails; +import org.springframework.boot.docker.compose.core.RunningService; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; + +/** + * {@link DockerComposeConnectionDetailsFactory} to create {@link LdapConnectionDetails} + * for an {@code ldap} service. + * + * @author Philipp Kessler + */ +class OpenLdapDockerComposeConnectionDetailsFactory + extends DockerComposeConnectionDetailsFactory { + + protected OpenLdapDockerComposeConnectionDetailsFactory() { + super("osixia/openldap"); + } + + @Override + protected LdapConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new OpenLdapDockerComposeConnectionDetails(source.getRunningService()); + } + + /** + * {@link LdapConnectionDetails} backed by an {@code openldap} {@link RunningService}. + */ + static class OpenLdapDockerComposeConnectionDetails extends DockerComposeConnectionDetails + implements LdapConnectionDetails { + + private final String[] urls; + + private final String base; + + private final String username; + + private final String password; + + OpenLdapDockerComposeConnectionDetails(RunningService service) { + super(service); + Map env = service.env(); + boolean usesTls = Boolean.parseBoolean(env.getOrDefault("LDAP_TLS", "true")); + String ldapPort = usesTls ? env.getOrDefault("LDAPS_PORT", "636") : env.getOrDefault("LDAP_PORT", "389"); + this.urls = new String[] { "%s://%s:%d".formatted(usesTls ? "ldaps" : "ldap", service.host(), + service.ports().get(Integer.parseInt(ldapPort))) }; + if (env.containsKey("LDAP_BASE_DN")) { + this.base = env.get("LDAP_BASE_DN"); + } + else { + this.base = Arrays.stream(env.getOrDefault("LDAP_DOMAIN", "example.org").split("\\.")) + .map("dc=%s"::formatted) + .collect(Collectors.joining(",")); + } + this.password = env.getOrDefault("LDAP_ADMIN_PASSWORD", "admin"); + this.username = "cn=admin,%s".formatted(this.base); + } + + @Override + public String[] getUrls() { + return this.urls; + } + + @Override + public String getBase() { + return this.base; + } + + @Override + public String getUsername() { + return this.username; + } + + @Override + public String getPassword() { + return this.password; + } + + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/ldap/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/ldap/package-info.java new file mode 100644 index 000000000000..489148d2777e --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/ldap/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for Docker Compose LDAP service connections. + */ +package org.springframework.boot.docker.compose.service.connection.ldap; diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbEnvironment.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbEnvironment.java index 388d77d95281..7da0a5e71630 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbEnvironment.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick */ class MariaDbEnvironment { @@ -52,7 +53,7 @@ private String extractPassword(Map env) { Assert.state(!env.containsKey("MYSQL_RANDOM_ROOT_PASSWORD"), "MYSQL_RANDOM_ROOT_PASSWORD is not supported"); Assert.state(!env.containsKey("MARIADB_ROOT_PASSWORD_HASH"), "MARIADB_ROOT_PASSWORD_HASH is not supported"); boolean allowEmpty = env.containsKey("MARIADB_ALLOW_EMPTY_PASSWORD") - || env.containsKey("MYSQL_ALLOW_EMPTY_PASSWORD"); + || env.containsKey("MYSQL_ALLOW_EMPTY_PASSWORD") || env.containsKey("ALLOW_EMPTY_PASSWORD"); String password = env.get("MARIADB_PASSWORD"); password = (password != null) ? password : env.get("MYSQL_PASSWORD"); password = (password != null) ? password : env.get("MARIADB_ROOT_PASSWORD"); diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbJdbcDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbJdbcDockerComposeConnectionDetailsFactory.java index f5c2e85a5511..c0759a02f628 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbJdbcDockerComposeConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbJdbcDockerComposeConnectionDetailsFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,12 +29,15 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick */ class MariaDbJdbcDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { + private static final String[] MARIADB_CONTAINER_NAMES = { "mariadb", "bitnami/mariadb" }; + protected MariaDbJdbcDockerComposeConnectionDetailsFactory() { - super("mariadb"); + super(MARIADB_CONTAINER_NAMES); } @Override diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbR2dbcDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbR2dbcDockerComposeConnectionDetailsFactory.java index 06b2f1a74245..75f22d156733 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbR2dbcDockerComposeConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbR2dbcDockerComposeConnectionDetailsFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,12 +31,15 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick */ class MariaDbR2dbcDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { + private static final String[] MARIADB_CONTAINER_NAMES = { "mariadb", "bitnami/mariadb" }; + MariaDbR2dbcDockerComposeConnectionDetailsFactory() { - super("mariadb", "io.r2dbc.spi.ConnectionFactoryOptions"); + super(MARIADB_CONTAINER_NAMES, "io.r2dbc.spi.ConnectionFactoryOptions"); } @Override diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mongo/MongoDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mongo/MongoDockerComposeConnectionDetailsFactory.java index 34e748c143e7..4fdba16f69f2 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mongo/MongoDockerComposeConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mongo/MongoDockerComposeConnectionDetailsFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,10 +34,12 @@ */ class MongoDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { + private static final String[] MONGODB_CONTAINER_NAMES = { "mongo", "bitnami/mongodb" }; + private static final int MONGODB_PORT = 27017; protected MongoDockerComposeConnectionDetailsFactory() { - super("mongo", "com.mongodb.ConnectionString"); + super(MONGODB_CONTAINER_NAMES, "com.mongodb.ConnectionString"); } @Override diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mongo/MongoEnvironment.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mongo/MongoEnvironment.java index 9b881fdab6ca..1129d09cb11b 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mongo/MongoEnvironment.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mongo/MongoEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick */ class MongoEnvironment { @@ -40,9 +41,9 @@ class MongoEnvironment { "MONGO_INITDB_ROOT_USERNAME_FILE is not supported"); Assert.state(!env.containsKey("MONGO_INITDB_ROOT_PASSWORD_FILE"), "MONGO_INITDB_ROOT_PASSWORD_FILE is not supported"); - this.username = env.get("MONGO_INITDB_ROOT_USERNAME"); - this.password = env.get("MONGO_INITDB_ROOT_PASSWORD"); - this.database = env.get("MONGO_INITDB_DATABASE"); + this.username = env.getOrDefault("MONGO_INITDB_ROOT_USERNAME", env.get("MONGODB_ROOT_USERNAME")); + this.password = env.getOrDefault("MONGO_INITDB_ROOT_PASSWORD", env.get("MONGODB_ROOT_PASSWORD")); + this.database = env.getOrDefault("MONGO_INITDB_DATABASE", env.get("MONGODB_DATABASE")); } String getUsername() { diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlEnvironment.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlEnvironment.java index ceb8afe5aa0d..d0c7fe4450b1 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlEnvironment.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick */ class MySqlEnvironment { @@ -44,7 +45,7 @@ class MySqlEnvironment { private String extractPassword(Map env) { Assert.state(!env.containsKey("MYSQL_RANDOM_ROOT_PASSWORD"), "MYSQL_RANDOM_ROOT_PASSWORD is not supported"); - boolean allowEmpty = env.containsKey("MYSQL_ALLOW_EMPTY_PASSWORD"); + boolean allowEmpty = env.containsKey("MYSQL_ALLOW_EMPTY_PASSWORD") || env.containsKey("ALLOW_EMPTY_PASSWORD"); String password = env.get("MYSQL_PASSWORD"); password = (password != null) ? password : env.get("MYSQL_ROOT_PASSWORD"); Assert.state(StringUtils.hasLength(password) || allowEmpty, "No MySQL password found"); diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlJdbcDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlJdbcDockerComposeConnectionDetailsFactory.java index 5977911879d8..d2fb07bf449d 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlJdbcDockerComposeConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlJdbcDockerComposeConnectionDetailsFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,12 +29,15 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick */ class MySqlJdbcDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { + private static final String[] MYSQL_CONTAINER_NAMES = { "mysql", "bitnami/mysql" }; + protected MySqlJdbcDockerComposeConnectionDetailsFactory() { - super("mysql"); + super(MYSQL_CONTAINER_NAMES); } @Override diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlR2dbcDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlR2dbcDockerComposeConnectionDetailsFactory.java index 6869007a6e84..1b25705b4902 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlR2dbcDockerComposeConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlR2dbcDockerComposeConnectionDetailsFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,12 +31,15 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick */ class MySqlR2dbcDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { + private static final String[] MYSQL_CONTAINER_NAMES = { "mysql", "bitnami/mysql" }; + MySqlR2dbcDockerComposeConnectionDetailsFactory() { - super("mysql", "io.r2dbc.spi.ConnectionFactoryOptions"); + super(MYSQL_CONTAINER_NAMES, "io.r2dbc.spi.ConnectionFactoryOptions"); } @Override diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..5b58d6d1d0df --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.neo4j; + +import java.net.URI; + +import org.neo4j.driver.AuthToken; + +import org.springframework.boot.autoconfigure.neo4j.Neo4jConnectionDetails; +import org.springframework.boot.docker.compose.core.RunningService; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; + +/** + * {@link DockerComposeConnectionDetailsFactory} to create {@link Neo4jConnectionDetails} + * for a {@code Neo4j} service. + * + * @author Andy Wilkinson + * @author Scott Frederick + */ +class Neo4jDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { + + private static final String[] NEO4J_CONTAINER_NAMES = { "neo4j", "bitnami/neo4j" }; + + Neo4jDockerComposeConnectionDetailsFactory() { + super(NEO4J_CONTAINER_NAMES); + } + + @Override + protected Neo4jConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new Neo4jDockerComposeConnectionDetails(source.getRunningService()); + } + + /** + * {@link Neo4jConnectionDetails} backed by a {@code Neo4j} {@link RunningService}. + */ + static class Neo4jDockerComposeConnectionDetails extends DockerComposeConnectionDetails + implements Neo4jConnectionDetails { + + private static final int BOLT_PORT = 7687; + + private final AuthToken authToken; + + private final URI uri; + + Neo4jDockerComposeConnectionDetails(RunningService service) { + super(service); + Neo4jEnvironment neo4jEnvironment = new Neo4jEnvironment(service.env()); + this.authToken = neo4jEnvironment.getAuthToken(); + this.uri = URI.create("neo4j://%s:%d".formatted(service.host(), service.ports().get(BOLT_PORT))); + } + + @Override + public URI getUri() { + return this.uri; + } + + @Override + public AuthToken getAuthToken() { + return this.authToken; + } + + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jEnvironment.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jEnvironment.java new file mode 100644 index 000000000000..eaad04475acb --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jEnvironment.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.neo4j; + +import java.util.Map; + +import org.neo4j.driver.AuthToken; +import org.neo4j.driver.AuthTokens; + +/** + * Neo4j environment details. + * + * @author Andy Wilkinson + * @author Scott Frederick + */ +class Neo4jEnvironment { + + private final AuthToken authToken; + + Neo4jEnvironment(Map env) { + AuthToken authToken = parse(env.get("NEO4J_AUTH")); + if (authToken == null && env.containsKey("NEO4J_PASSWORD")) { + authToken = parse("neo4j/" + env.get("NEO4J_PASSWORD")); + } + this.authToken = authToken; + } + + private AuthToken parse(String neo4jAuth) { + if (neo4jAuth == null) { + return null; + } + if ("none".equals(neo4jAuth)) { + return AuthTokens.none(); + } + if (neo4jAuth.startsWith("neo4j/")) { + return AuthTokens.basic("neo4j", neo4jAuth.substring(6)); + } + throw new IllegalStateException( + "Cannot extract auth token from NEO4J_AUTH environment variable with value '" + neo4jAuth + "'." + + " Value should be 'none' to disable authentication or start with 'neo4j/' to specify" + + " the neo4j user's password"); + } + + AuthToken getAuthToken() { + return this.authToken; + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/package-info.java new file mode 100644 index 000000000000..afea67c3cf5c --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for docker compose Neo4j service connections. + */ +package org.springframework.boot.docker.compose.service.connection.neo4j; diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleContainer.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleContainer.java new file mode 100644 index 000000000000..55776fd3a132 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleContainer.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.oracle; + +/** + * Enumeration of supported Oracle containers. + * + * @author Andy Wilkinson + */ +enum OracleContainer { + + FREE("gvenzl/oracle-free", "freepdb1"), + + XE("gvenzl/oracle-xe", "xepdb1"); + + private final String imageName; + + private final String defaultDatabase; + + OracleContainer(String imageName, String defaultDatabase) { + this.imageName = imageName; + this.defaultDatabase = defaultDatabase; + } + + String getImageName() { + return this.imageName; + } + + String getDefaultDatabase() { + return this.defaultDatabase; + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleEnvironment.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleEnvironment.java index 78011a7d6cfc..c38b595263c6 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleEnvironment.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleEnvironment.java @@ -34,10 +34,10 @@ class OracleEnvironment { private final String database; - OracleEnvironment(Map env) { + OracleEnvironment(Map env, String defaultDatabase) { this.username = env.getOrDefault("APP_USER", "system"); this.password = extractPassword(env); - this.database = env.getOrDefault("ORACLE_DATABASE", "xepdb1"); + this.database = env.getOrDefault("ORACLE_DATABASE", defaultDatabase); } private String extractPassword(Map env) { diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleFreeJdbcDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleFreeJdbcDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..85e017a47d74 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleFreeJdbcDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.oracle; + +import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; + +/** + * {@link DockerComposeConnectionDetailsFactory} to create {@link JdbcConnectionDetails} + * for an {@link OracleContainer#FREE} service. + * + * @author Andy Wilkinson + */ +class OracleFreeJdbcDockerComposeConnectionDetailsFactory extends OracleJdbcDockerComposeConnectionDetailsFactory { + + protected OracleFreeJdbcDockerComposeConnectionDetailsFactory() { + super(OracleContainer.FREE); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleFreeR2dbcDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleFreeR2dbcDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..3e4ae171b928 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleFreeR2dbcDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.oracle; + +import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; + +/** + * {@link DockerComposeConnectionDetailsFactory} to create {@link R2dbcConnectionDetails} + * for an {@link OracleContainer#FREE} service. + * + * @author Andy Wilkinson + */ +class OracleFreeR2dbcDockerComposeConnectionDetailsFactory extends OracleR2dbcDockerComposeConnectionDetailsFactory { + + protected OracleFreeR2dbcDockerComposeConnectionDetailsFactory() { + super(OracleContainer.FREE); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleJdbcDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleJdbcDockerComposeConnectionDetailsFactory.java index 9104a9ff267a..924195ab4d90 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleJdbcDockerComposeConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleJdbcDockerComposeConnectionDetailsFactory.java @@ -23,27 +23,30 @@ import org.springframework.util.StringUtils; /** - * {@link DockerComposeConnectionDetailsFactory} to create {@link JdbcConnectionDetails} - * for an {@code oracle-xe} service. + * Base class for a {@link DockerComposeConnectionDetailsFactory} to create + * {@link JdbcConnectionDetails} for an {@code oracle-free} or {@code oracle-xe} service. * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb */ -class OracleJdbcDockerComposeConnectionDetailsFactory +abstract class OracleJdbcDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { - protected OracleJdbcDockerComposeConnectionDetailsFactory() { - super("gvenzl/oracle-xe"); + private final String defaultDatabase; + + protected OracleJdbcDockerComposeConnectionDetailsFactory(OracleContainer container) { + super(container.getImageName()); + this.defaultDatabase = container.getDefaultDatabase(); } @Override protected JdbcConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { - return new OracleJdbcDockerComposeConnectionDetails(source.getRunningService()); + return new OracleJdbcDockerComposeConnectionDetails(source.getRunningService(), this.defaultDatabase); } /** - * {@link JdbcConnectionDetails} backed by an {@code oracle-xe} + * {@link JdbcConnectionDetails} backed by an {@code oracle-xe} or {@code oracle-free} * {@link RunningService}. */ static class OracleJdbcDockerComposeConnectionDetails extends DockerComposeConnectionDetails @@ -55,9 +58,9 @@ static class OracleJdbcDockerComposeConnectionDetails extends DockerComposeConne private final String jdbcUrl; - OracleJdbcDockerComposeConnectionDetails(RunningService service) { + OracleJdbcDockerComposeConnectionDetails(RunningService service, String defaultDatabase) { super(service); - this.environment = new OracleEnvironment(service.env()); + this.environment = new OracleEnvironment(service.env(), defaultDatabase); this.jdbcUrl = "jdbc:oracle:thin:@" + service.host() + ":" + service.ports().get(1521) + "/" + this.environment.getDatabase() + getParameters(service); } diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleR2dbcDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleR2dbcDockerComposeConnectionDetailsFactory.java index 5ebf98956e9b..9d54bee83f67 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleR2dbcDockerComposeConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleR2dbcDockerComposeConnectionDetailsFactory.java @@ -25,23 +25,26 @@ import org.springframework.boot.docker.compose.service.connection.r2dbc.ConnectionFactoryOptionsBuilder; /** - * {@link DockerComposeConnectionDetailsFactory} to create {@link R2dbcConnectionDetails} - * for an {@code oracle-xe} service. + * Base class for a {@link DockerComposeConnectionDetailsFactory} to create + * {@link R2dbcConnectionDetails} for an {@code oracle-free} or {@code oracle-xe} service. * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb */ -class OracleR2dbcDockerComposeConnectionDetailsFactory +abstract class OracleR2dbcDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { - OracleR2dbcDockerComposeConnectionDetailsFactory() { - super("gvenzl/oracle-xe", "io.r2dbc.spi.ConnectionFactoryOptions"); + private final String defaultDatabase; + + OracleR2dbcDockerComposeConnectionDetailsFactory(OracleContainer container) { + super(container.getImageName(), "io.r2dbc.spi.ConnectionFactoryOptions"); + this.defaultDatabase = container.getDefaultDatabase(); } @Override protected R2dbcConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { - return new OracleDbR2dbcDockerComposeConnectionDetails(source.getRunningService()); + return new OracleDbR2dbcDockerComposeConnectionDetails(source.getRunningService(), this.defaultDatabase); } /** @@ -56,9 +59,9 @@ static class OracleDbR2dbcDockerComposeConnectionDetails extends DockerComposeCo private final ConnectionFactoryOptions connectionFactoryOptions; - OracleDbR2dbcDockerComposeConnectionDetails(RunningService service) { + OracleDbR2dbcDockerComposeConnectionDetails(RunningService service, String defaultDatabase) { super(service); - OracleEnvironment environment = new OracleEnvironment(service.env()); + OracleEnvironment environment = new OracleEnvironment(service.env(), defaultDatabase); this.connectionFactoryOptions = connectionFactoryOptionsBuilder.build(service, environment.getDatabase(), environment.getUsername(), environment.getPassword()); } diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleXeJdbcDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleXeJdbcDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..75da136d567e --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleXeJdbcDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.oracle; + +import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; + +/** + * {@link DockerComposeConnectionDetailsFactory} to create {@link JdbcConnectionDetails} + * for an {@link OracleContainer#XE} service. + * + * @author Andy Wilkinson + */ +class OracleXeJdbcDockerComposeConnectionDetailsFactory extends OracleJdbcDockerComposeConnectionDetailsFactory { + + protected OracleXeJdbcDockerComposeConnectionDetailsFactory() { + super(OracleContainer.XE); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleXeR2dbcDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleXeR2dbcDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..f5b02edde660 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleXeR2dbcDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.oracle; + +import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; + +/** + * {@link DockerComposeConnectionDetailsFactory} to create {@link R2dbcConnectionDetails} + * for an {@link OracleContainer#XE} service. + * + * @author Andy Wilkinson + */ +class OracleXeR2dbcDockerComposeConnectionDetailsFactory extends OracleR2dbcDockerComposeConnectionDetailsFactory { + + protected OracleXeR2dbcDockerComposeConnectionDetailsFactory() { + super(OracleContainer.XE); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryMetricsDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryMetricsDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..49913297040c --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryMetricsDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.otlp; + +import org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsConnectionDetails; +import org.springframework.boot.docker.compose.core.RunningService; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; + +/** + * {@link DockerComposeConnectionDetailsFactory} to create + * {@link OtlpMetricsConnectionDetails} for an OTLP service. + * + * @author Eddú Meléndez + */ +class OpenTelemetryMetricsDockerComposeConnectionDetailsFactory + extends DockerComposeConnectionDetailsFactory { + + private static final int OTLP_PORT = 4318; + + OpenTelemetryMetricsDockerComposeConnectionDetailsFactory() { + super("otel/opentelemetry-collector-contrib", + "org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsExportAutoConfiguration"); + } + + @Override + protected OtlpMetricsConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new OpenTelemetryMetricsDockerComposeConnectionDetails(source.getRunningService()); + } + + private static final class OpenTelemetryMetricsDockerComposeConnectionDetails extends DockerComposeConnectionDetails + implements OtlpMetricsConnectionDetails { + + private final String host; + + private final int port; + + private OpenTelemetryMetricsDockerComposeConnectionDetails(RunningService source) { + super(source); + this.host = source.host(); + this.port = source.ports().get(OTLP_PORT); + } + + @Override + public String getUrl() { + return "http://%s:%d/v1/metrics".formatted(this.host, this.port); + } + + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryTracingDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryTracingDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..20e5b06b3daa --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryTracingDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.otlp; + +import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingConnectionDetails; +import org.springframework.boot.docker.compose.core.RunningService; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; + +/** + * {@link DockerComposeConnectionDetailsFactory} to create + * {@link OtlpTracingConnectionDetails} for an OTLP service. + * + * @author Eddú Meléndez + */ +class OpenTelemetryTracingDockerComposeConnectionDetailsFactory + extends DockerComposeConnectionDetailsFactory { + + private static final int OTLP_PORT = 4318; + + OpenTelemetryTracingDockerComposeConnectionDetailsFactory() { + super("otel/opentelemetry-collector-contrib", + "org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpAutoConfiguration"); + } + + @Override + protected OtlpTracingConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new OpenTelemetryTracingDockerComposeConnectionDetails(source.getRunningService()); + } + + private static final class OpenTelemetryTracingDockerComposeConnectionDetails extends DockerComposeConnectionDetails + implements OtlpTracingConnectionDetails { + + private final String host; + + private final int port; + + private OpenTelemetryTracingDockerComposeConnectionDetails(RunningService source) { + super(source); + this.host = source.host(); + this.port = source.ports().get(OTLP_PORT); + } + + @Override + public String getUrl() { + return "http://%s:%d/v1/traces".formatted(this.host, this.port); + } + + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/otlp/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/otlp/package-info.java new file mode 100644 index 000000000000..cbac91d2c639 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/otlp/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Support for docker compose OpenTelemetry service connections. + */ +package org.springframework.boot.docker.compose.service.connection.otlp; diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresEnvironment.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresEnvironment.java index 435a936e51c0..91649a79fe08 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresEnvironment.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,8 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick + * @author Sidmar Theodoro */ class PostgresEnvironment { @@ -37,17 +39,25 @@ class PostgresEnvironment { private final String database; PostgresEnvironment(Map env) { - this.username = env.getOrDefault("POSTGRES_USER", "postgres"); + this.username = env.getOrDefault("POSTGRES_USER", env.getOrDefault("POSTGRESQL_USER", "postgres")); this.password = extractPassword(env); - this.database = env.getOrDefault("POSTGRES_DB", this.username); + this.database = env.getOrDefault("POSTGRES_DB", env.getOrDefault("POSTGRESQL_DB", this.username)); } private String extractPassword(Map env) { - String password = env.get("POSTGRES_PASSWORD"); - Assert.state(StringUtils.hasLength(password), "No POSTGRES_PASSWORD defined"); + if (hasTrustHostAuthMethod(env)) { + return null; + } + String password = env.getOrDefault("POSTGRES_PASSWORD", env.get("POSTGRESQL_PASSWORD")); + Assert.state(StringUtils.hasLength(password), "PostgreSQL password must be provided"); return password; } + private Boolean hasTrustHostAuthMethod(Map env) { + String hostAuthMethod = env.get("POSTGRES_HOST_AUTH_METHOD"); + return "trust".equals(hostAuthMethod); + } + String getUsername() { return this.username; } diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresJdbcDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresJdbcDockerComposeConnectionDetailsFactory.java index 2330a3dc81cf..3f4ad8653d26 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresJdbcDockerComposeConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresJdbcDockerComposeConnectionDetailsFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,12 +29,15 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick */ class PostgresJdbcDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { + private static final String[] POSTGRES_CONTAINER_NAMES = { "postgres", "bitnami/postgresql" }; + protected PostgresJdbcDockerComposeConnectionDetailsFactory() { - super("postgres"); + super(POSTGRES_CONTAINER_NAMES); } @Override diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresR2dbcDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresR2dbcDockerComposeConnectionDetailsFactory.java index 835bd1fe63fa..cb1c66ded50b 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresR2dbcDockerComposeConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresR2dbcDockerComposeConnectionDetailsFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,12 +31,15 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick */ class PostgresR2dbcDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { + private static final String[] POSTGRES_CONTAINER_NAMES = { "postgres", "bitnami/postgresql" }; + PostgresR2dbcDockerComposeConnectionDetailsFactory() { - super("postgres", "io.r2dbc.spi.ConnectionFactoryOptions"); + super(POSTGRES_CONTAINER_NAMES, "io.r2dbc.spi.ConnectionFactoryOptions"); } @Override diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/pulsar/PulsarDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/pulsar/PulsarDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..0568a9811933 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/pulsar/PulsarDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.pulsar; + +import org.springframework.boot.autoconfigure.pulsar.PulsarConnectionDetails; +import org.springframework.boot.docker.compose.core.ConnectionPorts; +import org.springframework.boot.docker.compose.core.RunningService; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; + +/** + * {@link DockerComposeConnectionDetailsFactory} to create {@link PulsarConnectionDetails} + * for a {@code pulsar} service. + * + * @author Chris Bono + */ +class PulsarDockerComposeConnectionDetailsFactory + extends DockerComposeConnectionDetailsFactory { + + private static final int BROKER_PORT = 6650; + + private static final int ADMIN_PORT = 8080; + + PulsarDockerComposeConnectionDetailsFactory() { + super("apachepulsar/pulsar"); + } + + @Override + protected PulsarConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new PulsarDockerComposeConnectionDetails(source.getRunningService()); + } + + /** + * {@link PulsarConnectionDetails} backed by a {@code pulsar} {@link RunningService}. + */ + static class PulsarDockerComposeConnectionDetails extends DockerComposeConnectionDetails + implements PulsarConnectionDetails { + + private final String brokerUrl; + + private final String adminUrl; + + PulsarDockerComposeConnectionDetails(RunningService service) { + super(service); + ConnectionPorts ports = service.ports(); + this.brokerUrl = "pulsar://%s:%s".formatted(service.host(), ports.get(BROKER_PORT)); + this.adminUrl = "http://%s:%s".formatted(service.host(), ports.get(ADMIN_PORT)); + } + + @Override + public String getBrokerUrl() { + return this.brokerUrl; + } + + @Override + public String getAdminUrl() { + return this.adminUrl; + } + + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/pulsar/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/pulsar/package-info.java new file mode 100644 index 000000000000..7d8c4d1b1a56 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/pulsar/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for docker compose Pulsar service connections. + */ +package org.springframework.boot.docker.compose.service.connection.pulsar; diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitDockerComposeConnectionDetailsFactory.java index 7bc79b1d129f..c4e85853dc66 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitDockerComposeConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitDockerComposeConnectionDetailsFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,14 +30,17 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick */ class RabbitDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { + private static final String[] RABBITMQ_CONTAINER_NAMES = { "rabbitmq", "bitnami/rabbitmq" }; + private static final int RABBITMQ_PORT = 5672; protected RabbitDockerComposeConnectionDetailsFactory() { - super("rabbitmq"); + super(RABBITMQ_CONTAINER_NAMES); } @Override diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitEnvironment.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitEnvironment.java index 4cc471149f4e..7d6beb70f2e8 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitEnvironment.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick */ class RabbitEnvironment { @@ -32,8 +33,8 @@ class RabbitEnvironment { private final String password; RabbitEnvironment(Map env) { - this.username = env.getOrDefault("RABBITMQ_DEFAULT_USER", "guest"); - this.password = env.getOrDefault("RABBITMQ_DEFAULT_PASS", "guest"); + this.username = env.getOrDefault("RABBITMQ_DEFAULT_USER", env.getOrDefault("RABBITMQ_USERNAME", "guest")); + this.password = env.getOrDefault("RABBITMQ_DEFAULT_PASS", env.getOrDefault("RABBITMQ_PASSWORD", "guest")); } String getUsername() { diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactory.java index 9503bde010ac..5a15429d2f94 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,13 +28,18 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick + * @author Eddú Meléndez */ class RedisDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { + private static final String[] REDIS_CONTAINER_NAMES = { "redis", "bitnami/redis", "redis/redis-stack", + "redis/redis-stack-server" }; + private static final int REDIS_PORT = 6379; RedisDockerComposeConnectionDetailsFactory() { - super("redis"); + super(REDIS_CONTAINER_NAMES); } @Override diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/sqlserver/SqlServerJdbcDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/sqlserver/SqlServerJdbcDockerComposeConnectionDetailsFactory.java index 90d68b909f28..c10f6a585734 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/sqlserver/SqlServerJdbcDockerComposeConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/sqlserver/SqlServerJdbcDockerComposeConnectionDetailsFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ protected JdbcConnectionDetails getDockerComposeConnectionDetails(DockerComposeC static class SqlServerJdbcDockerComposeConnectionDetails extends DockerComposeConnectionDetails implements JdbcConnectionDetails { - private static final JdbcUrlBuilder jdbcUrlBuilder = new JdbcUrlBuilder("sqlserver", 1433); + private static final JdbcUrlBuilder jdbcUrlBuilder = new SqlServerJdbcUrlBuilder("sqlserver", 1433); private final SqlServerEnvironment environment; @@ -56,7 +56,7 @@ static class SqlServerJdbcDockerComposeConnectionDetails extends DockerComposeCo SqlServerJdbcDockerComposeConnectionDetails(RunningService service) { super(service); this.environment = new SqlServerEnvironment(service.env()); - this.jdbcUrl = disableEncryptionIfNecessary(jdbcUrlBuilder.build(service, "")); + this.jdbcUrl = disableEncryptionIfNecessary(jdbcUrlBuilder.build(service)); } private String disableEncryptionIfNecessary(String jdbcUrl) { @@ -86,6 +86,19 @@ public String getJdbcUrl() { return this.jdbcUrl; } + private static final class SqlServerJdbcUrlBuilder extends JdbcUrlBuilder { + + private SqlServerJdbcUrlBuilder(String driverProtocol, int containerPort) { + super(driverProtocol, containerPort); + } + + @Override + protected void appendParameters(StringBuilder url, String parameters) { + url.append(";").append(parameters); + } + + } + } } diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 4ec0d8d247bb..3d2b4e373833 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -18,6 +18,10 @@ "name": "spring.docker.compose.start.log-level", "defaultValue": "info" }, + { + "name": "spring.docker.compose.start.skip", + "defaultValue": "if-running" + }, { "name": "spring.docker.compose.stop.command", "defaultValue": "stop" diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories index 7c6623fbe5c9..d5b70a5f7cc3 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories @@ -5,22 +5,31 @@ org.springframework.boot.docker.compose.service.connection.DockerComposeServiceC # Connection Details Factories org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\ +org.springframework.boot.docker.compose.service.connection.activemq.ActiveMQClassicDockerComposeConnectionDetailsFactory,\ +org.springframework.boot.docker.compose.service.connection.activemq.ActiveMQDockerComposeConnectionDetailsFactory,\ +org.springframework.boot.docker.compose.service.connection.activemq.ArtemisDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.cassandra.CassandraDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.elasticsearch.ElasticsearchDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.flyway.JdbcAdaptingFlywayConnectionDetailsFactory,\ +org.springframework.boot.docker.compose.service.connection.ldap.OpenLdapDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.liquibase.JdbcAdaptingLiquibaseConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.mariadb.MariaDbJdbcDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.mariadb.MariaDbR2dbcDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.mongo.MongoDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.mysql.MySqlJdbcDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.mysql.MySqlR2dbcDockerComposeConnectionDetailsFactory,\ -org.springframework.boot.docker.compose.service.connection.oracle.OracleJdbcDockerComposeConnectionDetailsFactory,\ -org.springframework.boot.docker.compose.service.connection.oracle.OracleR2dbcDockerComposeConnectionDetailsFactory,\ +org.springframework.boot.docker.compose.service.connection.neo4j.Neo4jDockerComposeConnectionDetailsFactory,\ +org.springframework.boot.docker.compose.service.connection.oracle.OracleFreeJdbcDockerComposeConnectionDetailsFactory,\ +org.springframework.boot.docker.compose.service.connection.oracle.OracleXeJdbcDockerComposeConnectionDetailsFactory,\ +org.springframework.boot.docker.compose.service.connection.oracle.OracleFreeR2dbcDockerComposeConnectionDetailsFactory,\ +org.springframework.boot.docker.compose.service.connection.oracle.OracleXeR2dbcDockerComposeConnectionDetailsFactory,\ +org.springframework.boot.docker.compose.service.connection.otlp.OpenTelemetryMetricsDockerComposeConnectionDetailsFactory,\ +org.springframework.boot.docker.compose.service.connection.otlp.OpenTelemetryTracingDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.postgres.PostgresJdbcDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.postgres.PostgresR2dbcDockerComposeConnectionDetailsFactory,\ +org.springframework.boot.docker.compose.service.connection.pulsar.PulsarDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.rabbit.RabbitDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.redis.RedisDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.sqlserver.SqlServerJdbcDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.sqlserver.SqlServerR2dbcDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.zipkin.ZipkinDockerComposeConnectionDetailsFactory - diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DefaultDockerComposeTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DefaultDockerComposeTests.java index cb1bbf13d219..a03ff984dc1f 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DefaultDockerComposeTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DefaultDockerComposeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,31 +51,31 @@ class DefaultDockerComposeTests { @Test void upRunsUpCommand() { DefaultDockerCompose compose = new DefaultDockerCompose(this.cli, HOST); - compose.up(LogLevel.OFF); - then(this.cli).should().run(new DockerCliCommand.ComposeUp(LogLevel.OFF)); + compose.up(LogLevel.OFF, Collections.emptyList()); + then(this.cli).should().run(new DockerCliCommand.ComposeUp(LogLevel.OFF, Collections.emptyList())); } @Test void downRunsDownCommand() { DefaultDockerCompose compose = new DefaultDockerCompose(this.cli, HOST); Duration timeout = Duration.ofSeconds(1); - compose.down(timeout); - then(this.cli).should().run(new DockerCliCommand.ComposeDown(timeout)); + compose.down(timeout, Collections.emptyList()); + then(this.cli).should().run(new DockerCliCommand.ComposeDown(timeout, Collections.emptyList())); } @Test void startRunsStartCommand() { DefaultDockerCompose compose = new DefaultDockerCompose(this.cli, HOST); - compose.start(LogLevel.OFF); - then(this.cli).should().run(new DockerCliCommand.ComposeStart(LogLevel.OFF)); + compose.start(LogLevel.OFF, Collections.emptyList()); + then(this.cli).should().run(new DockerCliCommand.ComposeStart(LogLevel.OFF, Collections.emptyList())); } @Test void stopRunsStopCommand() { DefaultDockerCompose compose = new DefaultDockerCompose(this.cli, HOST); Duration timeout = Duration.ofSeconds(1); - compose.stop(timeout); - then(this.cli).should().run(new DockerCliCommand.ComposeStop(timeout)); + compose.stop(timeout, Collections.emptyList()); + then(this.cli).should().run(new DockerCliCommand.ComposeStop(timeout, Collections.emptyList())); } @Test diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DefaultRunningServiceTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DefaultRunningServiceTests.java index 1a866f82a3aa..e6dc5afb692f 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DefaultRunningServiceTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DefaultRunningServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -115,7 +115,7 @@ void toStringReturnsServiceName() { } private DefaultRunningService createRunningService(boolean psResponseHasImage) { - DockerHost host = DockerHost.get("192.168.1.1", () -> Collections.emptyList()); + DockerHost host = DockerHost.get("192.168.1.1", Collections::emptyList); String id = "123"; String name = "my-service"; String image = "redis"; diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerCliCommandTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerCliCommandTests.java index 4ed1d983bd17..6cafcb8bdf74 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerCliCommandTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerCliCommandTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,35 +68,37 @@ void composePs() { @Test void composeUp() { - DockerCliCommand command = new DockerCliCommand.ComposeUp(LogLevel.INFO); + DockerCliCommand command = new DockerCliCommand.ComposeUp(LogLevel.INFO, List.of("--renew-anon-volumes")); assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE); assertThat(command.getLogLevel()).isEqualTo(LogLevel.INFO); - assertThat(command.getCommand()).containsExactly("up", "--no-color", "--detach", "--wait"); + assertThat(command.getCommand()).containsExactly("up", "--no-color", "--detach", "--wait", + "--renew-anon-volumes"); assertThat(command.deserialize("[]")).isNull(); } @Test void composeDown() { - DockerCliCommand command = new DockerCliCommand.ComposeDown(Duration.ofSeconds(1)); + DockerCliCommand command = new DockerCliCommand.ComposeDown(Duration.ofSeconds(1), + List.of("--remove-orphans")); assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE); - assertThat(command.getCommand()).containsExactly("down", "--timeout", "1"); + assertThat(command.getCommand()).containsExactly("down", "--timeout", "1", "--remove-orphans"); assertThat(command.deserialize("[]")).isNull(); } @Test void composeStart() { - DockerCliCommand command = new DockerCliCommand.ComposeStart(LogLevel.INFO); + DockerCliCommand command = new DockerCliCommand.ComposeStart(LogLevel.INFO, List.of("--dry-run")); assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE); assertThat(command.getLogLevel()).isEqualTo(LogLevel.INFO); - assertThat(command.getCommand()).containsExactly("start"); + assertThat(command.getCommand()).containsExactly("start", "--dry-run"); assertThat(command.deserialize("[]")).isNull(); } @Test void composeStop() { - DockerCliCommand command = new DockerCliCommand.ComposeStop(Duration.ofSeconds(1)); + DockerCliCommand command = new DockerCliCommand.ComposeStop(Duration.ofSeconds(1), List.of("--dry-run")); assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE); - assertThat(command.getCommand()).containsExactly("stop", "--timeout", "1"); + assertThat(command.getCommand()).containsExactly("stop", "--timeout", "1", "--dry-run"); assertThat(command.deserialize("[]")).isNull(); } diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerComposeFileTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerComposeFileTests.java index 4d1692e247fd..02bab15eb46c 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerComposeFileTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerComposeFileTests.java @@ -106,7 +106,7 @@ void ofReturnsDockerComposeFile() throws Exception { FileCopyUtils.copy(new byte[0], file); DockerComposeFile composeFile = DockerComposeFile.of(file); assertThat(composeFile).isNotNull(); - assertThat(composeFile.toString()).isEqualTo(file.getCanonicalPath()); + assertThat(composeFile).hasToString(file.getCanonicalPath()); } @Test diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerHostTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerHostTests.java index b10feb3d7a32..9bd624622d43 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerHostTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerHostTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,7 +52,7 @@ class DockerHostTests { private static final Function NO_SYSTEM_ENV = (key) -> null; - private static final Supplier> NO_CONTEXT = () -> Collections.emptyList(); + private static final Supplier> NO_CONTEXT = Collections::emptyList; @Test void getWhenHasHost() { diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManagerTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManagerTests.java index 118841abf92f..7e7a933e84b2 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManagerTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,7 @@ import org.springframework.boot.docker.compose.core.DockerComposeFile; import org.springframework.boot.docker.compose.core.RunningService; import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Readiness.Wait; +import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Start.Skip; import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.context.ApplicationContext; @@ -181,10 +182,10 @@ void startWhenHasNoDefinedServicesDoesNothing() { this.lifecycleManager.start(); assertThat(listener.getEvent()).isNull(); then(this.dockerCompose).should().hasDefinedServices(); - then(this.dockerCompose).should(never()).up(any()); - then(this.dockerCompose).should(never()).start(any()); - then(this.dockerCompose).should(never()).down(any()); - then(this.dockerCompose).should(never()).stop(any()); + then(this.dockerCompose).should(never()).up(any(), any()); + then(this.dockerCompose).should(never()).start(any(), any()); + then(this.dockerCompose).should(never()).down(any(), any()); + then(this.dockerCompose).should(never()).stop(any(), any()); } @Test @@ -196,10 +197,10 @@ void startWhenLifecycleStartAndStopAndHasNoRunningServicesDoesUpAndStop() { this.lifecycleManager.start(); this.shutdownHandlers.run(); assertThat(listener.getEvent()).isNotNull(); - then(this.dockerCompose).should().up(any()); - then(this.dockerCompose).should(never()).start(any()); - then(this.dockerCompose).should().stop(any()); - then(this.dockerCompose).should(never()).down(any()); + then(this.dockerCompose).should().up(any(), any()); + then(this.dockerCompose).should(never()).start(any(), any()); + then(this.dockerCompose).should().stop(any(), any()); + then(this.dockerCompose).should(never()).down(any(), any()); } @Test @@ -211,10 +212,10 @@ void startWhenLifecycleStartAndStopAndHasRunningServicesDoesNothing() { this.lifecycleManager.start(); this.shutdownHandlers.run(); assertThat(listener.getEvent()).isNotNull(); - then(this.dockerCompose).should(never()).up(any()); - then(this.dockerCompose).should(never()).start(any()); - then(this.dockerCompose).should(never()).down(any()); - then(this.dockerCompose).should(never()).stop(any()); + then(this.dockerCompose).should(never()).up(any(), any()); + then(this.dockerCompose).should(never()).start(any(), any()); + then(this.dockerCompose).should(never()).down(any(), any()); + then(this.dockerCompose).should(never()).stop(any(), any()); } @Test @@ -226,10 +227,10 @@ void startWhenLifecycleNoneDoesNothing() { this.lifecycleManager.start(); this.shutdownHandlers.run(); assertThat(listener.getEvent()).isNotNull(); - then(this.dockerCompose).should(never()).up(any()); - then(this.dockerCompose).should(never()).start(any()); - then(this.dockerCompose).should(never()).down(any()); - then(this.dockerCompose).should(never()).stop(any()); + then(this.dockerCompose).should(never()).up(any(), any()); + then(this.dockerCompose).should(never()).start(any(), any()); + then(this.dockerCompose).should(never()).down(any(), any()); + then(this.dockerCompose).should(never()).stop(any(), any()); } @Test @@ -241,10 +242,10 @@ void startWhenLifecycleStartOnlyDoesOnlyStart() { this.lifecycleManager.start(); this.shutdownHandlers.run(); assertThat(listener.getEvent()).isNotNull(); - then(this.dockerCompose).should().up(any()); - then(this.dockerCompose).should(never()).start(any()); - then(this.dockerCompose).should(never()).down(any()); - then(this.dockerCompose).should(never()).stop(any()); + then(this.dockerCompose).should().up(any(), any()); + then(this.dockerCompose).should(never()).start(any(), any()); + then(this.dockerCompose).should(never()).down(any(), any()); + then(this.dockerCompose).should(never()).stop(any(), any()); this.shutdownHandlers.assertNoneAdded(); } @@ -258,10 +259,10 @@ void startWhenStartCommandStartDoesStartAndStop() { this.lifecycleManager.start(); this.shutdownHandlers.run(); assertThat(listener.getEvent()).isNotNull(); - then(this.dockerCompose).should(never()).up(any()); - then(this.dockerCompose).should().start(any()); - then(this.dockerCompose).should().stop(any()); - then(this.dockerCompose).should(never()).down(any()); + then(this.dockerCompose).should(never()).up(any(), any()); + then(this.dockerCompose).should().start(any(), any()); + then(this.dockerCompose).should().stop(any(), any()); + then(this.dockerCompose).should(never()).down(any(), any()); } @Test @@ -274,10 +275,10 @@ void startWhenStopCommandDownDoesStartAndDown() { this.lifecycleManager.start(); this.shutdownHandlers.run(); assertThat(listener.getEvent()).isNotNull(); - then(this.dockerCompose).should().up(any()); - then(this.dockerCompose).should(never()).start(any()); - then(this.dockerCompose).should(never()).stop(any()); - then(this.dockerCompose).should().down(any()); + then(this.dockerCompose).should().up(any(), any()); + then(this.dockerCompose).should(never()).start(any(), any()); + then(this.dockerCompose).should(never()).stop(any(), any()); + then(this.dockerCompose).should().down(any(), any()); } @Test @@ -291,7 +292,7 @@ void startWhenHasStopTimeoutUsesDuration() { this.lifecycleManager.start(); this.shutdownHandlers.run(); assertThat(listener.getEvent()).isNotNull(); - then(this.dockerCompose).should().stop(timeout); + then(this.dockerCompose).should().stop(timeout, Collections.emptyList()); } @Test @@ -384,6 +385,38 @@ void shouldNotLogIfThereAreNoServicesRunning(CapturedOutput output) { assertThat(output).doesNotContain("There are already Docker Compose services running, skipping startup"); } + @Test + void shouldStartIfSkipModeIsIfRunningAndNoServicesAreRunning() { + given(this.dockerCompose.hasDefinedServices()).willReturn(true); + this.properties.getStart().setSkip(Skip.IF_RUNNING); + this.lifecycleManager.start(); + then(this.dockerCompose).should().up(any(), any()); + } + + @Test + void shouldNotStartIfSkipModeIsIfRunningAndServicesAreAlreadyRunning() { + setUpRunningServices(); + this.properties.getStart().setSkip(Skip.IF_RUNNING); + this.lifecycleManager.start(); + then(this.dockerCompose).should(never()).up(any(), any()); + } + + @Test + void shouldStartIfSkipModeIsNeverAndNoServicesAreRunning() { + given(this.dockerCompose.hasDefinedServices()).willReturn(true); + this.properties.getStart().setSkip(Skip.NEVER); + this.lifecycleManager.start(); + then(this.dockerCompose).should().up(any(), any()); + } + + @Test + void shouldStartIfSkipModeIsNeverAndServicesAreAlreadyRunning() { + setUpRunningServices(); + this.properties.getStart().setSkip(Skip.NEVER); + this.lifecycleManager.start(); + then(this.dockerCompose).should().up(any(), any()); + } + private void setUpRunningServices() { setUpRunningServices(true); } diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/DockerComposePropertiesTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/DockerComposePropertiesTests.java index 6344d810b82a..b61f26a33dc9 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/DockerComposePropertiesTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/DockerComposePropertiesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,16 +18,21 @@ import java.io.File; import java.time.Duration; +import java.util.Collections; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; +import org.springframework.boot.docker.compose.core.RunningService; import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Readiness.Wait; +import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Start.Skip; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; /** * Tests for {@link DockerComposeProperties}. @@ -84,4 +89,16 @@ void getWhenPropertiesReturnsBound() { assertThat(properties.getReadiness().getTcp().getReadTimeout()).isEqualTo(Duration.ofMillis(500)); } + @Test + void skipModeNeverShouldNeverSkip() { + assertThat(Skip.NEVER.shouldSkip(Collections.emptyList())).isFalse(); + assertThat(Skip.NEVER.shouldSkip(List.of(mock(RunningService.class)))).isFalse(); + } + + @Test + void skipModeIfRunningShouldSkipWhenServicesAreRunning() { + assertThat(Skip.IF_RUNNING.shouldSkip(Collections.emptyList())).isFalse(); + assertThat(Skip.IF_RUNNING.shouldSkip(List.of(mock(RunningService.class)))).isTrue(); + } + } diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/StartCommandTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/StartCommandTests.java index e1c3dd20d5a0..b44599c79dbd 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/StartCommandTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/StartCommandTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,9 @@ package org.springframework.boot.docker.compose.lifecycle; +import java.util.Collections; + +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.docker.compose.core.DockerCompose; @@ -33,18 +36,23 @@ */ class StartCommandTests { - private DockerCompose dockerCompose = mock(DockerCompose.class); + private DockerCompose dockerCompose; + + @BeforeEach + void setUp() { + this.dockerCompose = mock(DockerCompose.class); + } @Test void applyToWhenUp() { - StartCommand.UP.applyTo(this.dockerCompose, LogLevel.INFO); - then(this.dockerCompose).should().up(LogLevel.INFO); + StartCommand.UP.applyTo(this.dockerCompose, LogLevel.INFO, Collections.emptyList()); + then(this.dockerCompose).should().up(LogLevel.INFO, Collections.emptyList()); } @Test void applyToWhenStart() { - StartCommand.START.applyTo(this.dockerCompose, LogLevel.INFO); - then(this.dockerCompose).should().start(LogLevel.INFO); + StartCommand.START.applyTo(this.dockerCompose, LogLevel.INFO, Collections.emptyList()); + then(this.dockerCompose).should().start(LogLevel.INFO, Collections.emptyList()); } } diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/StopCommandTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/StopCommandTests.java index 59f5c816943e..34fe5977d6b8 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/StopCommandTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/StopCommandTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,9 @@ package org.springframework.boot.docker.compose.lifecycle; import java.time.Duration; +import java.util.Collections; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.docker.compose.core.DockerCompose; @@ -34,20 +36,25 @@ */ class StopCommandTests { - private DockerCompose dockerCompose = mock(DockerCompose.class); + private DockerCompose dockerCompose; - private Duration duration = Duration.ofSeconds(10); + private final Duration duration = Duration.ofSeconds(10); + + @BeforeEach + void setUp() { + this.dockerCompose = mock(DockerCompose.class); + } @Test void applyToWhenDown() { - StopCommand.DOWN.applyTo(this.dockerCompose, this.duration); - then(this.dockerCompose).should().down(this.duration); + StopCommand.DOWN.applyTo(this.dockerCompose, this.duration, Collections.emptyList()); + then(this.dockerCompose).should().down(this.duration, Collections.emptyList()); } @Test void applyToWhenStart() { - StopCommand.STOP.applyTo(this.dockerCompose, this.duration); - then(this.dockerCompose).should().stop(this.duration); + StopCommand.STOP.applyTo(this.dockerCompose, this.duration, Collections.emptyList()); + then(this.dockerCompose).should().stop(this.duration, Collections.emptyList()); } } diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/TcpConnectServiceReadinessCheckTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/TcpConnectServiceReadinessCheckTests.java index 3e81a2cef3da..ccb6395d3620 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/TcpConnectServiceReadinessCheckTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/lifecycle/TcpConnectServiceReadinessCheckTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,14 +58,14 @@ void setup() { @Test void checkWhenServerWritesData() throws Exception { - withServer((socket) -> socket.getOutputStream().write('!'), (port) -> check(port)); + withServer((socket) -> socket.getOutputStream().write('!'), this::check); } @Test void checkWhenNoSocketOutput() throws Exception { // Simulate waiting for traffic from client to server. The sleep duration must // be longer than the read timeout of the ready check! - withServer((socket) -> sleep(Duration.ofSeconds(10)), (port) -> check(port)); + withServer((socket) -> sleep(Duration.ofSeconds(10)), this::check); } @Test diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/ConnectionNamePredicateTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/ConnectionNamePredicateTests.java index 183ca38ec177..76b1128232db 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/ConnectionNamePredicateTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/ConnectionNamePredicateTests.java @@ -74,7 +74,14 @@ void labeled() { .accepts(sourceOf("internalhost:8080/libs/libs/mzipkin", "openzipkin/zipkin")); } - private Predicate predicateOf(String required) { + @Test + void multiple() { + assertThat(predicateOf("elasticsearch1", "elasticsearch2")).accepts(sourceOf("elasticsearch1")) + .accepts(sourceOf("elasticsearch2")); + + } + + private Predicate predicateOf(String... required) { return new ConnectionNamePredicate(required); } diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQClassicEnvironmentTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQClassicEnvironmentTests.java new file mode 100644 index 000000000000..8ef192c78877 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQClassicEnvironmentTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.activemq; + +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ActiveMQClassicEnvironment}. + * + * @author Stephane Nicoll + * @author Eddú Meléndez + */ +class ActiveMQClassicEnvironmentTests { + + @Test + void getUserWhenHasNoActiveMqUser() { + ActiveMQClassicEnvironment environment = new ActiveMQClassicEnvironment(Collections.emptyMap()); + assertThat(environment.getUser()).isNull(); + } + + @Test + void getUserWhenHasActiveMqUser() { + ActiveMQClassicEnvironment environment = new ActiveMQClassicEnvironment( + Map.of("ACTIVEMQ_CONNECTION_USER", "me")); + assertThat(environment.getUser()).isEqualTo("me"); + } + + @Test + void getPasswordWhenHasNoActiveMqPassword() { + ActiveMQClassicEnvironment environment = new ActiveMQClassicEnvironment(Collections.emptyMap()); + assertThat(environment.getPassword()).isNull(); + } + + @Test + void getPasswordWhenHasActiveMqPassword() { + ActiveMQClassicEnvironment environment = new ActiveMQClassicEnvironment( + Map.of("ACTIVEMQ_CONNECTION_PASSWORD", "secret")); + assertThat(environment.getPassword()).isEqualTo("secret"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQEnvironmentTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQEnvironmentTests.java new file mode 100644 index 000000000000..04ee5929788f --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQEnvironmentTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.activemq; + +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ActiveMQEnvironment}. + * + * @author Stephane Nicoll + */ +class ActiveMQEnvironmentTests { + + @Test + void getUserWhenHasNoActiveMqUser() { + ActiveMQEnvironment environment = new ActiveMQEnvironment(Collections.emptyMap()); + assertThat(environment.getUser()).isNull(); + } + + @Test + void getUserWhenHasActiveMqUser() { + ActiveMQEnvironment environment = new ActiveMQEnvironment(Map.of("ACTIVEMQ_USERNAME", "me")); + assertThat(environment.getUser()).isEqualTo("me"); + } + + @Test + void getPasswordWhenHasNoActiveMqPassword() { + ActiveMQEnvironment environment = new ActiveMQEnvironment(Collections.emptyMap()); + assertThat(environment.getPassword()).isNull(); + } + + @Test + void getPasswordWhenHasActiveMqPassword() { + ActiveMQEnvironment environment = new ActiveMQEnvironment(Map.of("ACTIVEMQ_PASSWORD", "secret")); + assertThat(environment.getPassword()).isEqualTo("secret"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisEnvironmentTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisEnvironmentTests.java new file mode 100644 index 000000000000..76a980a249ae --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisEnvironmentTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.activemq; + +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ArtemisEnvironment}. + * + * @author Eddú Meléndez + */ +class ArtemisEnvironmentTests { + + @Test + void getUserWhenHasNoActiveMqUser() { + ArtemisEnvironment environment = new ArtemisEnvironment(Collections.emptyMap()); + assertThat(environment.getUser()).isNull(); + } + + @Test + void getUserWhenHasActiveMqUser() { + ArtemisEnvironment environment = new ArtemisEnvironment(Map.of("ARTEMIS_USER", "me")); + assertThat(environment.getUser()).isEqualTo("me"); + } + + @Test + void getPasswordWhenHasNoActiveMqPassword() { + ArtemisEnvironment environment = new ArtemisEnvironment(Collections.emptyMap()); + assertThat(environment.getPassword()).isNull(); + } + + @Test + void getPasswordWhenHasActiveMqPassword() { + ArtemisEnvironment environment = new ArtemisEnvironment(Map.of("ARTEMIS_PASSWORD", "secret")); + assertThat(environment.getPassword()).isEqualTo("secret"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/cassandra/CassandraDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/cassandra/CassandraDockerComposeConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index 5c7bd51379b3..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/cassandra/CassandraDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.service.connection.cassandra; - -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.cassandra.CassandraConnectionDetails; -import org.springframework.boot.autoconfigure.cassandra.CassandraConnectionDetails.Node; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration test for {@link CassandraDockerComposeConnectionDetailsFactory}. - * - * @author Scott Frederick - */ -class CassandraDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { - - CassandraDockerComposeConnectionDetailsFactoryIntegrationTests() { - super("cassandra-compose.yaml", DockerImageNames.cassandra()); - } - - @Test - void runCreatesConnectionDetails() { - CassandraConnectionDetails connectionDetails = run(CassandraConnectionDetails.class); - List contactPoints = connectionDetails.getContactPoints(); - assertThat(contactPoints.size()).isEqualTo(1); - Node node = contactPoints.get(0); - assertThat(node.host()).isNotNull(); - assertThat(node.port()).isGreaterThan(0); - assertThat(connectionDetails.getUsername()).isNull(); - assertThat(connectionDetails.getPassword()).isNull(); - assertThat(connectionDetails.getLocalDatacenter()).isEqualTo("dc1"); - } - -} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/cassandra/CassandraEnvironmentTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/cassandra/CassandraEnvironmentTests.java index 9344df66548f..f4f7bbd7ca39 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/cassandra/CassandraEnvironmentTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/cassandra/CassandraEnvironmentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,10 +36,16 @@ void getDatacenterWhenDatacenterIsNotSet() { assertThat(environment.getDatacenter()).isEqualTo("datacenter1"); } + @Test + void getDatacenterWhenDcIsSet() { + CassandraEnvironment environment = new CassandraEnvironment(Map.of("CASSANDRA_DC", "testdc1")); + assertThat(environment.getDatacenter()).isEqualTo("testdc1"); + } + @Test void getDatacenterWhenDatacenterIsSet() { - CassandraEnvironment environment = new CassandraEnvironment(Map.of("CASSANDRA_DC", "dc1")); - assertThat(environment.getDatacenter()).isEqualTo("dc1"); + CassandraEnvironment environment = new CassandraEnvironment(Map.of("CASSANDRA_DATACENTER", "testdc1")); + assertThat(environment.getDatacenter()).isEqualTo("testdc1"); } } diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/elasticsearch/ElasticsearchDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/elasticsearch/ElasticsearchDockerComposeConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index c373f26f9ac7..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/elasticsearch/ElasticsearchDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.service.connection.elasticsearch; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails; -import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails.Node; -import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails.Node.Protocol; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link ElasticsearchDockerComposeConnectionDetailsFactory}. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - */ -class ElasticsearchDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { - - ElasticsearchDockerComposeConnectionDetailsFactoryIntegrationTests() { - super("elasticsearch-compose.yaml", DockerImageNames.elasticsearch8()); - } - - @Test - void runCreatesConnectionDetails() { - ElasticsearchConnectionDetails connectionDetails = run(ElasticsearchConnectionDetails.class); - assertThat(connectionDetails.getUsername()).isEqualTo("elastic"); - assertThat(connectionDetails.getPassword()).isEqualTo("secret"); - assertThat(connectionDetails.getPathPrefix()).isNull(); - assertThat(connectionDetails.getNodes()).hasSize(1); - Node node = connectionDetails.getNodes().get(0); - assertThat(node.hostname()).isNotNull(); - assertThat(node.port()).isGreaterThan(0); - assertThat(node.protocol()).isEqualTo(Protocol.HTTP); - assertThat(node.username()).isEqualTo("elastic"); - assertThat(node.password()).isEqualTo("secret"); - } - -} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/flyway/JdbcAdaptingFlywayConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/flyway/JdbcAdaptingFlywayConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index a87d73f347a4..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/flyway/JdbcAdaptingFlywayConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.service.connection.flyway; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.flyway.FlywayConnectionDetails; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link JdbcAdaptingFlywayConnectionDetailsFactory}. - * - * @author Andy Wilkinson - */ -class JdbcAdaptingFlywayConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { - - JdbcAdaptingFlywayConnectionDetailsFactoryIntegrationTests() { - super("flyway-compose.yaml", DockerImageNames.postgresql()); - } - - @Test - void runCreatesConnectionDetails() { - FlywayConnectionDetails connectionDetails = run(FlywayConnectionDetails.class); - assertThat(connectionDetails.getUsername()).isEqualTo("myuser"); - assertThat(connectionDetails.getPassword()).isEqualTo("secret"); - assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:postgresql://").endsWith("/mydatabase"); - } - -} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/jdbc/JdbcUrlBuilderTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/jdbc/JdbcUrlBuilderTests.java index 6a3e15d8527d..bb0589a16ae1 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/jdbc/JdbcUrlBuilderTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/jdbc/JdbcUrlBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,6 +67,20 @@ void buildWhenHasParamsLabelBuildsUrl() { assertThat(url).isEqualTo("jdbc:mydb://myhost:456/mydb?foo=bar"); } + @Test + void buildWithCustomAppendParametersWhenHasParamsLabelBuildsUrl() { + RunningService service = mockService(456, Map.of("org.springframework.boot.jdbc.parameters", "foo=bar")); + String url = new JdbcUrlBuilder("mydb", 1234) { + + @Override + protected void appendParameters(StringBuilder url, String parameters) { + url.append(";").append(parameters); + } + + }.build(service, "mydb"); + assertThat(url).isEqualTo("jdbc:mydb://myhost:456/mydb;foo=bar"); + } + @Test void buildWhenServiceIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> this.builder.build(null, "mydb")) diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/liquibase/JdbcAdaptingLiquibaseConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/liquibase/JdbcAdaptingLiquibaseConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index 9530bdeb567d..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/liquibase/JdbcAdaptingLiquibaseConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.service.connection.liquibase; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.liquibase.LiquibaseConnectionDetails; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link JdbcAdaptingLiquibaseConnectionDetailsFactory}. - * - * @author Andy Wilkinson - */ -class JdbcAdaptingLiquibaseConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { - - JdbcAdaptingLiquibaseConnectionDetailsFactoryIntegrationTests() { - super("liquibase-compose.yaml", DockerImageNames.postgresql()); - } - - @Test - void runCreatesConnectionDetails() { - LiquibaseConnectionDetails connectionDetails = run(LiquibaseConnectionDetails.class); - assertThat(connectionDetails.getUsername()).isEqualTo("myuser"); - assertThat(connectionDetails.getPassword()).isEqualTo("secret"); - assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:postgresql://").endsWith("/mydatabase"); - } - -} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbEnvironmentTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbEnvironmentTests.java index d291bfab1d82..1aeb244e68c5 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbEnvironmentTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbEnvironmentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ * @author Andy Wilkinson * @author Phillip Webb * @author Jinseong Hwang + * @author Scott Frederick */ class MariaDbEnvironmentTests { @@ -130,6 +131,13 @@ void getPasswordWhenHasMariadbPasswordAndMysqlRootPassword() { assertThat(environment.getPassword()).isEqualTo("secret"); } + @Test + void getPasswordWhenHasNoPasswordAndAllowEmptyPassword() { + MariaDbEnvironment environment = new MariaDbEnvironment( + Map.of("ALLOW_EMPTY_PASSWORD", "true", "MARIADB_DATABASE", "db")); + assertThat(environment.getPassword()).isEmpty(); + } + @Test void getPasswordWhenHasNoPasswordAndMariadbAllowEmptyPassword() { MariaDbEnvironment environment = new MariaDbEnvironment( diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index 61b072771470..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.service.connection.mariadb; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link MariaDbJdbcDockerComposeConnectionDetailsFactory} - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - */ -class MariaDbJdbcDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { - - MariaDbJdbcDockerComposeConnectionDetailsFactoryIntegrationTests() { - super("mariadb-compose.yaml", DockerImageNames.mariadb()); - } - - @Test - void runCreatesConnectionDetails() { - JdbcConnectionDetails connectionDetails = run(JdbcConnectionDetails.class); - assertThat(connectionDetails.getUsername()).isEqualTo("myuser"); - assertThat(connectionDetails.getPassword()).isEqualTo("secret"); - assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:mariadb://").endsWith("/mydatabase"); - } - -} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index 0e69f50f9cc9..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.service.connection.mariadb; - -import io.r2dbc.spi.ConnectionFactoryOptions; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link MariaDbR2dbcDockerComposeConnectionDetailsFactory} - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - */ -class MariaDbR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { - - MariaDbR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests() { - super("mariadb-compose.yaml", DockerImageNames.mariadb()); - } - - @Test - void runCreatesConnectionDetails() { - R2dbcConnectionDetails connectionDetails = run(R2dbcConnectionDetails.class); - ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions(); - assertThat(connectionFactoryOptions.toString()).contains("database=mydatabase", "driver=mariadb", - "password=REDACTED", "user=myuser"); - assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.PASSWORD)).isEqualTo("secret"); - } - -} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mongo/MongoDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mongo/MongoDockerComposeConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index e863d424e648..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mongo/MongoDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.service.connection.mongo; - -import com.mongodb.ConnectionString; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.mongo.MongoConnectionDetails; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link MongoDockerComposeConnectionDetailsFactory}. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - * @author Scott Frederick - */ -class MongoDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { - - MongoDockerComposeConnectionDetailsFactoryIntegrationTests() { - super("mongo-compose.yaml", DockerImageNames.mongo()); - } - - @Test - void runCreatesConnectionDetails() { - MongoConnectionDetails connectionDetails = run(MongoConnectionDetails.class); - ConnectionString connectionString = connectionDetails.getConnectionString(); - assertThat(connectionString.getCredential().getUserName()).isEqualTo("root"); - assertThat(connectionString.getCredential().getPassword()).isEqualTo("secret".toCharArray()); - assertThat(connectionString.getCredential().getSource()).isEqualTo("admin"); - assertThat(connectionString.getDatabase()).isEqualTo("mydatabase"); - assertThat(connectionDetails.getGridFs()).isNull(); - } - -} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlEnvironmentTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlEnvironmentTests.java index 819d2ebd1359..e01b4a8ebc70 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlEnvironmentTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlEnvironmentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ * @author Andy Wilkinson * @author Phillip Webb * @author Jinseong Hwang + * @author Scott Frederick */ class MySqlEnvironmentTests { @@ -86,6 +87,13 @@ void getPasswordWhenHasNoPasswordAndMysqlAllowEmptyPassword() { assertThat(environment.getPassword()).isEmpty(); } + @Test + void getPasswordWhenHasNoPasswordAndAllowEmptyPassword() { + MySqlEnvironment environment = new MySqlEnvironment( + Map.of("ALLOW_EMPTY_PASSWORD", "true", "MYSQL_DATABASE", "db")); + assertThat(environment.getPassword()).isEmpty(); + } + @Test void getDatabaseWhenHasMysqlDatabase() { MySqlEnvironment environment = new MySqlEnvironment( diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index 418ccd85387d..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.service.connection.mysql; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link MySqlJdbcDockerComposeConnectionDetailsFactory} - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - */ -class MySqlJdbcDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { - - MySqlJdbcDockerComposeConnectionDetailsFactoryIntegrationTests() { - super("mysql-compose.yaml", DockerImageNames.mysql()); - } - - @Test - void runCreatesConnectionDetails() { - JdbcConnectionDetails connectionDetails = run(JdbcConnectionDetails.class); - assertThat(connectionDetails.getUsername()).isEqualTo("myuser"); - assertThat(connectionDetails.getPassword()).isEqualTo("secret"); - assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:mysql://").endsWith("/mydatabase"); - } - -} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index cf21913c926c..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/mysql/MySqlR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.service.connection.mysql; - -import io.r2dbc.spi.ConnectionFactoryOptions; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link MySqlR2dbcDockerComposeConnectionDetailsFactory} - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - */ -class MySqlR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { - - MySqlR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests() { - super("mysql-compose.yaml", DockerImageNames.mysql()); - } - - @Test - void runCreatesConnectionDetails() { - R2dbcConnectionDetails connectionDetails = run(R2dbcConnectionDetails.class); - ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions(); - assertThat(connectionFactoryOptions.toString()).contains("database=mydatabase", "driver=mysql", - "password=REDACTED", "user=myuser"); - assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.PASSWORD)).isEqualTo("secret"); - } - -} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jEnvironmentTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jEnvironmentTests.java new file mode 100644 index 000000000000..528682e773e1 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jEnvironmentTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.neo4j; + +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.neo4j.driver.AuthTokens; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link Neo4jEnvironment}. + * + * @author Andy Wilkinson + * @author Scott Frederick + */ +class Neo4jEnvironmentTests { + + @Test + void whenNeo4jAuthAndPasswordAreNullThenAuthTokenIsNull() { + Neo4jEnvironment environment = new Neo4jEnvironment(Collections.emptyMap()); + assertThat(environment.getAuthToken()).isNull(); + } + + @Test + void whenNeo4jAuthIsNoneThenAuthTokenIsNone() { + Neo4jEnvironment environment = new Neo4jEnvironment(Map.of("NEO4J_AUTH", "none")); + assertThat(environment.getAuthToken()).isEqualTo(AuthTokens.none()); + } + + @Test + void whenNeo4jAuthIsNeo4jSlashPasswordThenAuthTokenIsBasic() { + Neo4jEnvironment environment = new Neo4jEnvironment(Map.of("NEO4J_AUTH", "neo4j/custom-password")); + assertThat(environment.getAuthToken()).isEqualTo(AuthTokens.basic("neo4j", "custom-password")); + } + + @Test + void whenNeo4jAuthIsNeitherNoneNorNeo4jSlashPasswordEnvironmentCreationThrows() { + assertThatIllegalStateException() + .isThrownBy(() -> new Neo4jEnvironment(Map.of("NEO4J_AUTH", "graphdb/custom-password"))); + } + + @Test + void whenNeo4jPasswordIsProvidedThenAuthTokenIsBasic() { + Neo4jEnvironment environment = new Neo4jEnvironment(Map.of("NEO4J_PASSWORD", "custom-password")); + assertThat(environment.getAuthToken()).isEqualTo(AuthTokens.basic("neo4j", "custom-password")); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleEnvironmentTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleEnvironmentTests.java index 4b9f37fca7bc..b66b55196736 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleEnvironmentTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleEnvironmentTests.java @@ -35,77 +35,80 @@ class OracleEnvironmentTests { @Test void getUsernameWhenHasAppUser() { OracleEnvironment environment = new OracleEnvironment( - Map.of("APP_USER", "alice", "APP_USER_PASSWORD", "secret")); + Map.of("APP_USER", "alice", "APP_USER_PASSWORD", "secret"), "defaultDb"); assertThat(environment.getUsername()).isEqualTo("alice"); } @Test void getUsernameWhenHasNoAppUser() { - OracleEnvironment environment = new OracleEnvironment(Map.of("ORACLE_PASSWORD", "secret")); + OracleEnvironment environment = new OracleEnvironment(Map.of("ORACLE_PASSWORD", "secret"), "defaultDb"); assertThat(environment.getUsername()).isEqualTo("system"); } @Test void getPasswordWhenHasAppPassword() { OracleEnvironment environment = new OracleEnvironment( - Map.of("APP_USER", "alice", "APP_USER_PASSWORD", "secret")); + Map.of("APP_USER", "alice", "APP_USER_PASSWORD", "secret"), "defaultDb"); assertThat(environment.getPassword()).isEqualTo("secret"); } @Test void getPasswordWhenHasOraclePassword() { - OracleEnvironment environment = new OracleEnvironment(Map.of("ORACLE_PASSWORD", "secret")); + OracleEnvironment environment = new OracleEnvironment(Map.of("ORACLE_PASSWORD", "secret"), "defaultDb"); assertThat(environment.getPassword()).isEqualTo("secret"); } @Test void createWhenRandomPasswordAndAppPasswordDoesNotThrow() { assertThatNoException().isThrownBy(() -> new OracleEnvironment( - Map.of("APP_USER", "alice", "APP_USER_PASSWORD", "secret", "ORACLE_RANDOM_PASSWORD", "true"))); + Map.of("APP_USER", "alice", "APP_USER_PASSWORD", "secret", "ORACLE_RANDOM_PASSWORD", "true"), + "defaultDb")); } @Test void createWhenRandomPasswordThrowsException() { assertThatIllegalStateException() - .isThrownBy(() -> new OracleEnvironment(Map.of("ORACLE_RANDOM_PASSWORD", "true"))) + .isThrownBy(() -> new OracleEnvironment(Map.of("ORACLE_RANDOM_PASSWORD", "true"), "defaultDb")) .withMessage("ORACLE_RANDOM_PASSWORD is not supported without APP_USER and APP_USER_PASSWORD"); } @Test void createWhenAppUserAndNoAppPasswordThrowsException() { - assertThatIllegalStateException().isThrownBy(() -> new OracleEnvironment(Map.of("APP_USER", "alice"))) + assertThatIllegalStateException() + .isThrownBy(() -> new OracleEnvironment(Map.of("APP_USER", "alice"), "defaultDb")) .withMessage("No Oracle app password found"); } @Test void createWhenAppUserAndEmptyAppPasswordThrowsException() { assertThatIllegalStateException() - .isThrownBy(() -> new OracleEnvironment(Map.of("APP_USER", "alice", "APP_USER_PASSWORD", ""))) + .isThrownBy(() -> new OracleEnvironment(Map.of("APP_USER", "alice", "APP_USER_PASSWORD", ""), "defaultDb")) .withMessage("No Oracle app password found"); } @Test void createWhenHasNoPasswordThrowsException() { - assertThatIllegalStateException().isThrownBy(() -> new OracleEnvironment(Collections.emptyMap())) + assertThatIllegalStateException().isThrownBy(() -> new OracleEnvironment(Collections.emptyMap(), "defaultDb")) .withMessage("No Oracle password found"); } @Test void createWhenHasEmptyPasswordThrowsException() { - assertThatIllegalStateException().isThrownBy(() -> new OracleEnvironment(Map.of("ORACLE_PASSWORD", ""))) + assertThatIllegalStateException() + .isThrownBy(() -> new OracleEnvironment(Map.of("ORACLE_PASSWORD", ""), "defaultDb")) .withMessage("No Oracle password found"); } @Test void getDatabaseWhenHasNoOracleDatabase() { - OracleEnvironment environment = new OracleEnvironment(Map.of("ORACLE_PASSWORD", "secret")); - assertThat(environment.getDatabase()).isEqualTo("xepdb1"); + OracleEnvironment environment = new OracleEnvironment(Map.of("ORACLE_PASSWORD", "secret"), "defaultDb"); + assertThat(environment.getDatabase()).isEqualTo("defaultDb"); } @Test void getDatabaseWhenHasOracleDatabase() { OracleEnvironment environment = new OracleEnvironment( - Map.of("ORACLE_PASSWORD", "secret", "ORACLE_DATABASE", "db")); + Map.of("ORACLE_PASSWORD", "secret", "ORACLE_DATABASE", "db"), "defaultDb"); assertThat(environment.getDatabase()).isEqualTo("db"); } diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index 0d3c71cdc5a2..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.service.connection.oracle; - -import java.sql.Driver; -import java.time.Duration; - -import org.awaitility.Awaitility; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.OS; - -import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.springframework.boot.jdbc.DatabaseDriver; -import org.springframework.boot.testsupport.junit.DisabledOnOs; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.SimpleDriverDataSource; -import org.springframework.util.ClassUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link OracleJdbcDockerComposeConnectionDetailsFactory} - * - * @author Andy Wilkinson - */ -@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", - disabledReason = "The Oracle image has no ARM support") -class OracleJdbcDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { - - OracleJdbcDockerComposeConnectionDetailsFactoryIntegrationTests() { - super("oracle-compose.yaml", DockerImageNames.oracleXe()); - } - - @Test - @SuppressWarnings("unchecked") - void runCreatesConnectionDetailsThatCanBeUsedToAccessDatabase() throws Exception { - JdbcConnectionDetails connectionDetails = run(JdbcConnectionDetails.class); - assertThat(connectionDetails.getUsername()).isEqualTo("app_user"); - assertThat(connectionDetails.getPassword()).isEqualTo("app_user_secret"); - assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:oracle:thin:@").endsWith("/xepdb1"); - SimpleDriverDataSource dataSource = new SimpleDriverDataSource(); - dataSource.setUrl(connectionDetails.getJdbcUrl()); - dataSource.setUsername(connectionDetails.getUsername()); - dataSource.setPassword(connectionDetails.getPassword()); - dataSource.setDriverClass((Class) ClassUtils.forName(connectionDetails.getDriverClassName(), - getClass().getClassLoader())); - Awaitility.await().atMost(Duration.ofMinutes(1)).ignoreExceptions().untilAsserted(() -> { - JdbcTemplate template = new JdbcTemplate(dataSource); - assertThat(template.queryForObject(DatabaseDriver.ORACLE.getValidationQuery(), String.class)) - .isEqualTo("Hello"); - }); - } - -} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index 303f027f0c8b..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/oracle/OracleR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.service.connection.oracle; - -import java.time.Duration; - -import io.r2dbc.spi.ConnectionFactories; -import io.r2dbc.spi.ConnectionFactoryOptions; -import org.awaitility.Awaitility; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.OS; - -import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.springframework.boot.jdbc.DatabaseDriver; -import org.springframework.boot.testsupport.junit.DisabledOnOs; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; -import org.springframework.r2dbc.core.DatabaseClient; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link OracleR2dbcDockerComposeConnectionDetailsFactory} - * - * @author Andy Wilkinson - */ -@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", - disabledReason = "The Oracle image has no ARM support") -class OracleR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { - - OracleR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests() { - super("oracle-compose.yaml", DockerImageNames.oracleXe()); - } - - @Test - void runCreatesConnectionDetailsThatCanBeUsedToAccessDatabase() { - R2dbcConnectionDetails connectionDetails = run(R2dbcConnectionDetails.class); - ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions(); - assertThat(connectionFactoryOptions.toString()).contains("database=xepdb1", "driver=oracle", - "password=REDACTED", "user=app_user"); - assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.PASSWORD)) - .isEqualTo("app_user_secret"); - Awaitility.await().atMost(Duration.ofMinutes(1)).ignoreExceptions().untilAsserted(() -> { - Object result = DatabaseClient.create(ConnectionFactories.get(connectionFactoryOptions)) - .sql(DatabaseDriver.ORACLE.getValidationQuery()) - .map((row, metadata) -> row.get(0)) - .first() - .block(Duration.ofSeconds(30)); - assertThat(result).isEqualTo("Hello"); - }); - } - -} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresEnvironmentTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresEnvironmentTests.java index 58d590de53ed..3a5cb93357f3 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresEnvironmentTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresEnvironmentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,13 +30,15 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick + * @author Sidmar Theodoro */ class PostgresEnvironmentTests { @Test void createWhenNoPostgresPasswordThrowsException() { assertThatIllegalStateException().isThrownBy(() -> new PostgresEnvironment(Collections.emptyMap())) - .withMessage("No POSTGRES_PASSWORD defined"); + .withMessage("PostgreSQL password must be provided"); } @Test @@ -45,6 +47,12 @@ void getUsernameWhenNoPostgresUser() { assertThat(environment.getUsername()).isEqualTo("postgres"); } + @Test + void getUsernameWhenNoPostgresqlUser() { + PostgresEnvironment environment = new PostgresEnvironment(Map.of("POSTGRESQL_PASSWORD", "secret")); + assertThat(environment.getUsername()).isEqualTo("postgres"); + } + @Test void getUsernameWhenHasPostgresUser() { PostgresEnvironment environment = new PostgresEnvironment( @@ -52,18 +60,43 @@ void getUsernameWhenHasPostgresUser() { assertThat(environment.getUsername()).isEqualTo("me"); } + @Test + void getUsernameWhenHasPostgresqlUser() { + PostgresEnvironment environment = new PostgresEnvironment( + Map.of("POSTGRESQL_USER", "me", "POSTGRESQL_PASSWORD", "secret")); + assertThat(environment.getUsername()).isEqualTo("me"); + } + @Test void getPasswordWhenHasPostgresPassword() { PostgresEnvironment environment = new PostgresEnvironment(Map.of("POSTGRES_PASSWORD", "secret")); assertThat(environment.getPassword()).isEqualTo("secret"); } + @Test + void getPasswordWhenHasPostgresqlPassword() { + PostgresEnvironment environment = new PostgresEnvironment(Map.of("POSTGRESQL_PASSWORD", "secret")); + assertThat(environment.getPassword()).isEqualTo("secret"); + } + + @Test + void getPasswordWhenHasTrustHostAuthMethod() { + PostgresEnvironment environment = new PostgresEnvironment(Map.of("POSTGRES_HOST_AUTH_METHOD", "trust")); + assertThat(environment.getPassword()).isNull(); + } + @Test void getDatabaseWhenNoPostgresDbOrPostgresUser() { PostgresEnvironment environment = new PostgresEnvironment(Map.of("POSTGRES_PASSWORD", "secret")); assertThat(environment.getDatabase()).isEqualTo("postgres"); } + @Test + void getDatabaseWhenNoPostgresqlDbOrPostgresUser() { + PostgresEnvironment environment = new PostgresEnvironment(Map.of("POSTGRESQL_PASSWORD", "secret")); + assertThat(environment.getDatabase()).isEqualTo("postgres"); + } + @Test void getDatabaseWhenNoPostgresDbAndPostgresUser() { PostgresEnvironment environment = new PostgresEnvironment( @@ -71,6 +104,13 @@ void getDatabaseWhenNoPostgresDbAndPostgresUser() { assertThat(environment.getDatabase()).isEqualTo("me"); } + @Test + void getDatabaseWhenNoPostgresqlDbAndPostgresUser() { + PostgresEnvironment environment = new PostgresEnvironment( + Map.of("POSTGRESQL_USER", "me", "POSTGRESQL_PASSWORD", "secret")); + assertThat(environment.getDatabase()).isEqualTo("me"); + } + @Test void getDatabaseWhenHasPostgresDb() { PostgresEnvironment environment = new PostgresEnvironment( @@ -78,4 +118,11 @@ void getDatabaseWhenHasPostgresDb() { assertThat(environment.getDatabase()).isEqualTo("db"); } + @Test + void getDatabaseWhenHasPostgresqlDb() { + PostgresEnvironment environment = new PostgresEnvironment( + Map.of("POSTGRESQL_DB", "db", "POSTGRESQL_PASSWORD", "secret")); + assertThat(environment.getDatabase()).isEqualTo("db"); + } + } diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index b0a3873cdf58..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.service.connection.postgres; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link PostgresJdbcDockerComposeConnectionDetailsFactory}. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - */ -class PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { - - PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests() { - super("postgres-compose.yaml", DockerImageNames.postgresql()); - } - - @Test - void runCreatesConnectionDetails() { - JdbcConnectionDetails connectionDetails = run(JdbcConnectionDetails.class); - assertThat(connectionDetails.getUsername()).isEqualTo("myuser"); - assertThat(connectionDetails.getPassword()).isEqualTo("secret"); - assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:postgresql://").endsWith("/mydatabase"); - } - -} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index ba152f37c8cf..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.service.connection.postgres; - -import io.r2dbc.spi.ConnectionFactoryOptions; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link PostgresR2dbcDockerComposeConnectionDetailsFactory}. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - */ -class PostgresR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { - - PostgresR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests() { - super("postgres-compose.yaml", DockerImageNames.postgresql()); - } - - @Test - void runCreatesConnectionDetails() { - R2dbcConnectionDetails connectionDetails = run(R2dbcConnectionDetails.class); - ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions(); - assertThat(connectionFactoryOptions.toString()).contains("database=mydatabase", "driver=postgresql", - "password=REDACTED", "user=myuser"); - assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.PASSWORD)).isEqualTo("secret"); - } - -} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitDockerComposeConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index 96ccf959aaa3..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.service.connection.rabbit; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails; -import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails.Address; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link RabbitDockerComposeConnectionDetailsFactory}. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - */ -class RabbitDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { - - RabbitDockerComposeConnectionDetailsFactoryIntegrationTests() { - super("rabbit-compose.yaml", DockerImageNames.rabbit()); - } - - @Test - void runCreatesConnectionDetails() { - RabbitConnectionDetails connectionDetails = run(RabbitConnectionDetails.class); - assertThat(connectionDetails.getUsername()).isEqualTo("myuser"); - assertThat(connectionDetails.getPassword()).isEqualTo("secret"); - assertThat(connectionDetails.getVirtualHost()).isEqualTo("/"); - assertThat(connectionDetails.getAddresses()).hasSize(1); - Address address = connectionDetails.getFirstAddress(); - assertThat(address.host()).isNotNull(); - assertThat(address.port()).isGreaterThan(0); - } - -} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitEnvironmentTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitEnvironmentTests.java index fdac67e5cedc..95d07335395e 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitEnvironmentTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitEnvironmentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick */ class RabbitEnvironmentTests { @@ -44,6 +45,12 @@ void getUsernameWhenHasRabbitmqDefaultUser() { assertThat(environment.getUsername()).isEqualTo("me"); } + @Test + void getUsernameWhenHasRabbitmqUsername() { + RabbitEnvironment environment = new RabbitEnvironment(Map.of("RABBITMQ_USERNAME", "me")); + assertThat(environment.getUsername()).isEqualTo("me"); + } + @Test void getUsernameWhenNoRabbitmqDefaultPass() { RabbitEnvironment environment = new RabbitEnvironment(Collections.emptyMap()); @@ -56,4 +63,10 @@ void getUsernameWhenHasRabbitmqDefaultPass() { assertThat(environment.getPassword()).isEqualTo("secret"); } + @Test + void getUsernameWhenHasRabbitmqPassword() { + RabbitEnvironment environment = new RabbitEnvironment(Map.of("RABBITMQ_PASSWORD", "secret")); + assertThat(environment.getPassword()).isEqualTo("secret"); + } + } diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index 720f6a1d940f..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.service.connection.redis; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails; -import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails.Standalone; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration test for {@link RedisDockerComposeConnectionDetailsFactory}. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - */ -class RedisDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { - - RedisDockerComposeConnectionDetailsFactoryIntegrationTests() { - super("redis-compose.yaml", DockerImageNames.redis()); - } - - @Test - void runCreatesConnectionDetails() { - RedisConnectionDetails connectionDetails = run(RedisConnectionDetails.class); - Standalone standalone = connectionDetails.getStandalone(); - assertThat(connectionDetails.getUsername()).isNull(); - assertThat(connectionDetails.getPassword()).isNull(); - assertThat(connectionDetails.getCluster()).isNull(); - assertThat(connectionDetails.getSentinel()).isNull(); - assertThat(standalone).isNotNull(); - assertThat(standalone.getDatabase()).isZero(); - assertThat(standalone.getPort()).isGreaterThan(0); - assertThat(standalone.getHost()).isNotNull(); - } - -} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/sqlserver/SqlServerJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/sqlserver/SqlServerJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index f9f020e4e066..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/sqlserver/SqlServerJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.service.connection.sqlserver; - -import java.sql.Driver; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.OS; - -import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.springframework.boot.jdbc.DatabaseDriver; -import org.springframework.boot.testsupport.junit.DisabledOnOs; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.SimpleDriverDataSource; -import org.springframework.util.ClassUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link SqlServerJdbcDockerComposeConnectionDetailsFactory} - * - * @author Andy Wilkinson - */ -@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", - disabledReason = "The SQL server image has no ARM support") -class SqlServerJdbcDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { - - SqlServerJdbcDockerComposeConnectionDetailsFactoryIntegrationTests() { - super("mssqlserver-compose.yaml", DockerImageNames.sqlserver()); - } - - @Test - @SuppressWarnings("unchecked") - void runCreatesConnectionDetailsThatCanBeUsedToAccessDatabase() throws ClassNotFoundException, LinkageError { - JdbcConnectionDetails connectionDetails = run(JdbcConnectionDetails.class); - assertThat(connectionDetails.getUsername()).isEqualTo("SA"); - assertThat(connectionDetails.getPassword()).isEqualTo("verYs3cret"); - assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:sqlserver://"); - SimpleDriverDataSource dataSource = new SimpleDriverDataSource(); - dataSource.setUrl(connectionDetails.getJdbcUrl()); - dataSource.setUsername(connectionDetails.getUsername()); - dataSource.setPassword(connectionDetails.getPassword()); - dataSource.setDriverClass((Class) ClassUtils.forName(connectionDetails.getDriverClassName(), - getClass().getClassLoader())); - JdbcTemplate template = new JdbcTemplate(dataSource); - assertThat(template.queryForObject(DatabaseDriver.SQLSERVER.getValidationQuery(), Integer.class)).isEqualTo(1); - } - -} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/test/AbstractDockerComposeIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/test/AbstractDockerComposeIntegrationTests.java deleted file mode 100644 index 2bfb001bdaba..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/test/AbstractDockerComposeIntegrationTests.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.service.connection.test; - -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.nio.file.Path; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.io.TempDir; -import org.testcontainers.utility.DockerImageName; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.SpringApplicationShutdownHandlers; -import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; -import org.springframework.boot.testsupport.process.DisabledIfProcessUnavailable; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.util.FileCopyUtils; -import org.springframework.util.function.ThrowingSupplier; - -import static org.assertj.core.api.Assertions.fail; - -/** - * Abstract base class for integration tests. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Scott Frederick - */ -@DisabledIfProcessUnavailable({ "docker", "version" }) -@DisabledIfProcessUnavailable({ "docker", "compose" }) -public abstract class AbstractDockerComposeIntegrationTests { - - @TempDir - private static Path tempDir; - - private final Resource composeResource; - - private final DockerImageName dockerImageName; - - @AfterAll - static void shutDown() { - SpringApplicationShutdownHandlers shutdownHandlers = SpringApplication.getShutdownHandlers(); - ((Runnable) shutdownHandlers).run(); - } - - protected AbstractDockerComposeIntegrationTests(String composeResource, DockerImageName dockerImageName) { - this.composeResource = new ClassPathResource(composeResource, getClass()); - this.dockerImageName = dockerImageName; - } - - protected final T run(Class type) { - SpringApplication application = new SpringApplication(Config.class); - Map properties = new LinkedHashMap<>(); - properties.put("spring.docker.compose.skip.in-tests", "false"); - properties.put("spring.docker.compose.file", - transformedComposeFile(ThrowingSupplier.of(this.composeResource::getFile).get(), this.dockerImageName)); - properties.put("spring.docker.compose.stop.command", "down"); - application.setDefaultProperties(properties); - return application.run().getBean(type); - } - - private File transformedComposeFile(File composeFile, DockerImageName imageName) { - File tempComposeFile = Path.of(tempDir.toString(), composeFile.getName()).toFile(); - try { - String composeFileContent = FileCopyUtils.copyToString(new FileReader(composeFile)); - composeFileContent = composeFileContent.replace("{imageName}", imageName.asCanonicalNameString()); - FileCopyUtils.copy(composeFileContent, new FileWriter(tempComposeFile)); - } - catch (IOException ex) { - fail("Error transforming Docker compose file '" + composeFile + "' to '" + tempComposeFile + "': " - + ex.getMessage()); - } - return tempComposeFile; - } - - @Configuration(proxyBeanMethods = false) - static class Config { - - } - -} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/zipkin/ZipkinDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/zipkin/ZipkinDockerComposeConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index b77d558789b3..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/zipkin/ZipkinDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.service.connection.zipkin; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConnectionDetails; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link ZipkinDockerComposeConnectionDetailsFactory}. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - */ -class ZipkinDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { - - ZipkinDockerComposeConnectionDetailsFactoryIntegrationTests() { - super("zipkin-compose.yaml", DockerImageNames.zipkin()); - } - - @Test - void runCreatesConnectionDetails() { - ZipkinConnectionDetails connectionDetails = run(ZipkinConnectionDetails.class); - assertThat(connectionDetails.getSpanEndpoint()).startsWith("http://").endsWith("/api/v2/spans"); - } - -} diff --git a/spring-boot-project/spring-boot-docs/build.gradle b/spring-boot-project/spring-boot-docs/build.gradle index f55df27a85a7..53a6c6bd9a7f 100644 --- a/spring-boot-project/spring-boot-docs/build.gradle +++ b/spring-boot-project/spring-boot-docs/build.gradle @@ -1,6 +1,6 @@ plugins { id "java" - id "org.asciidoctor.jvm.convert" + id "org.antora" id "org.springframework.boot.conventions" id "org.springframework.boot.deployed" id 'org.jetbrains.kotlin.jvm' @@ -9,14 +9,21 @@ plugins { description = "Spring Boot Docs" configurations { - actuatorApiDocumentation autoConfiguration configurationProperties - gradlePluginDocumentation - mavenPluginDocumentation remoteSpringApplicationExample springApplicationExample testSlices + antoraContent + all { + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + if (details.requested.module.group == "org.apache.kafka" && details.requested.module.name == "kafka-server-common") { + details.artifactSelection { + selectArtifact(DependencyArtifact.DEFAULT_TYPE, null, null) + } + } + } + } } jar { @@ -42,15 +49,6 @@ plugins.withType(EclipsePlugin) { } dependencies { - actuatorApiDocumentation(project(path: ":spring-boot-project:spring-boot-actuator-autoconfigure", configuration: "documentation")) - - asciidoctorExtensions("io.spring.asciidoctor:spring-asciidoctor-extensions-spring-boot") - asciidoctorExtensions("io.spring.asciidoctor:spring-asciidoctor-extensions-section-ids") - asciidoctorExtensions(project(path: ":spring-boot-project:spring-boot-actuator-autoconfigure")) - asciidoctorExtensions(project(path: ":spring-boot-project:spring-boot-autoconfigure")) - asciidoctorExtensions(project(path: ":spring-boot-project:spring-boot-devtools")) - asciidoctorExtensions(project(path: ":spring-boot-project:spring-boot-docker-compose")) - autoConfiguration(project(path: ":spring-boot-project:spring-boot-autoconfigure", configuration: "autoConfigurationMetadata")) autoConfiguration(project(path: ":spring-boot-project:spring-boot-actuator-autoconfigure", configuration: "autoConfigurationMetadata")) autoConfiguration(project(path: ":spring-boot-project:spring-boot-devtools", configuration: "autoConfigurationMetadata")) @@ -63,8 +61,10 @@ dependencies { configurationProperties(project(path: ":spring-boot-project:spring-boot-docker-compose", configuration: "configurationPropertiesMetadata")) configurationProperties(project(path: ":spring-boot-project:spring-boot-devtools", configuration: "configurationPropertiesMetadata")) configurationProperties(project(path: ":spring-boot-project:spring-boot-test-autoconfigure", configuration: "configurationPropertiesMetadata")) + configurationProperties(project(path: ":spring-boot-project:spring-boot-testcontainers", configuration: "configurationPropertiesMetadata")) - gradlePluginDocumentation(project(path: ":spring-boot-project:spring-boot-tools:spring-boot-gradle-plugin", configuration: "documentation")) + dokkatoo(project(path: ":spring-boot-project:spring-boot")) + dokkatoo(project(path: ":spring-boot-project:spring-boot-test")) implementation(project(path: ":spring-boot-project:spring-boot-actuator")) implementation(project(path: ":spring-boot-project:spring-boot-actuator-autoconfigure")) @@ -78,7 +78,7 @@ dependencies { implementation(project(path: ":spring-boot-project:spring-boot-devtools")) implementation("ch.qos.logback:logback-classic") implementation("com.zaxxer:HikariCP") - implementation("io.micrometer:micrometer-core") + implementation("io.micrometer:micrometer-jakarta9") implementation("io.micrometer:micrometer-tracing") implementation("io.micrometer:micrometer-registry-graphite") implementation("io.micrometer:micrometer-registry-jmx") @@ -89,10 +89,6 @@ dependencies { implementation("jakarta.persistence:jakarta.persistence-api") implementation("jakarta.servlet:jakarta.servlet-api") implementation("jakarta.validation:jakarta.validation-api") - implementation("net.sourceforge.htmlunit:htmlunit") { - exclude group: "commons-logging", module: "commons-logging" - exclude group: "xml-apis", module: "xml-apis" - } implementation("org.apache.httpcomponents.client5:httpclient5") implementation("org.apache.commons:commons-dbcp2") { exclude group: "commons-logging", module: "commons-logging" @@ -111,6 +107,10 @@ dependencies { exclude group: "javax.xml.bind", module: "jaxb-api" exclude group: "org.jboss.spec.javax.transaction", module: "jboss-transaction-api_1.2_spec" } + implementation("org.htmlunit:htmlunit") { + exclude group: "commons-logging", module: "commons-logging" + exclude group: "xml-apis", module: "xml-apis" + } implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("org.jooq:jooq") { exclude group: "javax.xml.bind", module: "jaxb-api" @@ -151,7 +151,11 @@ dependencies { implementation("org.springframework.graphql:spring-graphql") implementation("org.springframework.graphql:spring-graphql-test") implementation("org.springframework.kafka:spring-kafka") - implementation("org.springframework.kafka:spring-kafka-test") + implementation("org.springframework.kafka:spring-kafka-test") { + exclude group: "commons-logging", module: "commons-logging" + } + implementation("org.springframework.pulsar:spring-pulsar") + implementation("org.springframework.pulsar:spring-pulsar-reactive") implementation("org.springframework.restdocs:spring-restdocs-mockmvc") implementation("org.springframework.restdocs:spring-restdocs-restassured") implementation("org.springframework.restdocs:spring-restdocs-webtestclient") @@ -167,8 +171,6 @@ dependencies { implementation("org.junit.jupiter:junit-jupiter") implementation("org.yaml:snakeyaml") - mavenPluginDocumentation(project(path: ":spring-boot-project:spring-boot-tools:spring-boot-maven-plugin", configuration: "documentation")) - remoteSpringApplicationExample(platform(project(":spring-boot-project:spring-boot-dependencies"))) remoteSpringApplicationExample(project(":spring-boot-project:spring-boot-devtools")) remoteSpringApplicationExample(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-logging")) @@ -177,6 +179,10 @@ dependencies { springApplicationExample(platform(project(":spring-boot-project:spring-boot-dependencies"))) springApplicationExample(project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter-web")) + antoraContent(project(path: ":spring-boot-project:spring-boot-actuator-autoconfigure", configuration: "antoraContent")) + antoraContent(project(path: ":spring-boot-project:spring-boot-tools:spring-boot-gradle-plugin", configuration: "antoraContent")) + antoraContent(project(path: ":spring-boot-project:spring-boot-tools:spring-boot-maven-plugin", configuration: "antoraContent")) + testImplementation(project(":spring-boot-project:spring-boot-actuator-autoconfigure")) testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation("org.assertj:assertj-core") @@ -189,8 +195,8 @@ dependencies { testSlices(project(path: ":spring-boot-project:spring-boot-test-autoconfigure", configuration: "testSliceMetadata")) } -task dependencyVersions(type: org.springframework.boot.build.constraints.ExtractVersionConstraints) { - enforcedPlatform(":spring-boot-project:spring-boot-dependencies") +dokkatoo { + moduleName.set("Spring Boot Kotlin API") } task aggregatedJavadoc(type: Javadoc) { @@ -198,7 +204,9 @@ task aggregatedJavadoc(type: Javadoc) { project.rootProject.gradle.projectsEvaluated { Set publishedProjects = rootProject.subprojects.findAll { it != project } .findAll { it.plugins.hasPlugin(JavaPlugin) && it.plugins.hasPlugin(MavenPublishPlugin) } - .findAll { !it.path.contains(":spring-boot-tools:") } + .findAll { !it.path.contains(":spring-boot-tools:") || + it.path.contains(":spring-boot-tools:spring-boot-buildpack-platform") || + it.path.contains(":spring-boot-tools:spring-boot-loader-tools") } .findAll { !it.name.startsWith('spring-boot-starter') } dependsOn publishedProjects.javadoc source publishedProjects.javadoc.source @@ -230,33 +238,33 @@ task aggregatedJavadoc(type: Javadoc) { task documentTestSlices(type: org.springframework.boot.build.test.autoconfigure.DocumentTestSlices) { testSlices = configurations.testSlices - outputFile = file("${buildDir}/docs/generated/test-auto-configuration/documented-slices.adoc") + outputFile = file("${buildDir}/generated/docs/test-auto-configuration/documented-slices.adoc") } task documentStarters(type: org.springframework.boot.build.starters.DocumentStarters) { - outputDir = file("${buildDir}/docs/generated/using/starters/") + outputDir = file("${buildDir}/generated/docs/using/starters/") } task documentAutoConfigurationClasses(type: org.springframework.boot.build.autoconfigure.DocumentAutoConfigurationClasses) { autoConfiguration = configurations.autoConfiguration - outputDir = file("${buildDir}/docs/generated/auto-configuration-classes/documented-auto-configuration-classes/") + outputDir = file("${buildDir}/generated/docs/auto-configuration-classes/documented-auto-configuration-classes/") } -task documentDependencyVersions(type: org.springframework.boot.build.constraints.DocumentConstrainedVersions) { +task documentDependencyVersionCoordinates(type: org.springframework.boot.build.constraints.DocumentConstrainedVersions) { dependsOn dependencyVersions constrainedVersions.set(providers.provider { dependencyVersions.constrainedVersions }) - outputFile = file("${buildDir}/docs/generated/dependency-versions/documented-coordinates.adoc") + outputFile = file("${buildDir}/generated/docs/dependency-versions/documented-coordinates.adoc") } -task documentVersionProperties(type: org.springframework.boot.build.constraints.DocumentVersionProperties) { +task documentDependencyVersionProperties(type: org.springframework.boot.build.constraints.DocumentVersionProperties) { dependsOn dependencyVersions versionProperties.set(providers.provider { dependencyVersions.versionProperties}) - outputFile = file("${buildDir}/docs/generated/dependency-versions/documented-properties.adoc") + outputFile = file("${buildDir}/generated/docs/dependency-versions/documented-properties.adoc") } task documentConfigurationProperties(type: org.springframework.boot.build.context.properties.DocumentConfigurationProperties) { configurationPropertyMetadata = configurations.configurationProperties - outputDir = file("${buildDir}/docs/generated/") + outputDir = file("${buildDir}/generated/docs/application-properties") } task documentDevtoolsPropertyDefaults(type: org.springframework.boot.build.devtools.DocumentDevtoolsPropertyDefaults) {} @@ -283,207 +291,111 @@ task runSpringApplicationExample(type: org.springframework.boot.build.docs.Appli task runLoggingFormatExample(type: org.springframework.boot.build.docs.ApplicationRunner) { classpath = configurations.springApplicationExample + sourceSets.main.output mainClass = "org.springframework.boot.docs.features.logexample.MyApplication" - args = ["--spring.main.banner-mode=off", "--server.port=0"] + args = ["--spring.main.banner-mode=off", "--server.port=0", "--spring.application.name=myapp"] output = file("$buildDir/example-output/logging-format.txt") expectedLogging = "Started MyApplication in " normalizeTomcatPort() } -tasks.withType(org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask) { - outputs.doNotCacheIf("This task uses log files as inputs which contain changing data (timestamp, pid)") { true } - dependsOn dependencyVersions - inputs.files(runRemoteSpringApplicationExample).withPropertyName("runRemoteSpringApplicationExample").withPathSensitivity(PathSensitivity.RELATIVE) - inputs.files(runSpringApplicationExample).withPropertyName("runSpringApplicationExample").withPathSensitivity(PathSensitivity.RELATIVE) - inputs.files(runLoggingFormatExample).withPropertyName("runLoggingFormatExample").withPathSensitivity(PathSensitivity.RELATIVE) - asciidoctorj { - fatalWarnings = ['^((?!successfully validated).)*$'] - } - doFirst { - def versionConstraints = dependencyVersions.versionConstraints - def toAntoraVersion = version -> { - String formatted = version.split("\\.").take(2).join('.') - return version.endsWith("-SNAPSHOT") ? formatted + "-SNAPSHOT" : formatted - } - attributes "hibernate-version": versionConstraints["org.hibernate.orm:hibernate-core"].split("\\.").take(2).join('.'), - "jetty-version": versionConstraints["org.eclipse.jetty:jetty-server"], - "jooq-version": versionConstraints["org.jooq:jooq"], - "lettuce-version": versionConstraints["io.lettuce:lettuce-core"], - "native-build-tools-version": nativeBuildToolsVersion, - "spring-amqp-version": versionConstraints["org.springframework.amqp:spring-amqp"], - "spring-batch-version": versionConstraints["org.springframework.batch:spring-batch-core"], - "spring-boot-version": project.version, - "spring-data-commons-version": versionConstraints["org.springframework.data:spring-data-commons"], - "spring-data-couchbase-version": versionConstraints["org.springframework.data:spring-data-couchbase"], - "spring-data-jdbc-version": versionConstraints["org.springframework.data:spring-data-jdbc"], - "spring-data-jpa-version": versionConstraints["org.springframework.data:spring-data-jpa"], - "spring-data-mongodb-version": versionConstraints["org.springframework.data:spring-data-mongodb"], - "spring-data-neo4j-version": versionConstraints["org.springframework.data:spring-data-neo4j"], - "spring-data-r2dbc-version": versionConstraints["org.springframework.data:spring-data-r2dbc"], - "spring-data-rest-version": versionConstraints["org.springframework.data:spring-data-rest-core"], - "spring-framework-version": versionConstraints["org.springframework:spring-core"], - "spring-framework-version-antora": toAntoraVersion(versionConstraints["org.springframework:spring-core"]), - "spring-graphql-version": versionConstraints["org.springframework.graphql:spring-graphql"], - "spring-integration-version": versionConstraints["org.springframework.integration:spring-integration-core"], - "spring-kafka-version": versionConstraints["org.springframework.kafka:spring-kafka"], - "spring-security-version-antora": toAntoraVersion(versionConstraints["org.springframework.security:spring-security-core"]), - "spring-authorization-server-version": versionConstraints["org.springframework.security:spring-security-oauth2-authorization-server"], - "spring-webservices-version": versionConstraints["org.springframework.ws:spring-ws-core"], - "tomcat-version": tomcatVersion.split("\\.").take(2).join('.'), - "remote-spring-application-output": runRemoteSpringApplicationExample.outputs.files.singleFile, - "spring-application-output": runSpringApplicationExample.outputs.files.singleFile, - "logging-format-output": runLoggingFormatExample.outputs.files.singleFile - } -} - -asciidoctor { - sources { - include "*.singleadoc" - } -} - -task asciidoctorPdf(type: org.asciidoctor.gradle.jvm.AsciidoctorTask) { - sources { - include "*.singleadoc" - } -} - -task asciidoctorMultipage(type: org.asciidoctor.gradle.jvm.AsciidoctorTask) { - sources { - include "*.adoc" - } +def getRelativeExamplesPath(var outputs) { + def fileName = outputs.files.singleFile.name + 'example$example-output/' + fileName } -syncDocumentationSourceForAsciidoctor { - dependsOn documentTestSlices - dependsOn documentStarters - dependsOn documentAutoConfigurationClasses - dependsOn documentDependencyVersions - dependsOn documentVersionProperties - dependsOn documentConfigurationProperties - dependsOn documentDevtoolsPropertyDefaults - from("${buildDir}/docs/generated") { - into "asciidoc" +def antoraRootAggregateContent = tasks.register("antoraRootAggregateContent", Zip) { + destinationDirectory = layout.buildDirectory.dir('generated/docs/antora-content') + archiveClassifier = "root-aggregate-content" + from("src/main") { + into "modules/ROOT/examples" + } + from(project.configurations.configurationProperties) { + eachFile { + it.path = rootProject + .projectDir + .toPath() + .relativize(it.file.toPath()) + .toString() + .replace('\\', '/') + .replaceAll('.*/([^/]+)/build.*', 'modules/ROOT/partials/$1/spring-configuration-metadata.json') + } } - from("src/main/java") { - into "main/java" + from(runRemoteSpringApplicationExample) { + into "modules/ROOT/examples" } - from("src/test/java") { - into "test/java" + from(documentDevtoolsPropertyDefaults) { + into "modules/ROOT/partials/propertydefaults" } - from("src/main/kotlin") { - into "main/kotlin" + from(documentStarters) { + into "modules/ROOT/partials/starters" } - from("src/main/groovy") { - into "main/groovy" + from(documentTestSlices) { + into "modules/appendix/partials/slices" } - from("src/main/resources") { - into "main/resources" + from(runSpringApplicationExample) { + into "modules/ROOT/partials/application" } -} - -syncDocumentationSourceForAsciidoctorMultipage { - dependsOn documentTestSlices - dependsOn documentStarters - dependsOn documentAutoConfigurationClasses - dependsOn documentDependencyVersions - dependsOn documentVersionProperties - dependsOn documentConfigurationProperties - dependsOn documentDevtoolsPropertyDefaults - from("${buildDir}/docs/generated") { - into "asciidoc" + from(runLoggingFormatExample) { + into "modules/ROOT/partials/logging" } - from("src/main/java") { - into "main/java" + from(documentDependencyVersionCoordinates) { + into "modules/appendix/partials/dependency-versions" } - from("src/test/java") { - into "test/java" + from(documentDependencyVersionProperties) { + into "modules/appendix/partials/dependency-versions" } - from("src/main/kotlin") { - into "main/kotlin" + from(documentAutoConfigurationClasses) { + into "modules/appendix/partials/auto-configuration-classes" } - from("src/main/groovy") { - into "main/groovy" + from(documentConfigurationProperties) { + into "modules/appendix/partials/configuration-properties" } - from("src/main/resources") { - into "main/resources" + from(tasks.getByName("generateAntoraYml")) { + into "modules" } } -syncDocumentationSourceForAsciidoctorPdf { - dependsOn documentTestSlices - dependsOn documentStarters - dependsOn documentAutoConfigurationClasses - dependsOn documentDependencyVersions - dependsOn documentVersionProperties - dependsOn documentConfigurationProperties - dependsOn documentDevtoolsPropertyDefaults - from("${buildDir}/docs/generated") { - into "asciidoc" - } - from("src/main/java") { - into "main/java" - } - from("src/test/java") { - into "test/java" - } - from("src/main/kotlin") { - into "main/kotlin" - } - from("src/main/groovy") { - into "main/groovy" +def antoraApiCatalogContent = tasks.register("antoraApiCatalogContent", Zip) { + destinationDirectory = layout.buildDirectory.dir('generated/docs/antora-content') + archiveClassifier = "api-catalog-content" + from(aggregatedJavadoc) { + into "java" } - from("src/main/resources") { - into "main/resources" + from(tasks.named("dokkatooGeneratePublicationHtml")) { + into "kotlin" } } -task zip(type: Zip) { - dependsOn asciidoctor, - asciidoctorMultipage, - asciidoctorPdf, - configurations.gradlePluginDocumentation, - configurations.actuatorApiDocumentation, - configurations.mavenPluginDocumentation - duplicatesStrategy "fail" - from(asciidoctor.outputDir) { - into "reference/htmlsingle" - } - from(asciidoctorPdf.outputDir) { - into "reference/pdf" - include "index.pdf" - rename { "spring-boot-reference.pdf" } - } - from(asciidoctorMultipage.outputDir) { - into "reference/html" - } - from(aggregatedJavadoc) { - into "api" - } - into("gradle-plugin") { - from { - zipTree(configurations.gradlePluginDocumentation.singleFile) - } - } - into("actuator-api") { - from { - zipTree(configurations.actuatorApiDocumentation.singleFile) - } - } - into("maven-plugin") { - from { - zipTree(configurations.mavenPluginDocumentation.singleFile) - } +def copyAntoraContentDependencies = tasks.register("copyAntoraContentDependencies", Copy) { + into layout.buildDirectory.dir('generated/docs/antora-dependencies-content') + from(configurations.antoraContent) + rename("spring-boot-actuator-autoconfigure", "spring-boot-docs") + rename("spring-boot-maven-plugin", "spring-boot-docs") + rename("spring-boot-gradle-plugin", "spring-boot-docs") +} + +tasks.named("antora") { + inputs.files(antoraRootAggregateContent, antoraApiCatalogContent, copyAntoraContentDependencies) +} + +gradle.projectsEvaluated { + def mavenPublication = publishing.publications.getByName("maven"); + configurations.antoraContent.dependencies.forEach { dependency -> + dependency.dependencyProject.configurations.getByName(dependency.targetConfiguration) + .artifacts.forEach(mavenPublication::artifact) } } -artifacts { - archives zip +dokkatoo { + dokkatooPublications.configureEach { + includes.from("src/docs/dokkatoo/dokka-overview.md") + } } publishing { publications { - maven(MavenPublication) { - artifact zip + getByName("maven") { + artifact antoraRootAggregateContent + artifact antoraApiCatalogContent } } } diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/antora.yml b/spring-boot-project/spring-boot-docs/src/docs/antora/antora.yml new file mode 100644 index 000000000000..86bca2047644 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/antora.yml @@ -0,0 +1,11 @@ +name: boot +version: true +ext: + zip_contents_collector: + include: + - name: root + classifier: aggregate-content + - name: api + classifier: catalog-content + module: api + destination: content-catalog diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/community.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/community.adoc new file mode 100644 index 000000000000..801f1c9ac616 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/community.adoc @@ -0,0 +1,17 @@ +[[community]] += Community + +If you have trouble with Spring Boot, we would like to help. + +* Try the xref:how-to:index.adoc[How-to documents]. +They provide solutions to the most common questions. +* Learn the Spring basics. +Spring Boot builds on many other Spring projects. +Check the https://spring.io[spring.io] web-site for a wealth of reference documentation. +If you are starting out with Spring, try one of the https://spring.io/guides[guides]. +* Ask a question. +We monitor https://stackoverflow.com[stackoverflow.com] for questions tagged with https://stackoverflow.com/tags/spring-boot[`spring-boot`]. +* Report bugs with Spring Boot at https://github.com/spring-projects/spring-boot/issues. + +NOTE: All of Spring Boot is open source, including the documentation. +If you find problems with the docs or if you want to improve them, please {url-github}[get involved]. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/documentation.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/documentation.adoc new file mode 100644 index 000000000000..d533959e9f51 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/documentation.adoc @@ -0,0 +1,153 @@ +:navtitle: Documentation +[[documentation]] += Documentation Overview + +This section provides a brief overview of Spring Boot reference documentation. +It serves as a map for the rest of the document. + + + +[[documentation.first-steps]] +== First Steps + +If you are getting started with Spring Boot or 'Spring' in general, start with the following topics: + +* *From scratch:* xref:index.adoc[Overview] | xref:system-requirements.adoc[Requirements] | xref:installing.adoc[Installation] +* *Tutorial:* xref:tutorial:first-application/index.adoc[Part 1] | xref:tutorial:first-application/index.adoc#getting-started.first-application.code[Part 2] +* *Running your example:* xref:tutorial:first-application/index.adoc#getting-started.first-application.run[Part 1] | xref:tutorial:first-application/index.adoc#getting-started.first-application.executable-jar[Part 2] + + + +[[documentation.upgrading]] +== Upgrading From an Earlier Version + +You should always ensure that you are running a {url-github-wiki}/Supported-Versions[supported version] of Spring Boot. + +Depending on the version that you are upgrading to, you can find some additional tips here: + +* *From 1.x:* xref:upgrading.adoc#upgrading.from-1x[Upgrading from 1.x] +* *To a new feature release:* xref:upgrading.adoc#upgrading.to-feature[Upgrading to New Feature Release] +* *Spring Boot CLI:* xref:upgrading.adoc#upgrading.cli[Upgrading the Spring Boot CLI] + + + +[[documentation.using]] +== Developing With Spring Boot + +Ready to actually start using Spring Boot? xref:reference:using/index.adoc[We have you covered]: + +* *Build systems:* xref:reference:using/build-systems.adoc#using.build-systems.maven[Maven] | xref:reference:using/build-systems.adoc#using.build-systems.gradle[Gradle] | xref:reference:using/build-systems.adoc#using.build-systems.ant[Ant] | xref:reference:using/build-systems.adoc#using.build-systems.starters[Starters] +* *Best practices:* xref:reference:using/structuring-your-code.adoc[Code Structure] | xref:reference:using/configuration-classes.adoc[@Configuration] | xref:reference:using/auto-configuration.adoc[@EnableAutoConfiguration] | xref:reference:using/spring-beans-and-dependency-injection.adoc[Beans and Dependency Injection] +* *Running your code:* xref:reference:using/running-your-application.adoc#using.running-your-application.from-an-ide[IDE] | xref:reference:using/running-your-application.adoc#using.running-your-application.as-a-packaged-application[Packaged] | xref:reference:using/running-your-application.adoc#using.running-your-application.with-the-maven-plugin[Maven] | xref:reference:using/running-your-application.adoc#using.running-your-application.with-the-gradle-plugin[Gradle] +* *Packaging your app:* xref:reference:using/packaging-for-production.adoc[Production jars] +* *Spring Boot CLI:* xref:cli:index.adoc[Using the CLI] + + + +[[documentation.features]] +== Learning About Spring Boot Features + +Need more details about Spring Boot's core features? +xref:reference:features/index.adoc[The following content is for you]: + +* *Spring Application:* xref:reference:features/spring-application.adoc[SpringApplication] +* *External Configuration:* xref:reference:features/external-config.adoc[External Configuration] +* *Profiles:* xref:reference:features/profiles.adoc[Profiles] +* *Logging:* xref:reference:features/logging.adoc[Logging] + + + +[[documentation.web]] +== Web + +If you develop Spring Boot web applications, take a look at the following content: + +* *Servlet Web Applications:* xref:reference:web/servlet.adoc[Spring MVC, Jersey, Embedded Servlet Containers] +* *Reactive Web Applications:* xref:reference:web/reactive.adoc[Spring Webflux, Embedded Servlet Containers] +* *Graceful Shutdown:* xref:reference:web/graceful-shutdown.adoc[Graceful Shutdown] +* *Spring Security:* xref:reference:web/spring-security.adoc[Default Security Configuration, Auto-configuration for OAuth2, SAML] +* *Spring Session:* xref:reference:web/spring-session.adoc[Auto-configuration for Spring Session] +* *Spring HATEOAS:* xref:reference:web/spring-hateoas.adoc[Auto-configuration for Spring HATEOAS] + + + +[[documentation.data]] +== Data + +If your application deals with a datastore, you can see how to configure that here: + +* *SQL:* xref:reference:data/sql.adoc[Configuring a SQL Datastore, Embedded Database support, Connection pools, and more.] +* *NOSQL:* xref:reference:data/nosql.adoc[Auto-configuration for NOSQL stores such as Redis, MongoDB, Neo4j, and others.] + + + +[[documentation.messaging]] +== Messaging + +If your application uses any messaging protocol, see one or more of the following sections: + +* *JMS:* xref:reference:messaging/jms.adoc[Auto-configuration for ActiveMQ and Artemis, Sending and Receiving messages through JMS] +* *AMQP:* xref:reference:messaging/amqp.adoc[Auto-configuration for RabbitMQ] +* *Kafka:* xref:reference:messaging/kafka.adoc[Auto-configuration for Spring Kafka] +* *Pulsar:* xref:reference:messaging/pulsar.adoc[Auto-configuration for Spring for Apache Pulsar] +* *RSocket:* xref:reference:messaging/rsocket.adoc[Auto-configuration for Spring Framework's RSocket Support] +* *Spring Integration:* xref:reference:messaging/spring-integration.adoc[Auto-configuration for Spring Integration] + + + +[[documentation.io]] +== IO + +If your application needs IO capabilities, see one or more of the following sections: + +* *Caching:* xref:reference:io/caching.adoc[Caching support with EhCache, Hazelcast, Infinispan, and more] +* *Quartz:* xref:reference:io/quartz.adoc[Quartz Scheduling] +* *Mail:* xref:reference:io/email.adoc[Sending Email] +* *Validation:* xref:reference:io/validation.adoc[JSR-303 Validation] +* *REST Clients:* xref:reference:io/rest-client.adoc[Calling REST Services with RestTemplate and WebClient] +* *Webservices:* xref:reference:io/webservices.adoc[Auto-configuration for Spring Web Services] +* *JTA:* xref:reference:io/jta.adoc[Distributed Transactions with JTA] + + + +[[documentation.container-images]] +== Container Images + +Spring Boot provides first-class support for building efficient container images. You can read more about it here: + +* *Efficient Container Images:* xref:reference:packaging/container-images/efficient-images.adoc[Tips to optimize container images such as Docker images] +* *Dockerfiles:* xref:reference:packaging/container-images/dockerfiles.adoc[Building container images using dockerfiles] +* *Cloud Native Buildpacks:* xref:reference:packaging/container-images/cloud-native-buildpacks.adoc[Support for Cloud Native Buildpacks with Maven and Gradle] + + + +[[documentation.actuator]] +== Moving to Production + +When you are ready to push your Spring Boot application to production, we have xref:how-to:actuator.adoc[some tricks] that you might like: + +* *Management endpoints:* xref:reference:actuator/endpoints.adoc[Overview] +* *Connection options:* xref:reference:actuator/monitoring.adoc[HTTP] | xref:reference:actuator/jmx.adoc[JMX] +* *Monitoring:* xref:reference:actuator/metrics.adoc[Metrics] | xref:reference:actuator/auditing.adoc[Auditing] | xref:reference:actuator/http-exchanges.adoc[HTTP Exchanges] | xref:reference:actuator/process-monitoring.adoc[Process] + + + +[[documentation.packaging]] +== Optimizing for Production + +Spring Boot applications can be optimized for production using technologies described in these sections: + +* *Efficient Deployments:* xref:reference:packaging/efficient.adoc#packaging.efficient.unpacking[Unpacking the Executable JAR] +* *GraalVM Native Images:* xref:reference:packaging/native-image/introducing-graalvm-native-images.adoc[Introduction] | xref:reference:packaging/native-image/advanced-topics.adoc[Advanced Topics] | xref:how-to:native-image/developing-your-first-application.adoc[Getting Started] | xref:how-to:native-image/testing-native-applications.adoc[Testing] +* *Class Data Sharing:* xref:reference:packaging/class-data-sharing.adoc[Overview] +* *Checkpoint and Restore* xref:reference:packaging/checkpoint-restore.adoc[Overview] + + +[[documentation.advanced]] +== Advanced Topics + +Finally, we have a few topics for more advanced users: + +* *Spring Boot Applications Deployment:* xref:how-to:deployment/cloud.adoc[Cloud Deployment] | xref:how-to:deployment/installing.adoc[OS Service] +* *Build tool plugins:* xref:maven-plugin:index.adoc[Maven] | xref:gradle-plugin:index.adoc[Gradle] +* *Appendix:* xref:appendix:application-properties/index.adoc[Application Properties] | xref:specification:configuration-metadata/index.adoc[Configuration Metadata] | xref:appendix:auto-configuration-classes/index.adoc[Auto-configuration Classes] | xref:appendix:test-auto-configuration/index.adoc[Test Auto-configuration Annotations] | xref:specification:executable-jar/index.adoc[Executable Jars] | xref:appendix:dependency-versions/index.adoc[Dependency Versions] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/index.adoc new file mode 100644 index 000000000000..d5822cab1487 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/index.adoc @@ -0,0 +1,15 @@ +:navtitle: Overview += Spring Boot + +Spring Boot helps you to create stand-alone, production-grade Spring-based applications that you can run. +We take an opinionated view of the Spring platform and third-party libraries, so that you can get started with minimum fuss. +Most Spring Boot applications need very little Spring configuration. + +You can use Spring Boot to create Java applications that can be started by using `java -jar` or more traditional war deployments. + +Our primary goals are: + +* Provide a radically faster and widely accessible getting-started experience for all Spring development. +* Be opinionated out of the box but get out of the way quickly as requirements start to diverge from the defaults. +* Provide a range of non-functional features that are common to large classes of projects (such as embedded servers, security, metrics, health checks, and externalized configuration). +* Absolutely no code generation (when not targeting native image) and no requirement for XML configuration. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/installing.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/installing.adoc new file mode 100644 index 000000000000..0e82650639c6 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/installing.adoc @@ -0,0 +1,209 @@ +[[getting-started.installing]] += Installing Spring Boot + +Spring Boot can be used with "`classic`" Java development tools or installed as a command line tool. +Either way, you need https://www.java.com[Java SDK v17] or higher. +Before you begin, you should check your current Java installation by using the following command: + +[source,shell] +---- +$ java -version +---- + +If you are new to Java development or if you want to experiment with Spring Boot, you might want to try the xref:installing.adoc#getting-started.installing.cli[Spring Boot CLI] (Command Line Interface) first. +Otherwise, read on for "`classic`" installation instructions. + + + +[[getting-started.installing.java]] +== Installation Instructions for the Java Developer + +You can use Spring Boot in the same way as any standard Java library. +To do so, include the appropriate `+spring-boot-*.jar+` files on your classpath. +Spring Boot does not require any special tools integration, so you can use any IDE or text editor. +Also, there is nothing special about a Spring Boot application, so you can run and debug a Spring Boot application as you would any other Java program. + +Although you _could_ copy Spring Boot jars, we generally recommend that you use a build tool that supports dependency management (such as Maven or Gradle). + + + +[[getting-started.installing.java.maven]] +=== Maven Installation + +Spring Boot is compatible with Apache Maven 3.6.3 or later. +If you do not already have Maven installed, you can follow the instructions at https://maven.apache.org. + +TIP: On many operating systems, Maven can be installed with a package manager. +If you use OSX Homebrew, try `brew install maven`. +Ubuntu users can run `sudo apt-get install maven`. +Windows users with https://chocolatey.org/[Chocolatey] can run `choco install maven` from an elevated (administrator) prompt. + +Spring Boot dependencies use the `org.springframework.boot` group id. +Typically, your Maven POM file inherits from the `spring-boot-starter-parent` project and declares dependencies to one or more xref:reference:using/build-systems.adoc#using.build-systems.starters[starters]. +Spring Boot also provides an optional xref:maven-plugin:index.adoc[Maven plugin] to create executable jars. + +More details on getting started with Spring Boot and Maven can be found in the xref:maven-plugin:getting-started.adoc[] section of the Maven plugin's reference guide. + + + +[[getting-started.installing.java.gradle]] +=== Gradle Installation + +Spring Boot is compatible with Gradle 7.x (7.6.4 or later) or 8.x (8.3 or later). +If you do not already have Gradle installed, you can follow the instructions at https://gradle.org. + +Spring Boot dependencies can be declared by using the `org.springframework.boot` `group`. +Typically, your project declares dependencies to one or more xref:reference:using/build-systems.adoc#using.build-systems.starters[starters]. +Spring Boot provides a useful xref:gradle-plugin:index.adoc[Gradle plugin] that can be used to simplify dependency declarations and to create executable jars. + +.Gradle Wrapper +**** +The Gradle Wrapper provides a nice way of "`obtaining`" Gradle when you need to build a project. +It is a small script and library that you commit alongside your code to bootstrap the build process. +See {url-gradle-docs}/gradle_wrapper.html for details. +**** + +More details on getting started with Spring Boot and Gradle can be found in the xref:gradle-plugin:getting-started.adoc[] section of the Gradle plugin's reference guide. + + + +[[getting-started.installing.cli]] +== Installing the Spring Boot CLI + +The Spring Boot CLI (Command Line Interface) is a command line tool that you can use to quickly prototype with Spring. + +You do not need to use the CLI to work with Spring Boot, but it is a quick way to get a Spring application off the ground without an IDE. + + + +[[getting-started.installing.cli.manual-installation]] +=== Manual Installation + +ifeval::["{artifact-release-type}" == "snapshot"] +You can download one of the `spring-boot-cli-\*-bin.zip` or `spring-boot-cli-*-bin.tar.gz` files from the {url-artifact-repository}/org/springframework/boot/spring-boot-cli/{version-spring-boot}/[Spring software repository]. +endif::[] +ifeval::["{artifact-release-type}" != "snapshot"] +You can download the Spring CLI distribution from one of the following locations: + +* {url-artifact-repository}/org/springframework/boot/spring-boot-cli/{version-spring-boot}/spring-boot-cli-{version-spring-boot}-bin.zip[spring-boot-cli-{version-spring-boot}-bin.zip] +* {url-artifact-repository}/org/springframework/boot/spring-boot-cli/{version-spring-boot}/spring-boot-cli-{version-spring-boot}-bin.tar.gz[spring-boot-cli-{version-spring-boot}-bin.tar.gz] +endif::[] + + +Once downloaded, follow the {url-github-raw}/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/content/INSTALL.txt[INSTALL.txt] instructions from the unpacked archive. +In summary, there is a `spring` script (`spring.bat` for Windows) in a `bin/` directory in the `.zip` file. +Alternatively, you can use `java -jar` with the `.jar` file (the script helps you to be sure that the classpath is set correctly). + + + +[[getting-started.installing.cli.sdkman]] +=== Installation with SDKMAN! + +SDKMAN! (The Software Development Kit Manager) can be used for managing multiple versions of various binary SDKs, including Groovy and the Spring Boot CLI. +Get SDKMAN! from https://sdkman.io and install Spring Boot by using the following commands: + +[source,shell,subs="verbatim,attributes"] +---- +$ sdk install springboot +$ spring --version +Spring CLI v{version-spring-boot} +---- + +If you develop features for the CLI and want access to the version you built, use the following commands: + +[source,shell,subs="verbatim,attributes"] +---- +$ sdk install springboot dev /path/to/spring-boot/spring-boot-cli/target/spring-boot-cli-{version-spring-boot}-bin/spring-{version-spring-boot}/ +$ sdk default springboot dev +$ spring --version +Spring CLI v{version-spring-boot} +---- + +The preceding instructions install a local instance of `spring` called the `dev` instance. +It points at your target build location, so every time you rebuild Spring Boot, `spring` is up-to-date. + +You can see it by running the following command: + +[source,shell,subs="verbatim,attributes"] +---- +$ sdk ls springboot + +================================================================================ +Available Springboot Versions +================================================================================ +> + dev +* {version-spring-boot} + +================================================================================ ++ - local version +* - installed +> - currently in use +================================================================================ +---- + + + +[[getting-started.installing.cli.homebrew]] +=== OSX Homebrew Installation + +If you are on a Mac and use https://brew.sh/[Homebrew], you can install the Spring Boot CLI by using the following commands: + +[source,shell] +---- +$ brew tap spring-io/tap +$ brew install spring-boot +---- + +Homebrew installs `spring` to `/usr/local/bin`. + +NOTE: If you do not see the formula, your installation of brew might be out-of-date. +In that case, run `brew update` and try again. + + + +[[getting-started.installing.cli.macports]] +=== MacPorts Installation + +If you are on a Mac and use https://www.macports.org/[MacPorts], you can install the Spring Boot CLI by using the following command: + +[source,shell] +---- +$ sudo port install spring-boot-cli +---- + + + +[[getting-started.installing.cli.completion]] +=== Command-line Completion + +The Spring Boot CLI includes scripts that provide command completion for the https://en.wikipedia.org/wiki/Bash_%28Unix_shell%29[BASH] and https://en.wikipedia.org/wiki/Z_shell[zsh] shells. +You can `source` the script (also named `spring`) in any shell or put it in your personal or system-wide bash completion initialization. +On a Debian system, the system-wide scripts are in `/shell-completion/bash` and all scripts in that directory are executed when a new shell starts. +For example, to run the script manually if you have installed by using SDKMAN!, use the following commands: + +[source,shell] +---- +$ . ~/.sdkman/candidates/springboot/current/shell-completion/bash/spring +$ spring + grab help jar run test version +---- + +NOTE: If you install the Spring Boot CLI by using Homebrew or MacPorts, the command-line completion scripts are automatically registered with your shell. + + + +[[getting-started.installing.cli.scoop]] +=== Windows Scoop Installation + +If you are on a Windows and use https://scoop.sh/[Scoop], you can install the Spring Boot CLI by using the following commands: + +[source,shell] +---- +$ scoop bucket add extras +$ scoop install springboot +---- + +Scoop installs `spring` to `~/scoop/apps/springboot/current/bin`. + +NOTE: If you do not see the app manifest, your installation of scoop might be out-of-date. +In that case, run `scoop update` and try again. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc new file mode 100644 index 000000000000..2b4426723769 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc @@ -0,0 +1,1617 @@ +:page-layout: redirect + +* xref:community.adoc[getting-help] +* xref:documentation.adoc[documentation] +* xref:index.adoc[getting-started] +* xref:upgrading.adoc[upgrading] +* xref:reference:using/index.adoc#using[using] +* xref:reference:features/index.adoc[features] +* xref:reference:web/index.adoc[web] +* xref:reference:data/index.adoc[data] +* xref:reference:io/index.adoc[io] +* xref:reference:messaging/index.adoc[messaging] +* xref:reference:packaging/container-images/index.adoc[container-images] +* xref:reference:actuator/index.adoc[actuator] +* xref:how-to:deployment/index.adoc[deployment] +* xref:reference:packaging/native-image/index.adoc[native-image] +* xref:cli:index.adoc[cli] +* xref:build-tool-plugin:index.adoc[build-tool-plugins] +* xref:how-to:index.adoc[howto] +* xref:appendix:application-properties/index.adoc[application-properties] +* xref:specification:configuration-metadata/index.adoc[configuration-metadata] +* xref:appendix:auto-configuration-classes/index.adoc[auto-configuration-classes] +* xref:appendix:test-auto-configuration/index.adoc[test-auto-configuration] +* xref:specification:executable-jar/index.adoc[executable-jar] +* xref:appendix:dependency-versions/index.adoc[dependency-versions] +* xref:community.adoc[#getting-help] +* xref:documentation.adoc[#documentation] +* xref:index.adoc[#getting-started] +* xref:upgrading.adoc[#upgrading] +* xref:reference:using/index.adoc#using[#using] +* xref:reference:features/index.adoc[#features] +* xref:reference:web/index.adoc[#web] +* xref:reference:data/index.adoc[#data] +* xref:reference:io/index.adoc[#io] +* xref:reference:messaging/index.adoc[#messaging] +* xref:reference:packaging/container-images/index.adoc[#container-images] +* xref:reference:actuator/index.adoc[#actuator] +* xref:how-to:deployment/index.adoc[#deployment] +* xref:reference:packaging/native-image/index.adoc[#native-image] +* xref:cli:index.adoc[#cli] +* xref:build-tool-plugin:index.adoc[#build-tool-plugins] +* xref:how-to:index.adoc[#howto] +* xref:appendix:application-properties/index.adoc[#application-properties] +* xref:specification:configuration-metadata/index.adoc[#configuration-metadata] +* xref:appendix:auto-configuration-classes/index.adoc[#auto-configuration-classes] +* xref:appendix:test-auto-configuration/index.adoc[#test-auto-configuration] +* xref:specification:executable-jar/index.adoc[#executable-jar] +* xref:appendix:dependency-versions/index.adoc[#dependency-versions] +* xref:ROOT:community.adoc#community[#community] +* xref:ROOT:community.adoc#community[#boot-documentation-getting-help] +* xref:ROOT:documentation.adoc#documentation[#documentation] +* xref:ROOT:documentation.adoc#documentation.actuator[#documentation.actuator] +* xref:ROOT:documentation.adoc#documentation.advanced[#documentation.advanced] +* xref:ROOT:documentation.adoc#documentation.container-images[#documentation.container-images] +* xref:ROOT:documentation.adoc#documentation.data[#documentation.data] +* xref:ROOT:documentation.adoc#documentation.features[#documentation.features] +* xref:ROOT:documentation.adoc#documentation.first-steps[#documentation.first-steps] +* xref:ROOT:documentation.adoc#documentation.io[#documentation.io] +* xref:ROOT:documentation.adoc#documentation.messaging[#documentation.messaging] +* xref:ROOT:documentation.adoc#documentation.packaging[#documentation.packaging] +* xref:ROOT:documentation.adoc#documentation.upgrading[#documentation.upgrading] +* xref:ROOT:documentation.adoc#documentation.using[#documentation.using] +* xref:ROOT:documentation.adoc#documentation.web[#documentation.web] +* xref:ROOT:installing.adoc#getting-started.installing[#getting-started.installing] +* xref:ROOT:installing.adoc#getting-started.installing.cli[#getting-started.installing.cli] +* xref:ROOT:installing.adoc#getting-started.installing.cli.completion[#getting-started.installing.cli.completion] +* xref:ROOT:installing.adoc#getting-started.installing.cli.homebrew[#getting-started.installing.cli.homebrew] +* xref:ROOT:installing.adoc#getting-started.installing.cli.macports[#getting-started.installing.cli.macports] +* xref:ROOT:installing.adoc#getting-started.installing.cli.manual-installation[#getting-started.installing.cli.manual-installation] +* xref:ROOT:installing.adoc#getting-started.installing.cli.scoop[#getting-started.installing.cli.scoop] +* xref:ROOT:installing.adoc#getting-started.installing.cli.sdkman[#getting-started.installing.cli.sdkman] +* xref:ROOT:installing.adoc#getting-started.installing.java[#getting-started.installing.java] +* xref:ROOT:installing.adoc#getting-started.installing.java.gradle[#getting-started.installing.java.gradle] +* xref:ROOT:installing.adoc#getting-started.installing.java.maven[#getting-started.installing.java.maven] +* xref:ROOT:system-requirements.adoc#getting-started.system-requirements[#getting-started.system-requirements] +* xref:ROOT:system-requirements.adoc#getting-started.system-requirements.graal[#getting-started.system-requirements.graal] +* xref:ROOT:system-requirements.adoc#getting-started.system-requirements.servlet-containers[#getting-started.system-requirements.servlet-containers] +* xref:ROOT:upgrading.adoc#upgrading[#upgrading] +* xref:ROOT:upgrading.adoc#upgrading[#getting-started-upgrading-from-an-earlier-version] +* xref:ROOT:upgrading.adoc#upgrading.cli[#upgrading.cli] +* xref:ROOT:upgrading.adoc#upgrading.from-1x[#upgrading.from-1x] +* xref:ROOT:upgrading.adoc#upgrading.to-feature[#upgrading.to-feature] +* xref:api:rest/actuator/auditevents.adoc#audit-events[actuator-api#audit-events] +* xref:api:rest/actuator/auditevents.adoc#audit-events.retrieving[actuator-api#audit-events.retrieving] +* xref:api:rest/actuator/auditevents.adoc#audit-events.retrieving.query-parameters[actuator-api#audit-events.retrieving.query-parameters] +* xref:api:rest/actuator/auditevents.adoc#audit-events.retrieving.response-structure[actuator-api#audit-events.retrieving.response-structure] +* xref:api:rest/actuator/beans.adoc#beans[actuator-api#beans] +* xref:api:rest/actuator/beans.adoc#beans.retrieving[actuator-api#beans.retrieving] +* xref:api:rest/actuator/beans.adoc#beans.retrieving.response-structure[actuator-api#beans.retrieving.response-structure] +* xref:api:rest/actuator/caches.adoc#caches[actuator-api#caches] +* xref:api:rest/actuator/caches.adoc#caches.all[actuator-api#caches.all] +* xref:api:rest/actuator/caches.adoc#caches.all.response-structure[actuator-api#caches.all.response-structure] +* xref:api:rest/actuator/caches.adoc#caches.evict-all[actuator-api#caches.evict-all] +* xref:api:rest/actuator/caches.adoc#caches.evict-named[actuator-api#caches.evict-named] +* xref:api:rest/actuator/caches.adoc#caches.evict-named.request-structure[actuator-api#caches.evict-named.request-structure] +* xref:api:rest/actuator/caches.adoc#caches.named[actuator-api#caches.named] +* xref:api:rest/actuator/caches.adoc#caches.named.query-parameters[actuator-api#caches.named.query-parameters] +* xref:api:rest/actuator/caches.adoc#caches.named.response-structure[actuator-api#caches.named.response-structure] +* xref:api:rest/actuator/conditions.adoc#conditions[actuator-api#conditions] +* xref:api:rest/actuator/conditions.adoc#conditions.retrieving[actuator-api#conditions.retrieving] +* xref:api:rest/actuator/conditions.adoc#conditions.retrieving.response-structure[actuator-api#conditions.retrieving.response-structure] +* xref:api:rest/actuator/configprops.adoc#configprops[actuator-api#configprops] +* xref:api:rest/actuator/configprops.adoc#configprops.retrieving[actuator-api#configprops.retrieving] +* xref:api:rest/actuator/configprops.adoc#configprops.retrieving-by-prefix[actuator-api#configprops.retrieving-by-prefix] +* xref:api:rest/actuator/configprops.adoc#configprops.retrieving-by-prefix.response-structure[actuator-api#configprops.retrieving-by-prefix.response-structure] +* xref:api:rest/actuator/configprops.adoc#configprops.retrieving.response-structure[actuator-api#configprops.retrieving.response-structure] +* xref:api:rest/actuator/env.adoc#env[actuator-api#env] +* xref:api:rest/actuator/env.adoc#env.entire[actuator-api#env.entire] +* xref:api:rest/actuator/env.adoc#env.entire.response-structure[actuator-api#env.entire.response-structure] +* xref:api:rest/actuator/env.adoc#env.single-property[actuator-api#env.single-property] +* xref:api:rest/actuator/env.adoc#env.single-property.response-structure[actuator-api#env.single-property.response-structure] +* xref:api:rest/actuator/flyway.adoc#flyway[actuator-api#flyway] +* xref:api:rest/actuator/flyway.adoc#flyway.retrieving[actuator-api#flyway.retrieving] +* xref:api:rest/actuator/flyway.adoc#flyway.retrieving.response-structure[actuator-api#flyway.retrieving.response-structure] +* xref:api:rest/actuator/health.adoc#health[actuator-api#health] +* xref:api:rest/actuator/health.adoc#health.retrieving[actuator-api#health.retrieving] +* xref:api:rest/actuator/health.adoc#health.retrieving-component[actuator-api#health.retrieving-component] +* xref:api:rest/actuator/health.adoc#health.retrieving-component-nested[actuator-api#health.retrieving-component-nested] +* xref:api:rest/actuator/health.adoc#health.retrieving-component-nested.response-structure[actuator-api#health.retrieving-component-nested.response-structure] +* xref:api:rest/actuator/health.adoc#health.retrieving-component.response-structure[actuator-api#health.retrieving-component.response-structure] +* xref:api:rest/actuator/health.adoc#health.retrieving.response-structure[actuator-api#health.retrieving.response-structure] +* xref:api:rest/actuator/heapdump.adoc#heapdump[actuator-api#heapdump] +* xref:api:rest/actuator/heapdump.adoc#heapdump.retrieving[actuator-api#heapdump.retrieving] +* xref:api:rest/actuator/httpexchanges.adoc#httpexchanges[actuator-api#httpexchanges] +* xref:api:rest/actuator/httpexchanges.adoc#httpexchanges.retrieving[actuator-api#httpexchanges.retrieving] +* xref:api:rest/actuator/httpexchanges.adoc#httpexchanges.retrieving[actuator-api#http-trace-retrieving] +* xref:api:rest/actuator/httpexchanges.adoc#httpexchanges.retrieving.response-structure[actuator-api#httpexchanges.retrieving.response-structure] +* xref:api:rest/actuator/httpexchanges.adoc#httpexchanges.retrieving.response-structure[actuator-api#http-trace-retrieving-response-structure] +* xref:api:rest/actuator/index.adoc#overview[actuator-api#overview] +* xref:api:rest/actuator/index.adoc#overview.endpoint-urls[actuator-api#overview.endpoint-urls] +* xref:api:rest/actuator/index.adoc#overview.timestamps[actuator-api#overview.timestamps] +* xref:api:rest/actuator/info.adoc#info[actuator-api#info] +* xref:api:rest/actuator/info.adoc#info.retrieving[actuator-api#info.retrieving] +* xref:api:rest/actuator/info.adoc#info.retrieving.response-structure[actuator-api#info.retrieving.response-structure] +* xref:api:rest/actuator/info.adoc#info.retrieving.response-structure.build[actuator-api#info.retrieving.response-structure.build] +* xref:api:rest/actuator/info.adoc#info.retrieving.response-structure.git[actuator-api#info.retrieving.response-structure.git] +* xref:api:rest/actuator/integrationgraph.adoc#integrationgraph[actuator-api#integrationgraph] +* xref:api:rest/actuator/integrationgraph.adoc#integrationgraph.rebuilding[actuator-api#integrationgraph.rebuilding] +* xref:api:rest/actuator/integrationgraph.adoc#integrationgraph.retrieving[actuator-api#integrationgraph.retrieving] +* xref:api:rest/actuator/integrationgraph.adoc#integrationgraph.retrieving.response-structure[actuator-api#integrationgraph.retrieving.response-structure] +* xref:api:rest/actuator/liquibase.adoc#liquibase[actuator-api#liquibase] +* xref:api:rest/actuator/liquibase.adoc#liquibase.retrieving[actuator-api#liquibase.retrieving] +* xref:api:rest/actuator/liquibase.adoc#liquibase.retrieving.response-structure[actuator-api#liquibase.retrieving.response-structure] +* xref:api:rest/actuator/logfile.adoc#logfile[actuator-api#logfile] +* xref:api:rest/actuator/logfile.adoc#logfile.retrieving[actuator-api#logfile.retrieving] +* xref:api:rest/actuator/logfile.adoc#logfile.retrieving-part[actuator-api#logfile.retrieving-part] +* xref:api:rest/actuator/loggers.adoc#loggers[actuator-api#loggers] +* xref:api:rest/actuator/loggers.adoc#loggers.all[actuator-api#loggers.all] +* xref:api:rest/actuator/loggers.adoc#loggers.all.response-structure[actuator-api#loggers.all.response-structure] +* xref:api:rest/actuator/loggers.adoc#loggers.clearing-level[actuator-api#loggers.clearing-level] +* xref:api:rest/actuator/loggers.adoc#loggers.group[actuator-api#loggers.group] +* xref:api:rest/actuator/loggers.adoc#loggers.group-setting-level[actuator-api#loggers.group-setting-level] +* xref:api:rest/actuator/loggers.adoc#loggers.group-setting-level.request-structure[actuator-api#loggers.group-setting-level.request-structure] +* xref:api:rest/actuator/loggers.adoc#loggers.group.response-structure[actuator-api#loggers.group.response-structure] +* xref:api:rest/actuator/loggers.adoc#loggers.setting-level[actuator-api#loggers.setting-level] +* xref:api:rest/actuator/loggers.adoc#loggers.setting-level.request-structure[actuator-api#loggers.setting-level.request-structure] +* xref:api:rest/actuator/loggers.adoc#loggers.single[actuator-api#loggers.single] +* xref:api:rest/actuator/loggers.adoc#loggers.single.response-structure[actuator-api#loggers.single.response-structure] +* xref:api:rest/actuator/mappings.adoc#mappings[actuator-api#mappings] +* xref:api:rest/actuator/mappings.adoc#mappings.retrieving[actuator-api#mappings.retrieving] +* xref:api:rest/actuator/mappings.adoc#mappings.retrieving.response-structure[actuator-api#mappings.retrieving.response-structure] +* xref:api:rest/actuator/mappings.adoc#mappings.retrieving.response-structure-dispatcher-handlers[actuator-api#mappings.retrieving.response-structure-dispatcher-handlers] +* xref:api:rest/actuator/mappings.adoc#mappings.retrieving.response-structure-dispatcher-servlets[actuator-api#mappings.retrieving.response-structure-dispatcher-servlets] +* xref:api:rest/actuator/mappings.adoc#mappings.retrieving.response-structure-servlet-filters[actuator-api#mappings.retrieving.response-structure-servlet-filters] +* xref:api:rest/actuator/mappings.adoc#mappings.retrieving.response-structure-servlets[actuator-api#mappings.retrieving.response-structure-servlets] +* xref:api:rest/actuator/metrics.adoc#metrics[actuator-api#metrics] +* xref:api:rest/actuator/metrics.adoc#metrics.drilling-down[actuator-api#metrics.drilling-down] +* xref:api:rest/actuator/metrics.adoc#metrics.retrieving-metric[actuator-api#metrics.retrieving-metric] +* xref:api:rest/actuator/metrics.adoc#metrics.retrieving-metric.query-parameters[actuator-api#metrics.retrieving-metric.query-parameters] +* xref:api:rest/actuator/metrics.adoc#metrics.retrieving-metric.response-structure[actuator-api#metrics.retrieving-metric.response-structure] +* xref:api:rest/actuator/metrics.adoc#metrics.retrieving-names[actuator-api#metrics.retrieving-names] +* xref:api:rest/actuator/metrics.adoc#metrics.retrieving-names.response-structure[actuator-api#metrics.retrieving-names.response-structure] +* xref:api:rest/actuator/prometheus.adoc#prometheus[actuator-api#prometheus] +* xref:api:rest/actuator/prometheus.adoc#prometheus.retrieving[actuator-api#prometheus.retrieving] +* xref:api:rest/actuator/prometheus.adoc#prometheus.retrieving-names[actuator-api#prometheus.retrieving-names] +* xref:api:rest/actuator/prometheus.adoc#prometheus.retrieving.query-parameters[actuator-api#prometheus.retrieving.query-parameters] +* xref:api:rest/actuator/quartz.adoc#quartz[actuator-api#quartz] +* xref:api:rest/actuator/quartz.adoc#quartz.job[actuator-api#quartz.job] +* xref:api:rest/actuator/quartz.adoc#quartz.job-group[actuator-api#quartz.job-group] +* xref:api:rest/actuator/quartz.adoc#quartz.job-group.response-structure[actuator-api#quartz.job-group.response-structure] +* xref:api:rest/actuator/quartz.adoc#quartz.job-groups[actuator-api#quartz.job-groups] +* xref:api:rest/actuator/quartz.adoc#quartz.job-groups.response-structure[actuator-api#quartz.job-groups.response-structure] +* xref:api:rest/actuator/quartz.adoc#quartz.job.response-structure[actuator-api#quartz.job.response-structure] +* xref:api:rest/actuator/quartz.adoc#quartz.report[actuator-api#quartz.report] +* xref:api:rest/actuator/quartz.adoc#quartz.report.response-structure[actuator-api#quartz.report.response-structure] +* xref:api:rest/actuator/quartz.adoc#quartz.trigger[actuator-api#quartz.trigger] +* xref:api:rest/actuator/quartz.adoc#quartz.trigger-group[actuator-api#quartz.trigger-group] +* xref:api:rest/actuator/quartz.adoc#quartz.trigger-group.response-structure[actuator-api#quartz.trigger-group.response-structure] +* xref:api:rest/actuator/quartz.adoc#quartz.trigger-groups[actuator-api#quartz.trigger-groups] +* xref:api:rest/actuator/quartz.adoc#quartz.trigger-groups.response-structure[actuator-api#quartz.trigger-groups.response-structure] +* xref:api:rest/actuator/quartz.adoc#quartz.trigger.calendar-interval-response-structure[actuator-api#quartz.trigger.calendar-interval-response-structure] +* xref:api:rest/actuator/quartz.adoc#quartz.trigger.common-response-structure[actuator-api#quartz.trigger.common-response-structure] +* xref:api:rest/actuator/quartz.adoc#quartz.trigger.cron-response-structure[actuator-api#quartz.trigger.cron-response-structure] +* xref:api:rest/actuator/quartz.adoc#quartz.trigger.custom-response-structure[actuator-api#quartz.trigger.custom-response-structure] +* xref:api:rest/actuator/quartz.adoc#quartz.trigger.daily-time-interval-response-structure[actuator-api#quartz.trigger.daily-time-interval-response-structure] +* xref:api:rest/actuator/quartz.adoc#quartz.trigger.simple-response-structure[actuator-api#quartz.trigger.simple-response-structure] +* xref:api:rest/actuator/sbom.adoc#sbom[actuator-api#sbom] +* xref:api:rest/actuator/sbom.adoc#sbom.retrieving-available-sboms[actuator-api#sbom.retrieving-available-sboms] +* xref:api:rest/actuator/sbom.adoc#sbom.retrieving-available-sboms.response-structure[actuator-api#sbom.retrieving-available-sboms.response-structure] +* xref:api:rest/actuator/sbom.adoc#sbom.retrieving-single-sbom[actuator-api#sbom.retrieving-single-sbom] +* xref:api:rest/actuator/sbom.adoc#sbom.retrieving-single-sbom.response-structure[actuator-api#sbom.retrieving-single-sbom.response-structure] +* xref:api:rest/actuator/scheduledtasks.adoc#scheduled-tasks[actuator-api#scheduled-tasks] +* xref:api:rest/actuator/scheduledtasks.adoc#scheduled-tasks.retrieving[actuator-api#scheduled-tasks.retrieving] +* xref:api:rest/actuator/scheduledtasks.adoc#scheduled-tasks.retrieving.response-structure[actuator-api#scheduled-tasks.retrieving.response-structure] +* xref:api:rest/actuator/sessions.adoc#sessions[actuator-api#sessions] +* xref:api:rest/actuator/sessions.adoc#sessions.deleting[actuator-api#sessions.deleting] +* xref:api:rest/actuator/sessions.adoc#sessions.retrieving[actuator-api#sessions.retrieving] +* xref:api:rest/actuator/sessions.adoc#sessions.retrieving-id[actuator-api#sessions.retrieving-id] +* xref:api:rest/actuator/sessions.adoc#sessions.retrieving-id.response-structure[actuator-api#sessions.retrieving-id.response-structure] +* xref:api:rest/actuator/sessions.adoc#sessions.retrieving.query-parameters[actuator-api#sessions.retrieving.query-parameters] +* xref:api:rest/actuator/sessions.adoc#sessions.retrieving.response-structure[actuator-api#sessions.retrieving.response-structure] +* xref:api:rest/actuator/shutdown.adoc#shutdown[actuator-api#shutdown] +* xref:api:rest/actuator/shutdown.adoc#shutdown.shutting-down[actuator-api#shutdown.shutting-down] +* xref:api:rest/actuator/shutdown.adoc#shutdown.shutting-down.response-structure[actuator-api#shutdown.shutting-down.response-structure] +* xref:api:rest/actuator/startup.adoc#startup[actuator-api#startup] +* xref:api:rest/actuator/startup.adoc#startup.retrieving[actuator-api#startup.retrieving] +* xref:api:rest/actuator/startup.adoc#startup.retrieving.drain[actuator-api#startup.retrieving.drain] +* xref:api:rest/actuator/startup.adoc#startup.retrieving.response-structure[actuator-api#startup.retrieving.response-structure] +* xref:api:rest/actuator/startup.adoc#startup.retrieving.snapshot[actuator-api#startup.retrieving.snapshot] +* xref:api:rest/actuator/threaddump.adoc#threaddump[actuator-api#threaddump] +* xref:api:rest/actuator/threaddump.adoc#threaddump.retrieving-json[actuator-api#threaddump.retrieving-json] +* xref:api:rest/actuator/threaddump.adoc#threaddump.retrieving-json.response-structure[actuator-api#threaddump.retrieving-json.response-structure] +* xref:api:rest/actuator/threaddump.adoc#threaddump.retrieving-text[actuator-api#threaddump.retrieving-text] +* xref:appendix:application-properties/index.adoc#appendix.application-properties[#appendix.application-properties] +* xref:appendix:application-properties/index.adoc#appendix.application-properties.actuator[#appendix.application-properties.actuator] +* xref:appendix:application-properties/index.adoc#appendix.application-properties.cache[#appendix.application-properties.cache] +* xref:appendix:application-properties/index.adoc#appendix.application-properties.core[#appendix.application-properties.core] +* xref:appendix:application-properties/index.adoc#appendix.application-properties.data[#appendix.application-properties.data] +* xref:appendix:application-properties/index.adoc#appendix.application-properties.data-migration[#appendix.application-properties.data-migration] +* xref:appendix:application-properties/index.adoc#appendix.application-properties.devtools[#appendix.application-properties.devtools] +* xref:appendix:application-properties/index.adoc#appendix.application-properties.docker-compose[#appendix.application-properties.docker-compose] +* xref:appendix:application-properties/index.adoc#appendix.application-properties.integration[#appendix.application-properties.integration] +* xref:appendix:application-properties/index.adoc#appendix.application-properties.json[#appendix.application-properties.json] +* xref:appendix:application-properties/index.adoc#appendix.application-properties.mail[#appendix.application-properties.mail] +* xref:appendix:application-properties/index.adoc#appendix.application-properties.rsocket[#appendix.application-properties.rsocket] +* xref:appendix:application-properties/index.adoc#appendix.application-properties.security[#appendix.application-properties.security] +* xref:appendix:application-properties/index.adoc#appendix.application-properties.server[#appendix.application-properties.server] +* xref:appendix:application-properties/index.adoc#appendix.application-properties.templating[#appendix.application-properties.templating] +* xref:appendix:application-properties/index.adoc#appendix.application-properties.testcontainers[#appendix.application-properties.testcontainers] +* xref:appendix:application-properties/index.adoc#appendix.application-properties.testing[#appendix.application-properties.testing] +* xref:appendix:application-properties/index.adoc#appendix.application-properties.transaction[#appendix.application-properties.transaction] +* xref:appendix:application-properties/index.adoc#appendix.application-properties.web[#appendix.application-properties.web] +* xref:appendix:auto-configuration-classes/actuator.adoc#appendix.auto-configuration-classes.actuator[#appendix.auto-configuration-classes.actuator] +* xref:appendix:auto-configuration-classes/core.adoc#appendix.auto-configuration-classes.core[#appendix.auto-configuration-classes.core] +* xref:appendix:auto-configuration-classes/index.adoc#appendix.auto-configuration-classes[#appendix.auto-configuration-classes] +* xref:appendix:dependency-versions/coordinates.adoc#appendix.dependency-versions.coordinates[#appendix.dependency-versions.coordinates] +* xref:appendix:dependency-versions/index.adoc#appendix.dependency-versions[#appendix.dependency-versions] +* xref:appendix:dependency-versions/properties.adoc#appendix.dependency-versions.properties[#appendix.dependency-versions.properties] +* xref:appendix:test-auto-configuration/index.adoc#appendix.test-auto-configuration[#appendix.test-auto-configuration] +* xref:appendix:test-auto-configuration/slices.adoc#appendix.test-auto-configuration.slices[#appendix.test-auto-configuration.slices] +* xref:build-tool-plugin:antlib.adoc#build-tool-plugins.antlib[#build-tool-plugins.antlib] +* xref:build-tool-plugin:antlib.adoc#build-tool-plugins.antlib.findmainclass[#build-tool-plugins.antlib.findmainclass] +* xref:build-tool-plugin:antlib.adoc#build-tool-plugins.antlib.findmainclass.examples[#build-tool-plugins.antlib.findmainclass.examples] +* xref:build-tool-plugin:antlib.adoc#build-tool-plugins.antlib.tasks[#build-tool-plugins.antlib.tasks] +* xref:build-tool-plugin:antlib.adoc#build-tool-plugins.antlib.tasks.examples[#build-tool-plugins.antlib.tasks.examples] +* xref:build-tool-plugin:antlib.adoc#build-tool-plugins.antlib.tasks.exejar[#build-tool-plugins.antlib.tasks.exejar] +* xref:build-tool-plugin:index.adoc#build-tool-plugins[#build-tool-plugins] +* xref:build-tool-plugin:other-build-systems.adoc#build-tool-plugins.other-build-systems[#build-tool-plugins.other-build-systems] +* xref:build-tool-plugin:other-build-systems.adoc#build-tool-plugins.other-build-systems.example-repackage-implementation[#build-tool-plugins.other-build-systems.example-repackage-implementation] +* xref:build-tool-plugin:other-build-systems.adoc#build-tool-plugins.other-build-systems.finding-main-class[#build-tool-plugins.other-build-systems.finding-main-class] +* xref:build-tool-plugin:other-build-systems.adoc#build-tool-plugins.other-build-systems.nested-libraries[#build-tool-plugins.other-build-systems.nested-libraries] +* xref:build-tool-plugin:other-build-systems.adoc#build-tool-plugins.other-build-systems.repackaging-archives[#build-tool-plugins.other-build-systems.repackaging-archives] +* xref:cli:index.adoc#cli[#cli] +* xref:cli:installation.adoc#cli.installation[#cli.installation] +* xref:cli:using-the-cli.adoc#cli.using-the-cli[#cli.using-the-cli] +* xref:cli:using-the-cli.adoc#cli.using-the-cli.embedded-shell[#cli.using-the-cli.embedded-shell] +* xref:cli:using-the-cli.adoc#cli.using-the-cli.initialize-new-project[#cli.using-the-cli.initialize-new-project] +* xref:gradle-plugin:aot.adoc#aot[gradle-plugin#aot] +* xref:gradle-plugin:aot.adoc#aot.processing-applications[gradle-plugin#aot.processing-applications] +* xref:gradle-plugin:aot.adoc#aot.processing-tests[gradle-plugin#aot.processing-tests] +* xref:gradle-plugin:getting-started.adoc#getting-started[gradle-plugin#getting-started] +* xref:gradle-plugin:index.adoc#gradle-plugin[gradle-plugin#gradle-plugin] +* xref:gradle-plugin:integrating-with-actuator.adoc#integrating-with-actuator[gradle-plugin#integrating-with-actuator] +* xref:gradle-plugin:integrating-with-actuator.adoc#integrating-with-actuator.build-info[gradle-plugin#integrating-with-actuator.build-info] +* xref:gradle-plugin:introduction.adoc#introduction[gradle-plugin#introduction] +* xref:gradle-plugin:managing-dependencies.adoc#managing-dependencies[gradle-plugin#managing-dependencies] +* xref:gradle-plugin:managing-dependencies.adoc#managing-dependencies.dependency-management-plugin[gradle-plugin#managing-dependencies.dependency-management-plugin] +* xref:gradle-plugin:managing-dependencies.adoc#managing-dependencies.dependency-management-plugin.customizing[gradle-plugin#managing-dependencies.dependency-management-plugin.customizing] +* xref:gradle-plugin:managing-dependencies.adoc#managing-dependencies.dependency-management-plugin.learning-more[gradle-plugin#managing-dependencies.dependency-management-plugin.learning-more] +* xref:gradle-plugin:managing-dependencies.adoc#managing-dependencies.dependency-management-plugin.using-in-isolation[gradle-plugin#managing-dependencies.dependency-management-plugin.using-in-isolation] +* xref:gradle-plugin:managing-dependencies.adoc#managing-dependencies.gradle-bom-support[gradle-plugin#managing-dependencies.gradle-bom-support] +* xref:gradle-plugin:managing-dependencies.adoc#managing-dependencies.gradle-bom-support.customizing[gradle-plugin#managing-dependencies.gradle-bom-support.customizing] +* xref:gradle-plugin:packaging-oci-image.adoc#build-image[gradle-plugin#build-image] +* xref:gradle-plugin:packaging-oci-image.adoc#build-image.customization[gradle-plugin#build-image.customization] +* xref:gradle-plugin:packaging-oci-image.adoc#build-image.customization.tags[gradle-plugin#build-image.customization.tags] +* xref:gradle-plugin:packaging-oci-image.adoc#build-image.docker-daemon[gradle-plugin#build-image.docker-daemon] +* xref:gradle-plugin:packaging-oci-image.adoc#build-image.docker-registry[gradle-plugin#build-image.docker-registry] +* xref:gradle-plugin:packaging-oci-image.adoc#build-image.examples[gradle-plugin#build-image.examples] +* xref:gradle-plugin:packaging-oci-image.adoc#build-image.examples.builder-configuration[gradle-plugin#build-image.examples.builder-configuration] +* xref:gradle-plugin:packaging-oci-image.adoc#build-image.examples.buildpacks[gradle-plugin#build-image.examples.buildpacks] +* xref:gradle-plugin:packaging-oci-image.adoc#build-image.examples.caches[gradle-plugin#build-image.examples.caches] +* xref:gradle-plugin:packaging-oci-image.adoc#build-image.examples.custom-image-builder[gradle-plugin#build-image.examples.custom-image-builder] +* xref:gradle-plugin:packaging-oci-image.adoc#build-image.examples.custom-image-name[gradle-plugin#build-image.examples.custom-image-name] +* xref:gradle-plugin:packaging-oci-image.adoc#build-image.examples.docker[gradle-plugin#build-image.examples.docker] +* xref:gradle-plugin:packaging-oci-image.adoc#build-image.examples.docker.auth[gradle-plugin#build-image.examples.docker.auth] +* xref:gradle-plugin:packaging-oci-image.adoc#build-image.examples.docker.colima[gradle-plugin#build-image.examples.docker.colima] +* xref:gradle-plugin:packaging-oci-image.adoc#build-image.examples.docker.minikube[gradle-plugin#build-image.examples.docker.minikube] +* xref:gradle-plugin:packaging-oci-image.adoc#build-image.examples.docker.podman[gradle-plugin#build-image.examples.docker.podman] +* xref:gradle-plugin:packaging-oci-image.adoc#build-image.examples.publish[gradle-plugin#build-image.examples.publish] +* xref:gradle-plugin:packaging-oci-image.adoc#build-image.examples.runtime-jvm-configuration[gradle-plugin#build-image.examples.runtime-jvm-configuration] +* xref:gradle-plugin:packaging.adoc#packaging-executable[gradle-plugin#packaging-executable] +* xref:gradle-plugin:packaging.adoc#packaging-executable.and-plain-archives[gradle-plugin#packaging-executable.and-plain-archives] +* xref:gradle-plugin:packaging.adoc#packaging-executable.configuring[gradle-plugin#packaging-executable.configuring] +* xref:gradle-plugin:packaging.adoc#packaging-executable.configuring.including-development-only-dependencies[gradle-plugin#packaging-executable.configuring.including-development-only-dependencies] +* xref:gradle-plugin:packaging.adoc#packaging-executable.configuring.launch-script[gradle-plugin#packaging-executable.configuring.launch-script] +* xref:gradle-plugin:packaging.adoc#packaging-executable.configuring.layered-archives[gradle-plugin#packaging-executable.configuring.layered-archives] +* xref:gradle-plugin:packaging.adoc#packaging-executable.configuring.layered-archives.configuration[gradle-plugin#packaging-executable.configuring.layered-archives.configuration] +* xref:gradle-plugin:packaging.adoc#packaging-executable.configuring.main-class[gradle-plugin#packaging-executable.configuring.main-class] +* xref:gradle-plugin:packaging.adoc#packaging-executable.configuring.properties-launcher[gradle-plugin#packaging-executable.configuring.properties-launcher] +* xref:gradle-plugin:packaging.adoc#packaging-executable.configuring.unpacking[gradle-plugin#packaging-executable.configuring.unpacking] +* xref:gradle-plugin:packaging.adoc#packaging-executable.jars[gradle-plugin#packaging-executable.jars] +* xref:gradle-plugin:packaging.adoc#packaging-executable.wars[gradle-plugin#packaging-executable.wars] +* xref:gradle-plugin:packaging.adoc#packaging-executable.wars.deployable[gradle-plugin#packaging-executable.wars.deployable] +* xref:gradle-plugin:publishing.adoc#publishing-your-application[gradle-plugin#publishing-your-application] +* xref:gradle-plugin:publishing.adoc#publishing-your-application.distribution[gradle-plugin#publishing-your-application.distribution] +* xref:gradle-plugin:publishing.adoc#publishing-your-application.maven-publish[gradle-plugin#publishing-your-application-maven] +* xref:gradle-plugin:publishing.adoc#publishing-your-application.maven-publish[gradle-plugin#publishing-your-application.maven-publish] +* xref:gradle-plugin:reacting.adoc#reacting-to-other-plugins[gradle-plugin#reacting-to-other-plugins] +* xref:gradle-plugin:reacting.adoc#reacting-to-other-plugins.application[gradle-plugin#reacting-to-other-plugins.application] +* xref:gradle-plugin:reacting.adoc#reacting-to-other-plugins.dependency-management[gradle-plugin#reacting-to-other-plugins.dependency-management] +* xref:gradle-plugin:reacting.adoc#reacting-to-other-plugins.java[gradle-plugin#reacting-to-other-plugins.java] +* xref:gradle-plugin:reacting.adoc#reacting-to-other-plugins.kotlin[gradle-plugin#reacting-to-other-plugins.kotlin] +* xref:gradle-plugin:reacting.adoc#reacting-to-other-plugins.nbt[gradle-plugin#reacting-to-other-plugins.nbt] +* xref:gradle-plugin:reacting.adoc#reacting-to-other-plugins.war[gradle-plugin#reacting-to-other-plugins.war] +* xref:gradle-plugin:running.adoc#running-your-application[gradle-plugin#running-your-application] +* xref:gradle-plugin:running.adoc#running-your-application.passing-arguments[gradle-plugin#running-your-application.passing-arguments] +* xref:gradle-plugin:running.adoc#running-your-application.passing-system-properties[gradle-plugin#running-your-application.passing-system-properties] +* xref:gradle-plugin:running.adoc#running-your-application.reloading-resources[gradle-plugin#running-your-application.reloading-resources] +* xref:gradle-plugin:running.adoc#running-your-application.using-a-test-main-class[gradle-plugin#running-your-application.using-a-test-main-class] +* xref:how-to:actuator.adoc#howto.actuator[#howto.actuator] +* xref:how-to:actuator.adoc#howto.actuator.change-http-port-or-address[#howto.actuator.change-http-port-or-address] +* xref:how-to:actuator.adoc#howto.actuator.customize-whitelabel-error-page[#howto.actuator.customize-whitelabel-error-page] +* xref:how-to:actuator.adoc#howto.actuator.customizing-sanitization[#howto.actuator.customizing-sanitization] +* xref:how-to:actuator.adoc#howto.actuator.map-health-indicators-to-metrics[#howto.actuator.map-health-indicators-to-metrics] +* xref:how-to:aot.adoc#howto.aot[#howto.aot] +* xref:how-to:aot.adoc#howto.aot.conditions[#howto.aot.conditions] +* xref:how-to:application.adoc#howto.application[#howto.application] +* xref:how-to:application.adoc#howto.application.context-hierarchy[#howto.application.context-hierarchy] +* xref:how-to:application.adoc#howto.application.customize-the-environment-or-application-context[#howto.application.customize-the-environment-or-application-context] +* xref:how-to:application.adoc#howto.application.failure-analyzer[#howto.application.failure-analyzer] +* xref:how-to:application.adoc#howto.application.non-web-application[#howto.application.non-web-application] +* xref:how-to:application.adoc#howto.application.troubleshoot-auto-configuration[#howto.application.troubleshoot-auto-configuration] +* xref:how-to:batch.adoc#howto.batch[#howto.batch] +* xref:how-to:batch.adoc#howto.batch.restarting-a-failed-job[#howto.batch.restarting-a-failed-job] +* xref:how-to:batch.adoc#howto.batch.running-from-the-command-line[#howto.batch.running-from-the-command-line] +* xref:how-to:batch.adoc#howto.batch.running-jobs-on-startup[#howto.batch.running-jobs-on-startup] +* xref:how-to:batch.adoc#howto.batch.specifying-a-data-source[#howto.batch.specifying-a-data-source] +* xref:how-to:batch.adoc#howto.batch.specifying-a-transaction-manager[#howto.batch.specifying-a-transaction-manager] +* xref:how-to:batch.adoc#howto.batch.storing-job-repository[#howto.batch.storing-job-repository] +* xref:how-to:build.adoc#howto.build[#howto.build] +* xref:how-to:build.adoc#howto.build.build-an-executable-archive-with-ant-without-using-spring-boot-antlib[#howto.build.build-an-executable-archive-with-ant-without-using-spring-boot-antlib] +* xref:how-to:build.adoc#howto.build.create-a-nonexecutable-jar[#howto.build.create-a-nonexecutable-jar] +* xref:how-to:build.adoc#howto.build.create-an-executable-jar-with-maven[#howto.build.create-an-executable-jar-with-maven] +* xref:how-to:build.adoc#howto.build.customize-dependency-versions[#howto.build.customize-dependency-versions] +* xref:how-to:build.adoc#howto.build.extract-specific-libraries-when-an-executable-jar-runs[#howto.build.extract-specific-libraries-when-an-executable-jar-runs] +* xref:how-to:build.adoc#howto.build.generate-git-info[#howto.build.generate-git-info] +* xref:how-to:build.adoc#howto.build.generate-info[#howto.build.generate-info] +* xref:how-to:build.adoc#howto.build.remote-debug-maven[#howto.build.remote-debug-maven] +* xref:how-to:build.adoc#howto.build.use-a-spring-boot-application-as-dependency[#howto.build.use-a-spring-boot-application-as-dependency] +* xref:how-to:data-access.adoc#howto.data-access[#howto.data-access] +* xref:how-to:data-access.adoc#howto.data-access.configure-a-component-that-is-used-by-jpa[#howto.data-access.configure-a-component-that-is-used-by-jpa] +* xref:how-to:data-access.adoc#howto.data-access.configure-custom-datasource[#howto.data-access.configure-custom-datasource] +* xref:how-to:data-access.adoc#howto.data-access.configure-hibernate-naming-strategy[#howto.data-access.configure-hibernate-naming-strategy] +* xref:how-to:data-access.adoc#howto.data-access.configure-hibernate-second-level-caching[#howto.data-access.configure-hibernate-second-level-caching] +* xref:how-to:data-access.adoc#howto.data-access.configure-jooq-with-multiple-datasources[#howto.data-access.configure-jooq-with-multiple-datasources] +* xref:how-to:data-access.adoc#howto.data-access.configure-two-datasources[#howto.data-access.configure-two-datasources] +* xref:how-to:data-access.adoc#howto.data-access.customize-spring-data-web-support[#howto.data-access.customize-spring-data-web-support] +* xref:how-to:data-access.adoc#howto.data-access.dependency-injection-in-hibernate-components[#howto.data-access.dependency-injection-in-hibernate-components] +* xref:how-to:data-access.adoc#howto.data-access.exposing-spring-data-repositories-as-rest[#howto.data-access.exposing-spring-data-repositories-as-rest] +* xref:how-to:data-access.adoc#howto.data-access.jpa-properties[#howto.data-access.jpa-properties] +* xref:how-to:data-access.adoc#howto.data-access.separate-entity-definitions-from-spring-configuration[#howto.data-access.separate-entity-definitions-from-spring-configuration] +* xref:how-to:data-access.adoc#howto.data-access.spring-data-repositories[#howto.data-access.spring-data-repositories] +* xref:how-to:data-access.adoc#howto.data-access.use-custom-entity-manager[#howto.data-access.use-custom-entity-manager] +* xref:how-to:data-access.adoc#howto.data-access.use-multiple-entity-managers[#howto.data-access.use-multiple-entity-managers] +* xref:how-to:data-access.adoc#howto.data-access.use-spring-data-jpa-and-mongo-repositories[#howto.data-access.use-spring-data-jpa-and-mongo-repositories] +* xref:how-to:data-access.adoc#howto.data-access.use-traditional-persistence-xml[#howto.data-access.use-traditional-persistence-xml] +* xref:how-to:data-initialization.adoc#howto.data-initialization[#howto.data-initialization] +* xref:how-to:data-initialization.adoc#howto.data-initialization.batch[#howto.data-initialization.batch] +* xref:how-to:data-initialization.adoc#howto.data-initialization.dependencies[#howto.data-initialization.dependencies] +* xref:how-to:data-initialization.adoc#howto.data-initialization.dependencies.depends-on-initialization-detection[#howto.data-initialization.dependencies.depends-on-initialization-detection] +* xref:how-to:data-initialization.adoc#howto.data-initialization.dependencies.initializer-detection[#howto.data-initialization.dependencies.initializer-detection] +* xref:how-to:data-initialization.adoc#howto.data-initialization.migration-tool[#howto.data-initialization.migration-tool] +* xref:how-to:data-initialization.adoc#howto.data-initialization.migration-tool.flyway[#howto.data-initialization.migration-tool.flyway] +* xref:how-to:data-initialization.adoc#howto.data-initialization.migration-tool.flyway-tests[#howto.data-initialization.migration-tool.flyway-tests] +* xref:how-to:data-initialization.adoc#howto.data-initialization.migration-tool.liquibase[#howto.data-initialization.migration-tool.liquibase] +* xref:how-to:data-initialization.adoc#howto.data-initialization.migration-tool.liquibase-tests[#howto.data-initialization.migration-tool.liquibase-tests] +* xref:how-to:data-initialization.adoc#howto.data-initialization.using-basic-sql-scripts[#howto.data-initialization.using-basic-sql-scripts] +* xref:how-to:data-initialization.adoc#howto.data-initialization.using-hibernate[#howto.data-initialization.using-hibernate] +* xref:how-to:data-initialization.adoc#howto.data-initialization.using-hibernate[#howto.data-initialization.using-jpa] +* xref:how-to:docker-compose.adoc#howto.docker-compose[#howto.docker-compose] +* xref:how-to:docker-compose.adoc#howto.docker-compose.jdbc-url[#howto.docker-compose.jdbc-url] +* xref:how-to:docker-compose.adoc#howto.docker-compose.sharing-services[#howto.docker-compose.sharing-services] +* xref:how-to:hotswapping.adoc#howto.hotswapping[#howto.hotswapping] +* xref:how-to:hotswapping.adoc#howto.hotswapping.fast-application-restarts[#howto.hotswapping.fast-application-restarts] +* xref:how-to:hotswapping.adoc#howto.hotswapping.reload-java-classes-without-restarting[#howto.hotswapping.reload-java-classes-without-restarting] +* xref:how-to:hotswapping.adoc#howto.hotswapping.reload-static-content[#howto.hotswapping.reload-static-content] +* xref:how-to:hotswapping.adoc#howto.hotswapping.reload-templates[#howto.hotswapping.reload-templates] +* xref:how-to:hotswapping.adoc#howto.hotswapping.reload-templates.freemarker[#howto.hotswapping.reload-templates.freemarker] +* xref:how-to:hotswapping.adoc#howto.hotswapping.reload-templates.groovy[#howto.hotswapping.reload-templates.groovy] +* xref:how-to:hotswapping.adoc#howto.hotswapping.reload-templates.thymeleaf[#howto.hotswapping.reload-templates.thymeleaf] +* xref:how-to:http-clients.adoc#howto.http-clients[#howto.http-clients] +* xref:how-to:http-clients.adoc#howto.http-clients.rest-template-proxy-configuration[#howto.http-clients.rest-template-proxy-configuration] +* xref:how-to:http-clients.adoc#howto.http-clients.webclient-reactor-netty-customization[#howto.http-clients.webclient-reactor-netty-customization] +* xref:how-to:index.adoc#howto[#howto] +* xref:how-to:jersey.adoc#howto.jersey[#howto.jersey] +* xref:how-to:jersey.adoc#howto.jersey.alongside-another-web-framework[#howto.jersey.alongside-another-web-framework] +* xref:how-to:jersey.adoc#howto.jersey.spring-security[#howto.jersey.spring-security] +* xref:how-to:logging.adoc#howto.logging[#howto.logging] +* xref:how-to:logging.adoc#howto.logging.log4j[#howto.logging.log4j] +* xref:how-to:logging.adoc#howto.logging.log4j.composite-configuration[#howto.logging.log4j.composite-configuration] +* xref:how-to:logging.adoc#howto.logging.log4j.yaml-or-json-config[#howto.logging.log4j.yaml-or-json-config] +* xref:how-to:logging.adoc#howto.logging.logback[#howto.logging.logback] +* xref:how-to:logging.adoc#howto.logging.logback.file-only-output[#howto.logging.logback.file-only-output] +* xref:how-to:messaging.adoc#howto.messaging[#howto.messaging] +* xref:how-to:messaging.adoc#howto.messaging.disable-transacted-jms-session[#howto.messaging.disable-transacted-jms-session] +* xref:how-to:nosql.adoc#howto.nosql[#howto.nosql] +* xref:how-to:nosql.adoc#howto.nosql.jedis-instead-of-lettuce[#howto.nosql.jedis-instead-of-lettuce] +* xref:how-to:properties-and-configuration.adoc#howto.properties-and-configuration[#howto.properties-and-configuration] +* xref:how-to:properties-and-configuration.adoc#howto.properties-and-configuration.change-configuration-depending-on-the-environment[#howto.properties-and-configuration.change-configuration-depending-on-the-environment] +* xref:how-to:properties-and-configuration.adoc#howto.properties-and-configuration.discover-build-in-options-for-external-properties[#howto.properties-and-configuration.discover-build-in-options-for-external-properties] +* xref:how-to:properties-and-configuration.adoc#howto.properties-and-configuration.expand-properties[#howto.properties-and-configuration.expand-properties] +* xref:how-to:properties-and-configuration.adoc#howto.properties-and-configuration.expand-properties.gradle[#howto.properties-and-configuration.expand-properties.gradle] +* xref:how-to:properties-and-configuration.adoc#howto.properties-and-configuration.expand-properties.maven[#howto.properties-and-configuration.expand-properties.maven] +* xref:how-to:properties-and-configuration.adoc#howto.properties-and-configuration.external-properties-location[#howto.properties-and-configuration.external-properties-location] +* xref:how-to:properties-and-configuration.adoc#howto.properties-and-configuration.externalize-configuration[#howto.properties-and-configuration.externalize-configuration] +* xref:how-to:properties-and-configuration.adoc#howto.properties-and-configuration.set-active-spring-profiles[#howto.properties-and-configuration.set-active-spring-profiles] +* xref:how-to:properties-and-configuration.adoc#howto.properties-and-configuration.set-default-spring-profile-name[#howto.properties-and-configuration.set-default-spring-profile-name] +* xref:how-to:properties-and-configuration.adoc#howto.properties-and-configuration.short-command-line-arguments[#howto.properties-and-configuration.short-command-line-arguments] +* xref:how-to:properties-and-configuration.adoc#howto.properties-and-configuration.yaml[#howto.properties-and-configuration.yaml] +* xref:how-to:security.adoc#howto.security[#howto.security] +* xref:how-to:security.adoc#howto.security.change-user-details-service-and-add-user-accounts[#howto.security.change-user-details-service-and-add-user-accounts] +* xref:how-to:security.adoc#howto.security.enable-https[#howto.security.enable-https] +* xref:how-to:security.adoc#howto.security.switch-off-spring-boot-configuration[#howto.security.switch-off-spring-boot-configuration] +* xref:how-to:spring-mvc.adoc#howto.spring-mvc[#howto.spring-mvc] +* xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-jackson-objectmapper[#howto.spring-mvc.customize-jackson-objectmapper] +* xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-responsebody-rendering[#howto.spring-mvc.customize-responsebody-rendering] +* xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-view-resolvers[#howto.spring-mvc.customize-view-resolvers] +* xref:how-to:spring-mvc.adoc#howto.spring-mvc.multipart-file-uploads[#howto.spring-mvc.multipart-file-uploads] +* xref:how-to:spring-mvc.adoc#howto.spring-mvc.switch-off-default-configuration[#howto.spring-mvc.switch-off-default-configuration] +* xref:how-to:spring-mvc.adoc#howto.spring-mvc.switch-off-dispatcherservlet[#howto.spring-mvc.switch-off-dispatcherservlet] +* xref:how-to:spring-mvc.adoc#howto.spring-mvc.write-json-rest-service[#howto.spring-mvc.write-json-rest-service] +* xref:how-to:spring-mvc.adoc#howto.spring-mvc.write-xml-rest-service[#howto.spring-mvc.write-xml-rest-service] +* xref:how-to:testing.adoc#howto.testing[#howto.testing] +* xref:how-to:testing.adoc#howto.testing.slice-tests[#howto.testing.slice-tests] +* xref:how-to:testing.adoc#howto.testing.with-spring-security[#howto.testing.with-spring-security] +* xref:how-to:testing.adoc#howto.testing.with-spring-security[#howto-use-test-with-spring-security] +* xref:how-to:deployment/traditional-deployment.adoc#howto.traditional-deployment[#howto.traditional-deployment] +* xref:how-to:deployment/traditional-deployment.adoc#howto.traditional-deployment.convert-existing-application[#howto.traditional-deployment.convert-existing-application] +* xref:how-to:deployment/traditional-deployment.adoc#howto.traditional-deployment.war[#howto.traditional-deployment.war] +* xref:how-to:deployment/traditional-deployment.adoc#howto.traditional-deployment.weblogic[#howto.traditional-deployment.weblogic] +* xref:how-to:webserver.adoc#howto-configure-webserver-customizers[#howto-configure-webserver-customizers] +* xref:how-to:webserver.adoc#howto.webserver[#howto.webserver] +* xref:how-to:webserver.adoc#howto.webserver.add-servlet-filter-listener[#howto.webserver.add-servlet-filter-listener] +* xref:how-to:webserver.adoc#howto.webserver.add-servlet-filter-listener.spring-bean[#howto.webserver.add-servlet-filter-listener.spring-bean] +* xref:how-to:webserver.adoc#howto.webserver.add-servlet-filter-listener.spring-bean.disable[#howto.webserver.add-servlet-filter-listener.spring-bean.disable] +* xref:how-to:webserver.adoc#howto.webserver.add-servlet-filter-listener.using-scanning[#howto.webserver.add-servlet-filter-listener.using-scanning] +* xref:how-to:webserver.adoc#howto.webserver.change-port[#howto.webserver.change-port] +* xref:how-to:webserver.adoc#howto.webserver.configure[#howto.webserver.configure] +* xref:how-to:webserver.adoc#howto.webserver.configure-access-logs[#howto.webserver.configure-access-logs] +* xref:how-to:webserver.adoc#howto.webserver.configure-http2[#howto.webserver.configure-http2] +* xref:how-to:webserver.adoc#howto.webserver.configure-http2.jetty[#howto.webserver.configure-http2.jetty] +* xref:how-to:webserver.adoc#howto.webserver.configure-http2.netty[#howto.webserver.configure-http2.netty] +* xref:how-to:webserver.adoc#howto.webserver.configure-http2.tomcat[#howto.webserver.configure-http2.tomcat] +* xref:how-to:webserver.adoc#howto.webserver.configure-http2.undertow[#howto.webserver.configure-http2.undertow] +* xref:how-to:webserver.adoc#howto.webserver.configure-ssl[#howto.webserver.configure-ssl] +* xref:how-to:webserver.adoc#howto.webserver.configure-ssl.pem-files[#howto.webserver.configure-ssl.pem-files] +* xref:how-to:webserver.adoc#howto.webserver.create-websocket-endpoints-using-serverendpoint[#howto.webserver.create-websocket-endpoints-using-serverendpoint] +* xref:how-to:webserver.adoc#howto.webserver.disable[#howto.webserver.disable] +* xref:how-to:webserver.adoc#howto.webserver.discover-port[#howto.webserver.discover-port] +* xref:how-to:webserver.adoc#howto.webserver.enable-multiple-connectors-in-tomcat[#howto.webserver.enable-multiple-connectors-in-tomcat] +* xref:how-to:webserver.adoc#howto.webserver.enable-multiple-listeners-in-undertow[#howto.webserver.enable-multiple-listeners-in-undertow] +* xref:how-to:webserver.adoc#howto.webserver.enable-response-compression[#howto.webserver.enable-response-compression] +* xref:how-to:webserver.adoc#howto.webserver.enable-tomcat-mbean-registry[#howto.webserver.enable-tomcat-mbean-registry] +* xref:how-to:webserver.adoc#howto.webserver.use-another[#howto.webserver.use-another] +* xref:how-to:webserver.adoc#howto.webserver.use-behind-a-proxy-server[#howto.webserver.use-behind-a-proxy-server] +* xref:how-to:webserver.adoc#howto.webserver.use-behind-a-proxy-server.tomcat[#howto.webserver.use-behind-a-proxy-server.tomcat] +* xref:how-to:webserver.adoc#howto.webserver.use-random-port[#howto.webserver.use-random-port] +* xref:maven-plugin:aot.adoc#aot[maven-plugin#aot] +* xref:maven-plugin:aot.adoc#aot.process-aot-goal[maven-plugin#aot.process-aot-goal] +* xref:maven-plugin:aot.adoc#aot.process-aot-goal.optional-parameters[maven-plugin#aot.process-aot-goal.optional-parameters] +* xref:maven-plugin:aot.adoc#aot.process-aot-goal.parameter-details[maven-plugin#aot.process-aot-goal.parameter-details] +* xref:maven-plugin:aot.adoc#aot.process-aot-goal.parameter-details.arguments[maven-plugin#aot.process-aot-goal.parameter-details.arguments] +* xref:maven-plugin:aot.adoc#aot.process-aot-goal.parameter-details.classes-directory[maven-plugin#aot.process-aot-goal.parameter-details.classes-directory] +* xref:maven-plugin:aot.adoc#aot.process-aot-goal.parameter-details.compiler-arguments[maven-plugin#aot.process-aot-goal.parameter-details.compiler-arguments] +* xref:maven-plugin:aot.adoc#aot.process-aot-goal.parameter-details.exclude-group-ids[maven-plugin#aot.process-aot-goal.parameter-details.exclude-group-ids] +* xref:maven-plugin:aot.adoc#aot.process-aot-goal.parameter-details.excludes[maven-plugin#aot.process-aot-goal.parameter-details.excludes] +* xref:maven-plugin:aot.adoc#aot.process-aot-goal.parameter-details.generated-classes[maven-plugin#aot.process-aot-goal.parameter-details.generated-classes] +* xref:maven-plugin:aot.adoc#aot.process-aot-goal.parameter-details.generated-resources[maven-plugin#aot.process-aot-goal.parameter-details.generated-resources] +* xref:maven-plugin:aot.adoc#aot.process-aot-goal.parameter-details.generated-sources[maven-plugin#aot.process-aot-goal.parameter-details.generated-sources] +* xref:maven-plugin:aot.adoc#aot.process-aot-goal.parameter-details.includes[maven-plugin#aot.process-aot-goal.parameter-details.includes] +* xref:maven-plugin:aot.adoc#aot.process-aot-goal.parameter-details.jvm-arguments[maven-plugin#aot.process-aot-goal.parameter-details.jvm-arguments] +* xref:maven-plugin:aot.adoc#aot.process-aot-goal.parameter-details.main-class[maven-plugin#aot.process-aot-goal.parameter-details.main-class] +* xref:maven-plugin:aot.adoc#aot.process-aot-goal.parameter-details.profiles[maven-plugin#aot.process-aot-goal.parameter-details.profiles] +* xref:maven-plugin:aot.adoc#aot.process-aot-goal.parameter-details.skip[maven-plugin#aot.process-aot-goal.parameter-details.skip] +* xref:maven-plugin:aot.adoc#aot.process-aot-goal.parameter-details.system-property-variables[maven-plugin#aot.process-aot-goal.parameter-details.system-property-variables] +* xref:maven-plugin:aot.adoc#aot.process-aot-goal.required-parameters[maven-plugin#aot.process-aot-goal.required-parameters] +* xref:maven-plugin:aot.adoc#aot.process-test-aot-goal[maven-plugin#aot.process-test-aot-goal] +* xref:maven-plugin:aot.adoc#aot.process-test-aot-goal.optional-parameters[maven-plugin#aot.process-test-aot-goal.optional-parameters] +* xref:maven-plugin:aot.adoc#aot.process-test-aot-goal.parameter-details[maven-plugin#aot.process-test-aot-goal.parameter-details] +* xref:maven-plugin:aot.adoc#aot.process-test-aot-goal.parameter-details.classes-directory[maven-plugin#aot.process-test-aot-goal.parameter-details.classes-directory] +* xref:maven-plugin:aot.adoc#aot.process-test-aot-goal.parameter-details.compiler-arguments[maven-plugin#aot.process-test-aot-goal.parameter-details.compiler-arguments] +* xref:maven-plugin:aot.adoc#aot.process-test-aot-goal.parameter-details.exclude-group-ids[maven-plugin#aot.process-test-aot-goal.parameter-details.exclude-group-ids] +* xref:maven-plugin:aot.adoc#aot.process-test-aot-goal.parameter-details.excludes[maven-plugin#aot.process-test-aot-goal.parameter-details.excludes] +* xref:maven-plugin:aot.adoc#aot.process-test-aot-goal.parameter-details.generated-classes[maven-plugin#aot.process-test-aot-goal.parameter-details.generated-classes] +* xref:maven-plugin:aot.adoc#aot.process-test-aot-goal.parameter-details.generated-resources[maven-plugin#aot.process-test-aot-goal.parameter-details.generated-resources] +* xref:maven-plugin:aot.adoc#aot.process-test-aot-goal.parameter-details.generated-sources[maven-plugin#aot.process-test-aot-goal.parameter-details.generated-sources] +* xref:maven-plugin:aot.adoc#aot.process-test-aot-goal.parameter-details.generated-test-classes[maven-plugin#aot.process-test-aot-goal.parameter-details.generated-test-classes] +* xref:maven-plugin:aot.adoc#aot.process-test-aot-goal.parameter-details.includes[maven-plugin#aot.process-test-aot-goal.parameter-details.includes] +* xref:maven-plugin:aot.adoc#aot.process-test-aot-goal.parameter-details.jvm-arguments[maven-plugin#aot.process-test-aot-goal.parameter-details.jvm-arguments] +* xref:maven-plugin:aot.adoc#aot.process-test-aot-goal.parameter-details.skip[maven-plugin#aot.process-test-aot-goal.parameter-details.skip] +* xref:maven-plugin:aot.adoc#aot.process-test-aot-goal.parameter-details.system-property-variables[maven-plugin#aot.process-test-aot-goal.parameter-details.system-property-variables] +* xref:maven-plugin:aot.adoc#aot.process-test-aot-goal.parameter-details.test-classes-directory[maven-plugin#aot.process-test-aot-goal.parameter-details.test-classes-directory] +* xref:maven-plugin:aot.adoc#aot.process-test-aot-goal.required-parameters[maven-plugin#aot.process-test-aot-goal.required-parameters] +* xref:maven-plugin:aot.adoc#aot.processing-applications[maven-plugin#aot.processing-applications] +* xref:maven-plugin:aot.adoc#aot.processing-applications.using-the-native-profile[maven-plugin#aot.processing-applications.using-the-native-profile] +* xref:maven-plugin:aot.adoc#aot.processing-tests[maven-plugin#aot.processing-tests] +* xref:maven-plugin:build-image.adoc#build-image[maven-plugin#build-image] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal[maven-plugin#build-image.build-image-goal] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.optional-parameters[maven-plugin#build-image.build-image-goal.optional-parameters] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details[maven-plugin#build-image.build-image-goal.parameter-details] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.classifier[maven-plugin#build-image.build-image-goal.parameter-details.classifier] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.docker[maven-plugin#build-image.build-image-goal.parameter-details.docker] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.exclude-devtools[maven-plugin#build-image.build-image-goal.parameter-details.exclude-devtools] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.exclude-docker-compose[maven-plugin#build-image.build-image-goal.parameter-details.exclude-docker-compose] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.exclude-group-ids[maven-plugin#build-image.build-image-goal.parameter-details.exclude-group-ids] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.excludes[maven-plugin#build-image.build-image-goal.parameter-details.excludes] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.image[maven-plugin#build-image.build-image-goal.parameter-details.image] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.include-system-scope[maven-plugin#build-image.build-image-goal.parameter-details.include-system-scope] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.include-tools[maven-plugin#build-image.build-image-goal.parameter-details.include-tools] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.includes[maven-plugin#build-image.build-image-goal.parameter-details.includes] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.layers[maven-plugin#build-image.build-image-goal.parameter-details.layers] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.layout[maven-plugin#build-image.build-image-goal.parameter-details.layout] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.layout-factory[maven-plugin#build-image.build-image-goal.parameter-details.layout-factory] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.loader-implementation[maven-plugin#build-image.build-image-goal.parameter-details.loader-implementation] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.main-class[maven-plugin#build-image.build-image-goal.parameter-details.main-class] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.skip[maven-plugin#build-image.build-image-goal.parameter-details.skip] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.source-directory[maven-plugin#build-image.build-image-goal.parameter-details.source-directory] +* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.required-parameters[maven-plugin#build-image.build-image-goal.required-parameters] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal[maven-plugin#build-image.build-image-no-fork-goal] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.optional-parameters[maven-plugin#build-image.build-image-no-fork-goal.optional-parameters] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details[maven-plugin#build-image.build-image-no-fork-goal.parameter-details] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.classifier[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.classifier] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.docker[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.docker] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.exclude-devtools[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.exclude-devtools] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.exclude-docker-compose[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.exclude-docker-compose] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.exclude-group-ids[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.exclude-group-ids] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.excludes[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.excludes] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.image[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.image] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.include-system-scope[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.include-system-scope] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.include-tools[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.include-tools] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.includes[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.includes] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.layers[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.layers] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.layout[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.layout] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.layout-factory[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.layout-factory] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.loader-implementation[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.loader-implementation] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.main-class[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.main-class] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.skip[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.skip] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.source-directory[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.source-directory] +* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.required-parameters[maven-plugin#build-image.build-image-no-fork-goal.required-parameters] +* xref:maven-plugin:build-image.adoc#build-image.customization[maven-plugin#build-image.customization] +* xref:maven-plugin:build-image.adoc#build-image.customization.tags[maven-plugin#build-image.customization.tags] +* xref:maven-plugin:build-image.adoc#build-image.docker-daemon[maven-plugin#build-image.docker-daemon] +* xref:maven-plugin:build-image.adoc#build-image.docker-registry[maven-plugin#build-image.docker-registry] +* xref:maven-plugin:build-image.adoc#build-image.examples[maven-plugin#build-image.examples] +* xref:maven-plugin:build-image.adoc#build-image.examples.builder-configuration[maven-plugin#build-image.examples.builder-configuration] +* xref:maven-plugin:build-image.adoc#build-image.examples.buildpacks[maven-plugin#build-image.examples.buildpacks] +* xref:maven-plugin:build-image.adoc#build-image.examples.caches[maven-plugin#build-image.examples.caches] +* xref:maven-plugin:build-image.adoc#build-image.examples.custom-image-builder[maven-plugin#build-image.examples.custom-image-builder] +* xref:maven-plugin:build-image.adoc#build-image.examples.custom-image-name[maven-plugin#build-image.examples.custom-image-name] +* xref:maven-plugin:build-image.adoc#build-image.examples.docker[maven-plugin#build-image.examples.docker] +* xref:maven-plugin:build-image.adoc#build-image.examples.docker.auth[maven-plugin#build-image.examples.docker.auth] +* xref:maven-plugin:build-image.adoc#build-image.examples.docker.colima[maven-plugin#build-image.examples.docker.colima] +* xref:maven-plugin:build-image.adoc#build-image.examples.docker.minikube[maven-plugin#build-image.examples.docker.minikube] +* xref:maven-plugin:build-image.adoc#build-image.examples.docker.podman[maven-plugin#build-image.examples.docker.podman] +* xref:maven-plugin:build-image.adoc#build-image.examples.publish[maven-plugin#build-image.examples.publish] +* xref:maven-plugin:build-image.adoc#build-image.examples.runtime-jvm-configuration[maven-plugin#build-image.examples.runtime-jvm-configuration] +* xref:maven-plugin:build-info.adoc#build-info[maven-plugin#build-info] +* xref:maven-plugin:build-info.adoc#build-info.build-info-goal[maven-plugin#build-info.build-info-goal] +* xref:maven-plugin:build-info.adoc#build-info.build-info-goal.optional-parameters[maven-plugin#build-info.build-info-goal.optional-parameters] +* xref:maven-plugin:build-info.adoc#build-info.build-info-goal.parameter-details[maven-plugin#build-info.build-info-goal.parameter-details] +* xref:maven-plugin:build-info.adoc#build-info.build-info-goal.parameter-details.additional-properties[maven-plugin#build-info.build-info-goal.parameter-details.additional-properties] +* xref:maven-plugin:build-info.adoc#build-info.build-info-goal.parameter-details.exclude-info-properties[maven-plugin#build-info.build-info-goal.parameter-details.exclude-info-properties] +* xref:maven-plugin:build-info.adoc#build-info.build-info-goal.parameter-details.output-file[maven-plugin#build-info.build-info-goal.parameter-details.output-file] +* xref:maven-plugin:build-info.adoc#build-info.build-info-goal.parameter-details.skip[maven-plugin#build-info.build-info-goal.parameter-details.skip] +* xref:maven-plugin:build-info.adoc#build-info.build-info-goal.parameter-details.time[maven-plugin#build-info.build-info-goal.parameter-details.time] +* xref:maven-plugin:getting-started.adoc#getting-started[maven-plugin#getting-started] +* xref:maven-plugin:goals.adoc#goals[maven-plugin#goals] +* xref:maven-plugin:help.adoc#help[maven-plugin#help] +* xref:maven-plugin:help.adoc#help.help-goal[maven-plugin#help.help-goal] +* xref:maven-plugin:help.adoc#help.help-goal.optional-parameters[maven-plugin#help.help-goal.optional-parameters] +* xref:maven-plugin:help.adoc#help.help-goal.parameter-details[maven-plugin#help.help-goal.parameter-details] +* xref:maven-plugin:help.adoc#help.help-goal.parameter-details.detail[maven-plugin#help.help-goal.parameter-details.detail] +* xref:maven-plugin:help.adoc#help.help-goal.parameter-details.goal[maven-plugin#help.help-goal.parameter-details.goal] +* xref:maven-plugin:help.adoc#help.help-goal.parameter-details.indent-size[maven-plugin#help.help-goal.parameter-details.indent-size] +* xref:maven-plugin:help.adoc#help.help-goal.parameter-details.line-length[maven-plugin#help.help-goal.parameter-details.line-length] +* xref:maven-plugin:index.adoc#maven-plugin[maven-plugin#maven-plugin] +* xref:maven-plugin:integration-tests.adoc#integration-tests[maven-plugin#integration-tests] +* xref:maven-plugin:integration-tests.adoc#integration-tests.examples[maven-plugin#integration-tests.examples] +* xref:maven-plugin:integration-tests.adoc#integration-tests.examples.jmx-port[maven-plugin#integration-tests.examples.jmx-port] +* xref:maven-plugin:integration-tests.adoc#integration-tests.examples.random-port[maven-plugin#integration-tests.examples.random-port] +* xref:maven-plugin:integration-tests.adoc#integration-tests.examples.skip[maven-plugin#integration-tests.examples.skip] +* xref:maven-plugin:integration-tests.adoc#integration-tests.no-starter-parent[maven-plugin#integration-tests.no-starter-parent] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal[maven-plugin#integration-tests.start-goal] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.optional-parameters[maven-plugin#integration-tests.start-goal.optional-parameters] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details[maven-plugin#integration-tests.start-goal.parameter-details] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.add-resources[maven-plugin#integration-tests.start-goal.parameter-details.add-resources] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.additional-classpath-elements[maven-plugin#integration-tests.start-goal.parameter-details.additional-classpath-elements] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.agents[maven-plugin#integration-tests.start-goal.parameter-details.agents] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.arguments[maven-plugin#integration-tests.start-goal.parameter-details.arguments] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.classes-directory[maven-plugin#integration-tests.start-goal.parameter-details.classes-directory] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.commandline-arguments[maven-plugin#integration-tests.start-goal.parameter-details.commandline-arguments] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.additional-classpath-elements[maven-plugin#integration-tests.start-goal.parameter-details.directories] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.environment-variables[maven-plugin#integration-tests.start-goal.parameter-details.environment-variables] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.exclude-group-ids[maven-plugin#integration-tests.start-goal.parameter-details.exclude-group-ids] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.excludes[maven-plugin#integration-tests.start-goal.parameter-details.excludes] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.includes[maven-plugin#integration-tests.start-goal.parameter-details.includes] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.jmx-name[maven-plugin#integration-tests.start-goal.parameter-details.jmx-name] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.jmx-port[maven-plugin#integration-tests.start-goal.parameter-details.jmx-port] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.jvm-arguments[maven-plugin#integration-tests.start-goal.parameter-details.jvm-arguments] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.main-class[maven-plugin#integration-tests.start-goal.parameter-details.main-class] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.max-attempts[maven-plugin#integration-tests.start-goal.parameter-details.max-attempts] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.noverify[maven-plugin#integration-tests.start-goal.parameter-details.noverify] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.profiles[maven-plugin#integration-tests.start-goal.parameter-details.profiles] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.skip[maven-plugin#integration-tests.start-goal.parameter-details.skip] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.system-property-variables[maven-plugin#integration-tests.start-goal.parameter-details.system-property-variables] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.use-test-classpath[maven-plugin#integration-tests.start-goal.parameter-details.use-test-classpath] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.wait[maven-plugin#integration-tests.start-goal.parameter-details.wait] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.parameter-details.working-directory[maven-plugin#integration-tests.start-goal.parameter-details.working-directory] +* xref:maven-plugin:integration-tests.adoc#integration-tests.start-goal.required-parameters[maven-plugin#integration-tests.start-goal.required-parameters] +* xref:maven-plugin:integration-tests.adoc#integration-tests.stop-goal[maven-plugin#integration-tests.stop-goal] +* xref:maven-plugin:integration-tests.adoc#integration-tests.stop-goal.optional-parameters[maven-plugin#integration-tests.stop-goal.optional-parameters] +* xref:maven-plugin:integration-tests.adoc#integration-tests.stop-goal.parameter-details[maven-plugin#integration-tests.stop-goal.parameter-details] +* xref:maven-plugin:integration-tests.adoc#integration-tests.stop-goal.parameter-details.jmx-name[maven-plugin#integration-tests.stop-goal.parameter-details.jmx-name] +* xref:maven-plugin:integration-tests.adoc#integration-tests.stop-goal.parameter-details.jmx-port[maven-plugin#integration-tests.stop-goal.parameter-details.jmx-port] +* xref:maven-plugin:integration-tests.adoc#integration-tests.stop-goal.parameter-details.skip[maven-plugin#integration-tests.stop-goal.parameter-details.skip] +* xref:maven-plugin:packaging.adoc#packaging[maven-plugin#packaging] +* xref:maven-plugin:packaging.adoc#packaging.examples[maven-plugin#packaging.examples] +* xref:maven-plugin:packaging.adoc#packaging.examples.custom-classifier[maven-plugin#packaging.examples.custom-classifier] +* xref:maven-plugin:packaging.adoc#packaging.examples.custom-layers-configuration[maven-plugin#packaging.examples.custom-layers-configuration] +* xref:maven-plugin:packaging.adoc#packaging.examples.custom-layout[maven-plugin#packaging.examples.custom-layout] +* xref:maven-plugin:packaging.adoc#packaging.examples.custom-name[maven-plugin#packaging.examples.custom-name] +* xref:maven-plugin:packaging.adoc#packaging.examples.exclude-dependency[maven-plugin#packaging.examples.exclude-dependency] +* xref:maven-plugin:packaging.adoc#packaging.examples.layered-archive-tools[maven-plugin#packaging.examples.layered-archive-tools] +* xref:maven-plugin:packaging.adoc#packaging.examples.local-artifact[maven-plugin#packaging.examples.local-artifact] +* xref:maven-plugin:packaging.adoc#packaging.layers[maven-plugin#packaging.layers] +* xref:maven-plugin:packaging.adoc#packaging.layers.configuration[maven-plugin#packaging.layers.configuration] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal[maven-plugin#packaging.repackage-goal] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.optional-parameters[maven-plugin#packaging.repackage-goal.optional-parameters] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details[maven-plugin#packaging.repackage-goal.parameter-details] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.attach[maven-plugin#packaging.repackage-goal.parameter-details.attach] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.classifier[maven-plugin#packaging.repackage-goal.parameter-details.classifier] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.embedded-launch-script[maven-plugin#packaging.repackage-goal.parameter-details.embedded-launch-script] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.embedded-launch-script-properties[maven-plugin#packaging.repackage-goal.parameter-details.embedded-launch-script-properties] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.exclude-devtools[maven-plugin#packaging.repackage-goal.parameter-details.exclude-devtools] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.exclude-docker-compose[maven-plugin#packaging.repackage-goal.parameter-details.exclude-docker-compose] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.exclude-group-ids[maven-plugin#packaging.repackage-goal.parameter-details.exclude-group-ids] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.excludes[maven-plugin#packaging.repackage-goal.parameter-details.excludes] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.executable[maven-plugin#packaging.repackage-goal.parameter-details.executable] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.include-system-scope[maven-plugin#packaging.repackage-goal.parameter-details.include-system-scope] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.include-tools[maven-plugin#packaging.repackage-goal.parameter-details.include-tools] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.includes[maven-plugin#packaging.repackage-goal.parameter-details.includes] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.layers[maven-plugin#packaging.repackage-goal.parameter-details.layers] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.layout[maven-plugin#packaging.repackage-goal.parameter-details.layout] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.layout-factory[maven-plugin#packaging.repackage-goal.parameter-details.layout-factory] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.loader-implementation[maven-plugin#packaging.repackage-goal.parameter-details.loader-implementation] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.main-class[maven-plugin#packaging.repackage-goal.parameter-details.main-class] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.output-directory[maven-plugin#packaging.repackage-goal.parameter-details.output-directory] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.output-timestamp[maven-plugin#packaging.repackage-goal.parameter-details.output-timestamp] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.requires-unpack[maven-plugin#packaging.repackage-goal.parameter-details.requires-unpack] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.skip[maven-plugin#packaging.repackage-goal.parameter-details.skip] +* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.required-parameters[maven-plugin#packaging.repackage-goal.required-parameters] +* xref:maven-plugin:run.adoc#run[maven-plugin#run] +* xref:maven-plugin:run.adoc#run.examples[maven-plugin#run.examples] +* xref:maven-plugin:run.adoc#run.examples.debug[maven-plugin#run.examples.debug] +* xref:maven-plugin:run.adoc#run.examples.environment-variables[maven-plugin#run.examples.environment-variables] +* xref:maven-plugin:run.adoc#run.examples.specify-active-profiles[maven-plugin#run.examples.specify-active-profiles] +* xref:maven-plugin:run.adoc#run.examples.system-properties[maven-plugin#run.examples.system-properties] +* xref:maven-plugin:run.adoc#run.examples.using-application-arguments[maven-plugin#run.examples.using-application-arguments] +* xref:maven-plugin:run.adoc#run.run-goal[maven-plugin#run.run-goal] +* xref:maven-plugin:run.adoc#run.run-goal.optional-parameters[maven-plugin#run.run-goal.optional-parameters] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details[maven-plugin#run.run-goal.parameter-details] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.add-resources[maven-plugin#run.run-goal.parameter-details.add-resources] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.additional-classpath-elements[maven-plugin#run.run-goal.parameter-details.additional-classpath-elements] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.agents[maven-plugin#run.run-goal.parameter-details.agents] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.arguments[maven-plugin#run.run-goal.parameter-details.arguments] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.classes-directory[maven-plugin#run.run-goal.parameter-details.classes-directory] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.commandline-arguments[maven-plugin#run.run-goal.parameter-details.commandline-arguments] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.additional-classpath-elements[maven-plugin#run.run-goal.parameter-details.directories] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.environment-variables[maven-plugin#run.run-goal.parameter-details.environment-variables] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.exclude-group-ids[maven-plugin#run.run-goal.parameter-details.exclude-group-ids] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.excludes[maven-plugin#run.run-goal.parameter-details.excludes] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.includes[maven-plugin#run.run-goal.parameter-details.includes] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.jvm-arguments[maven-plugin#run.run-goal.parameter-details.jvm-arguments] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.main-class[maven-plugin#run.run-goal.parameter-details.main-class] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.noverify[maven-plugin#run.run-goal.parameter-details.noverify] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.optimized-launch[maven-plugin#run.run-goal.parameter-details.optimized-launch] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.profiles[maven-plugin#run.run-goal.parameter-details.profiles] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.skip[maven-plugin#run.run-goal.parameter-details.skip] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.system-property-variables[maven-plugin#run.run-goal.parameter-details.system-property-variables] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.use-test-classpath[maven-plugin#run.run-goal.parameter-details.use-test-classpath] +* xref:maven-plugin:run.adoc#run.run-goal.parameter-details.working-directory[maven-plugin#run.run-goal.parameter-details.working-directory] +* xref:maven-plugin:run.adoc#run.run-goal.required-parameters[maven-plugin#run.run-goal.required-parameters] +* xref:maven-plugin:run.adoc#run.test-run-goal[maven-plugin#run.test-run-goal] +* xref:maven-plugin:run.adoc#run.test-run-goal.optional-parameters[maven-plugin#run.test-run-goal.optional-parameters] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details[maven-plugin#run.test-run-goal.parameter-details] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.add-resources[maven-plugin#run.test-run-goal.parameter-details.add-resources] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.additional-classpath-elements[maven-plugin#run.test-run-goal.parameter-details.additional-classpath-elements] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.agents[maven-plugin#run.test-run-goal.parameter-details.agents] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.arguments[maven-plugin#run.test-run-goal.parameter-details.arguments] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.classes-directory[maven-plugin#run.test-run-goal.parameter-details.classes-directory] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.commandline-arguments[maven-plugin#run.test-run-goal.parameter-details.commandline-arguments] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.additional-classpath-elements[maven-plugin#run.test-run-goal.parameter-details.directories] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.environment-variables[maven-plugin#run.test-run-goal.parameter-details.environment-variables] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.exclude-group-ids[maven-plugin#run.test-run-goal.parameter-details.exclude-group-ids] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.excludes[maven-plugin#run.test-run-goal.parameter-details.excludes] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.includes[maven-plugin#run.test-run-goal.parameter-details.includes] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.jvm-arguments[maven-plugin#run.test-run-goal.parameter-details.jvm-arguments] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.main-class[maven-plugin#run.test-run-goal.parameter-details.main-class] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.noverify[maven-plugin#run.test-run-goal.parameter-details.noverify] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.optimized-launch[maven-plugin#run.test-run-goal.parameter-details.optimized-launch] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.profiles[maven-plugin#run.test-run-goal.parameter-details.profiles] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.skip[maven-plugin#run.test-run-goal.parameter-details.skip] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.system-property-variables[maven-plugin#run.test-run-goal.parameter-details.system-property-variables] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.test-classes-directory[maven-plugin#run.test-run-goal.parameter-details.test-classes-directory] +* xref:maven-plugin:run.adoc#run.test-run-goal.parameter-details.working-directory[maven-plugin#run.test-run-goal.parameter-details.working-directory] +* xref:maven-plugin:run.adoc#run.test-run-goal.required-parameters[maven-plugin#run.test-run-goal.required-parameters] +* xref:maven-plugin:using.adoc#using[maven-plugin#using] +* xref:maven-plugin:using.adoc#using.import[maven-plugin#using.import] +* xref:maven-plugin:using.adoc#using.overriding-command-line[maven-plugin#using.overriding-command-line] +* xref:maven-plugin:using.adoc#using.parent-pom[maven-plugin#using.parent-pom] +* xref:reference:actuator/auditing.adoc#actuator.auditing[#actuator.auditing] +* xref:reference:actuator/auditing.adoc#actuator.auditing.custom[#actuator.auditing.custom] +* xref:reference:actuator/cloud-foundry.adoc#actuator.cloud-foundry[#actuator.cloud-foundry] +* xref:reference:actuator/cloud-foundry.adoc#actuator.cloud-foundry.custom-context-path[#actuator.cloud-foundry.custom-context-path] +* xref:reference:actuator/cloud-foundry.adoc#actuator.cloud-foundry.disable[#actuator.cloud-foundry.disable] +* xref:reference:actuator/cloud-foundry.adoc#actuator.cloud-foundry.ssl[#actuator.cloud-foundry.ssl] +* xref:reference:actuator/enabling.adoc#actuator.enabling[#actuator.enabling] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints[#actuator.endpoints] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.caching[#actuator.endpoints.caching] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.cors[#actuator.endpoints.cors] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.enabling[#actuator.endpoints.enabling] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.exposing[#actuator.endpoints.exposing] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.health[#actuator.endpoints.health] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.health.auto-configured-health-indicators[#actuator.endpoints.health.auto-configured-health-indicators] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.health.auto-configured-reactive-health-indicators[#actuator.endpoints.health.auto-configured-reactive-health-indicators] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.health.datasource[#actuator.endpoints.health.datasource] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.health.groups[#actuator.endpoints.health.groups] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.health.reactive-health-indicators[#actuator.endpoints.health.reactive-health-indicators] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.health.writing-custom-health-indicators[#actuator.endpoints.health.writing-custom-health-indicators] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.hypermedia[#actuator.endpoints.hypermedia] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.implementing-custom[#actuator.endpoints.implementing-custom] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.implementing-custom.input[#actuator.endpoints.implementing-custom.input] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.implementing-custom.input.conversion[#actuator.endpoints.implementing-custom.input.conversion] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.implementing-custom.web[#actuator.endpoints.implementing-custom.web] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.implementing-custom.web.consumes-predicates[#actuator.endpoints.implementing-custom.web.consumes-predicates] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.implementing-custom.web.method-predicates[#actuator.endpoints.implementing-custom.web.method-predicates] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.implementing-custom.web.path-predicates[#actuator.endpoints.implementing-custom.web.path-predicates] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.implementing-custom.web.produces-predicates[#actuator.endpoints.implementing-custom.web.produces-predicates] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.implementing-custom.web.range-requests[#actuator.endpoints.implementing-custom.web.range-requests] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.implementing-custom.web.request-predicates[#actuator.endpoints.implementing-custom.web.request-predicates] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.implementing-custom.web.response-status[#actuator.endpoints.implementing-custom.web.response-status] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.implementing-custom.web.security[#actuator.endpoints.implementing-custom.web.security] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.info[#actuator.endpoints.info] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.info.auto-configured-info-contributors[#actuator.endpoints.info.auto-configured-info-contributors] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.info.build-information[#actuator.endpoints.info.build-information] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.info.custom-application-information[#actuator.endpoints.info.custom-application-information] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.info.git-commit-information[#actuator.endpoints.info.git-commit-information] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.info.java-information[#actuator.endpoints.info.java-information] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.info.os-information[#actuator.endpoints.info.os-information] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.info.process-information[#actuator.endpoints.info.process-information] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.info.writing-custom-info-contributors[#actuator.endpoints.info.writing-custom-info-contributors] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.kubernetes-probes[#actuator.endpoints.kubernetes-probes] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.kubernetes-probes.external-state[#actuator.endpoints.kubernetes-probes.external-state] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.kubernetes-probes.lifecycle[#actuator.endpoints.kubernetes-probes.lifecycle] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.sanitization[#howto-sanitize-sensitive-values] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.sanitization[#actuator.endpoints.sanitization] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.sanitization[#howto-sanitize-sensible-values] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.sbom[#actuator.endpoints.sbom] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.sbom.additional[#actuator.endpoints.sbom.additional] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.sbom.other-formats[#actuator.endpoints.sbom.other-formats] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.security[#actuator.endpoints.security] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.security[#boot-features-security-actuator] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.security.csrf[#boot-features-security-csrf] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.security.csrf[#actuator.endpoints.security.csrf] +* xref:reference:actuator/http-exchanges.adoc#actuator.http-exchanges[#production-ready-http-tracing] +* xref:reference:actuator/http-exchanges.adoc#actuator.http-exchanges[#actuator.http-exchanges] +* xref:reference:actuator/http-exchanges.adoc#actuator.http-exchanges.custom[#actuator.http-exchanges.custom] +* xref:reference:actuator/http-exchanges.adoc#actuator.http-exchanges.custom[#production-ready-http-tracing-custom] +* xref:reference:actuator/index.adoc#actuator[#actuator] +* xref:reference:actuator/jmx.adoc#actuator.jmx[#actuator.jmx] +* xref:reference:actuator/jmx.adoc#actuator.jmx[#boot-features-jmx] +* xref:reference:actuator/jmx.adoc#actuator.jmx.custom-mbean-names[#actuator.jmx.custom-mbean-names] +* xref:reference:actuator/jmx.adoc#actuator.jmx.disable-jmx-endpoints[#actuator.jmx.disable-jmx-endpoints] +* xref:reference:actuator/loggers.adoc#actuator.loggers[#actuator.loggers] +* xref:reference:actuator/loggers.adoc#actuator.loggers.configure[#actuator.loggers.configure] +* xref:reference:actuator/metrics.adoc#actuator.metrics[#actuator.metrics] +* xref:reference:actuator/metrics.adoc#actuator.metrics.customizing[#actuator.metrics.customizing] +* xref:reference:actuator/metrics.adoc#actuator.metrics.customizing.common-tags[#actuator.metrics.customizing.common-tags] +* xref:reference:actuator/metrics.adoc#actuator.metrics.customizing.per-meter-properties[#actuator.metrics.customizing.per-meter-properties] +* xref:reference:actuator/metrics.adoc#actuator.metrics.endpoint[#actuator.metrics.endpoint] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export[#actuator.metrics.export] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.appoptics[#actuator.metrics.export.appoptics] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.atlas[#actuator.metrics.export.atlas] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.datadog[#actuator.metrics.export.datadog] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.dynatrace[#actuator.metrics.export.dynatrace] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.dynatrace.v1-api[#actuator.metrics.export.dynatrace.v1-api] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.dynatrace.v2-api[#actuator.metrics.export.dynatrace.v2-api] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.dynatrace.v2-api.auto-config[#actuator.metrics.export.dynatrace.v2-api.auto-config] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.dynatrace.v2-api.manual-config[#actuator.metrics.export.dynatrace.v2-api.manual-config] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.dynatrace.version-independent-settings[#actuator.metrics.export.dynatrace.version-independent-settings] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.elastic[#actuator.metrics.export.elastic] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.ganglia[#actuator.metrics.export.ganglia] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.graphite[#actuator.metrics.export.graphite] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.humio[#actuator.metrics.export.humio] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.influx[#actuator.metrics.export.influx] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.jmx[#actuator.metrics.export.jmx] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.kairos[#actuator.metrics.export.kairos] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.newrelic[#actuator.metrics.export.newrelic] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.otlp[#actuator.metrics.export.otlp] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.prometheus[#actuator.metrics.export.prometheus] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.signalfx[#actuator.metrics.export.signalfx] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.simple[#actuator.metrics.export.simple] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.stackdriver[#actuator.metrics.export.stackdriver] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.statsd[#actuator.metrics.export.statsd] +* xref:reference:actuator/metrics.adoc#actuator.metrics.export.wavefront[#actuator.metrics.export.wavefront] +* xref:reference:actuator/metrics.adoc#actuator.metrics.getting-started[#actuator.metrics.getting-started] +* xref:reference:actuator/metrics.adoc#actuator.metrics.micrometer-observation[#actuator.metrics.micrometer-observation] +* xref:reference:actuator/metrics.adoc#actuator.metrics.registering-custom[#actuator.metrics.registering-custom] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported[#actuator.metrics.supported] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.application-startup[#actuator.metrics.supported.application-startup] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.cache[#actuator.metrics.supported.cache] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.hibernate[#actuator.metrics.supported.hibernate] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.http-clients[#actuator.metrics.supported.http-clients] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.jdbc[#actuator.metrics.supported.jdbc] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.jersey[#actuator.metrics.supported.jersey] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.jetty[#actuator.metrics.supported.jetty] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.jms[#actuator.metrics.supported.jms] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.jvm[#actuator.metrics.supported.jvm] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.kafka[#actuator.metrics.supported.kafka] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.logger[#actuator.metrics.supported.logger] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.mongodb[#actuator.metrics.supported.mongodb] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.mongodb.command[#actuator.metrics.supported.mongodb.command] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.mongodb.connection-pool[#actuator.metrics.supported.mongodb.connection-pool] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.rabbitmq[#actuator.metrics.supported.rabbitmq] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.redis[#actuator.metrics.supported.redis] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.spring-batch[#actuator.metrics.supported.spring-batch] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.spring-data-repository[#actuator.metrics.supported.spring-data-repository] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.spring-graphql[#actuator.metrics.supported.spring-graphql] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.spring-integration[#actuator.metrics.supported.spring-integration] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.spring-mvc[#actuator.metrics.supported.spring-mvc] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.spring-webflux[#actuator.metrics.supported.spring-webflux] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.system[#actuator.metrics.supported.system] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.tasks[#actuator.metrics.supported.tasks] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.timed-annotation[#actuator.metrics.supported.timed-annotation] +* xref:reference:actuator/metrics.adoc#actuator.metrics.supported.tomcat[#actuator.metrics.supported.tomcat] +* xref:reference:actuator/monitoring.adoc#actuator.monitoring[#actuator.monitoring] +* xref:reference:actuator/monitoring.adoc#actuator.monitoring.customizing-management-server-address[#actuator.monitoring.customizing-management-server-address] +* xref:reference:actuator/monitoring.adoc#actuator.monitoring.customizing-management-server-context-path[#actuator.monitoring.customizing-management-server-context-path] +* xref:reference:actuator/monitoring.adoc#actuator.monitoring.customizing-management-server-port[#actuator.monitoring.customizing-management-server-port] +* xref:reference:actuator/monitoring.adoc#actuator.monitoring.disabling-http-endpoints[#actuator.monitoring.disabling-http-endpoints] +* xref:reference:actuator/monitoring.adoc#actuator.monitoring.management-specific-ssl[#actuator.monitoring.management-specific-ssl] +* xref:reference:actuator/observability.adoc#actuator.observability[#actuator.observability] +* xref:reference:actuator/observability.adoc#actuator.observability.annotations[#actuator.observability.annotations] +* xref:reference:actuator/observability.adoc#actuator.observability.common-tags[#actuator.observability.common-tags] +* xref:reference:actuator/observability.adoc#actuator.observability.opentelemetry[#actuator.observability.opentelemetry] +* xref:reference:actuator/observability.adoc#actuator.observability.preventing-observations[#actuator.observability.preventing-observations] +* xref:reference:actuator/process-monitoring.adoc#actuator.process-monitoring[#actuator.process-monitoring] +* xref:reference:actuator/process-monitoring.adoc#actuator.process-monitoring.configuration[#actuator.process-monitoring.configuration] +* xref:reference:actuator/process-monitoring.adoc#actuator.process-monitoring.programmatically[#actuator.process-monitoring.programmatically] +* xref:reference:actuator/tracing.adoc#actuator.micrometer-tracing[#actuator.micrometer-tracing] +* xref:reference:actuator/tracing.adoc#actuator.micrometer-tracing.baggage[#actuator.micrometer-tracing.baggage] +* xref:reference:actuator/tracing.adoc#actuator.micrometer-tracing.creating-spans[#actuator.micrometer-tracing.creating-spans] +* xref:reference:actuator/tracing.adoc#actuator.micrometer-tracing.getting-started[#actuator.micrometer-tracing.getting-started] +* xref:reference:actuator/tracing.adoc#actuator.micrometer-tracing.logging[#actuator.micrometer-tracing.logging] +* xref:reference:actuator/tracing.adoc#actuator.micrometer-tracing.micrometer-observation[#actuator.micrometer-tracing.micrometer-observation] +* xref:reference:actuator/tracing.adoc#actuator.micrometer-tracing.propagating-traces[#actuator.micrometer-tracing.propagating-traces] +* xref:reference:actuator/tracing.adoc#actuator.micrometer-tracing.tests[#actuator.micrometer-tracing.tests] +* xref:reference:actuator/tracing.adoc#actuator.micrometer-tracing.tracer-implementations[#actuator.micrometer-tracing.tracer-implementations] +* xref:reference:actuator/tracing.adoc#actuator.micrometer-tracing.tracer-implementations.brave-wavefront[#actuator.micrometer-tracing.tracer-implementations.brave-wavefront] +* xref:reference:actuator/tracing.adoc#actuator.micrometer-tracing.tracer-implementations.brave-zipkin[#actuator.micrometer-tracing.tracer-implementations.brave-zipkin] +* xref:reference:actuator/tracing.adoc#actuator.micrometer-tracing.tracer-implementations.otel-otlp[#actuator.micrometer-tracing.tracer-implementations.otel-otlp] +* xref:reference:actuator/tracing.adoc#actuator.micrometer-tracing.tracer-implementations.otel-wavefront[#actuator.micrometer-tracing.tracer-implementations.otel-wavefront] +* xref:reference:actuator/tracing.adoc#actuator.micrometer-tracing.tracer-implementations.otel-zipkin[#actuator.micrometer-tracing.tracer-implementations.otel-zipkin] +* xref:reference:actuator/tracing.adoc#actuator.micrometer-tracing.tracers[#actuator.micrometer-tracing.tracers] +* xref:reference:packaging/container-images/cloud-native-buildpacks.adoc#packaging.container-images.buildpacks[#boot-features-container-images-buildpacks] +* xref:reference:packaging/container-images/cloud-native-buildpacks.adoc#packaging.container-images.buildpacks[#container-images.buildpacks] +* xref:reference:packaging/container-images/dockerfiles.adoc#packaging.container-images.dockerfiles[#container-images.dockerfiles] +* xref:reference:packaging/container-images/dockerfiles.adoc#packaging.container-images.dockerfiles[#boot-features-container-images-docker] +* xref:reference:packaging/container-images/efficient-images.adoc#packaging.container-images.efficient-images[#container-images.efficient-images] +* xref:reference:packaging/container-images/efficient-images.adoc#packaging.container-images.efficient-images[#boot-features-container-images] +* xref:reference:packaging/container-images/efficient-images.adoc#packaging.container-images.efficient-images[#boot-features-container-images-building] +* xref:reference:packaging/container-images/efficient-images.adoc#packaging.container-images.efficient-images.layering[#container-images.efficient-images.layering] +* xref:reference:packaging/container-images/efficient-images.adoc#packaging.container-images.efficient-images.layering[#boot-layering-docker-images] +* xref:reference:packaging/container-images/index.adoc#packaging.container-images[#container-images] +* xref:reference:data/index.adoc#data[#data] +* xref:reference:data/nosql.adoc#data.nosql[#boot-features-nosql] +* xref:reference:data/nosql.adoc#data.nosql[#data.nosql] +* xref:reference:data/nosql.adoc#data.nosql.cassandra[#data.nosql.cassandra] +* xref:reference:data/nosql.adoc#data.nosql.cassandra[#boot-features-cassandra] +* xref:reference:data/nosql.adoc#data.nosql.cassandra.connecting[#data.nosql.cassandra.connecting] +* xref:reference:data/nosql.adoc#data.nosql.cassandra.connecting[#boot-features-connecting-to-cassandra] +* xref:reference:data/nosql.adoc#data.nosql.cassandra.repositories[#data.nosql.cassandra.repositories] +* xref:reference:data/nosql.adoc#data.nosql.cassandra.repositories[#boot-features-spring-data-cassandra-repositories] +* xref:reference:data/nosql.adoc#data.nosql.couchbase[#data.nosql.couchbase] +* xref:reference:data/nosql.adoc#data.nosql.couchbase[#boot-features-couchbase] +* xref:reference:data/nosql.adoc#data.nosql.couchbase.connecting[#boot-features-connecting-to-couchbase] +* xref:reference:data/nosql.adoc#data.nosql.couchbase.connecting[#data.nosql.couchbase.connecting] +* xref:reference:data/nosql.adoc#data.nosql.couchbase.repositories[#boot-features-spring-data-couchbase-repositories] +* xref:reference:data/nosql.adoc#data.nosql.couchbase.repositories[#data.nosql.couchbase.repositories] +* xref:reference:data/nosql.adoc#data.nosql.elasticsearch[#data.nosql.elasticsearch] +* xref:reference:data/nosql.adoc#data.nosql.elasticsearch[#boot-features-elasticsearch] +* xref:reference:data/nosql.adoc#data.nosql.elasticsearch[#boot-features-connecting-to-elasticsearch-reactive-rest] +* xref:reference:data/nosql.adoc#data.nosql.elasticsearch.connecting-using-rest[#boot-features-connecting-to-elasticsearch-rest] +* xref:reference:data/nosql.adoc#data.nosql.elasticsearch.connecting-using-rest[#data.nosql.elasticsearch.connecting-using-rest] +* xref:reference:data/nosql.adoc#data.nosql.elasticsearch.connecting-using-rest.javaapiclient[#data.nosql.elasticsearch.connecting-using-rest.javaapiclient] +* xref:reference:data/nosql.adoc#data.nosql.elasticsearch.connecting-using-rest.reactiveclient[#data.nosql.elasticsearch.connecting-using-rest.reactiveclient] +* xref:reference:data/nosql.adoc#data.nosql.elasticsearch.connecting-using-rest.restclient[#data.nosql.elasticsearch.connecting-using-rest.restclient] +* xref:reference:data/nosql.adoc#data.nosql.elasticsearch.connecting-using-spring-data[#data.nosql.elasticsearch.connecting-using-spring-data] +* xref:reference:data/nosql.adoc#data.nosql.elasticsearch.connecting-using-spring-data[#boot-features-connecting-to-elasticsearch-spring-data] +* xref:reference:data/nosql.adoc#data.nosql.elasticsearch.repositories[#data.nosql.elasticsearch.repositories] +* xref:reference:data/nosql.adoc#data.nosql.elasticsearch.repositories[#boot-features-spring-data-elasticsearch-repositories] +* xref:reference:data/nosql.adoc#data.nosql.influxdb[#boot-features-influxdb] +* xref:reference:data/nosql.adoc#data.nosql.influxdb[#data.nosql.influxdb] +* xref:reference:data/nosql.adoc#data.nosql.influxdb.connecting[#data.nosql.influxdb.connecting] +* xref:reference:data/nosql.adoc#data.nosql.influxdb.connecting[#boot-features-connecting-to-influxdb] +* xref:reference:data/nosql.adoc#data.nosql.ldap[#boot-features-ldap] +* xref:reference:data/nosql.adoc#data.nosql.ldap[#data.nosql.ldap] +* xref:reference:data/nosql.adoc#data.nosql.ldap.connecting[#boot-features-ldap-connecting] +* xref:reference:data/nosql.adoc#data.nosql.ldap.connecting[#data.nosql.ldap.connecting] +* xref:reference:data/nosql.adoc#data.nosql.ldap.embedded[#data.nosql.ldap.embedded] +* xref:reference:data/nosql.adoc#data.nosql.ldap.embedded[#boot-features-ldap-embedded] +* xref:reference:data/nosql.adoc#data.nosql.ldap.repositories[#boot-features-ldap-spring-data-repositories] +* xref:reference:data/nosql.adoc#data.nosql.ldap.repositories[#data.nosql.ldap.repositories] +* xref:reference:data/nosql.adoc#data.nosql.mongodb[#data.nosql.mongodb] +* xref:reference:data/nosql.adoc#data.nosql.mongodb[#boot-features-mongodb] +* xref:reference:data/nosql.adoc#data.nosql.mongodb.connecting[#data.nosql.mongodb.connecting] +* xref:reference:data/nosql.adoc#data.nosql.mongodb.connecting[#boot-features-connecting-to-mongodb] +* xref:reference:data/nosql.adoc#data.nosql.mongodb.repositories[#data.nosql.mongodb.repositories] +* xref:reference:data/nosql.adoc#data.nosql.mongodb.repositories[#boot-features-spring-data-mongo-repositories] +* xref:reference:data/nosql.adoc#data.nosql.mongodb.repositories[#boot-features-spring-data-mongodb-repositories] +* xref:reference:data/nosql.adoc#data.nosql.mongodb.template[#data.nosql.mongodb.template] +* xref:reference:data/nosql.adoc#data.nosql.mongodb.template[#boot-features-mongo-template] +* xref:reference:data/nosql.adoc#data.nosql.neo4j[#data.nosql.neo4j] +* xref:reference:data/nosql.adoc#data.nosql.neo4j[#boot-features-neo4j] +* xref:reference:data/nosql.adoc#data.nosql.neo4j.connecting[#data.nosql.neo4j.connecting] +* xref:reference:data/nosql.adoc#data.nosql.neo4j.connecting[#boot-features-connecting-to-neo4j] +* xref:reference:data/nosql.adoc#data.nosql.neo4j.repositories[#data.nosql.neo4j.repositories] +* xref:reference:data/nosql.adoc#data.nosql.neo4j.repositories[#boot-features-spring-data-neo4j-repositories] +* xref:reference:data/nosql.adoc#data.nosql.redis[#data.nosql.redis] +* xref:reference:data/nosql.adoc#data.nosql.redis[#boot-features-redis] +* xref:reference:data/nosql.adoc#data.nosql.redis.connecting[#boot-features-connecting-to-redis] +* xref:reference:data/nosql.adoc#data.nosql.redis.connecting[#data.nosql.redis.connecting] +* xref:reference:data/sql.adoc#data.sql[#boot-features-sql] +* xref:reference:data/sql.adoc#data.sql[#data.sql] +* xref:reference:data/sql.adoc#data.sql.datasource[#boot-features-configure-datasource] +* xref:reference:data/sql.adoc#data.sql.datasource[#data.sql.datasource] +* xref:reference:data/sql.adoc#data.sql.datasource.configuration[#data.sql.datasource.configuration] +* xref:reference:data/sql.adoc#data.sql.datasource.configuration[#boot-features-connect-to-production-database-configuration] +* xref:reference:data/sql.adoc#data.sql.datasource.connection-pool[#boot-features-connect-to-production-database-connection-pool] +* xref:reference:data/sql.adoc#data.sql.datasource.connection-pool[#data.sql.datasource.connection-pool] +* xref:reference:data/sql.adoc#data.sql.datasource.embedded[#data.sql.datasource.embedded] +* xref:reference:data/sql.adoc#data.sql.datasource.embedded[#boot-features-embedded-database-support] +* xref:reference:data/sql.adoc#data.sql.datasource.jndi[#boot-features-connecting-to-a-jndi-datasource] +* xref:reference:data/sql.adoc#data.sql.datasource.jndi[#data.sql.datasource.jndi] +* xref:reference:data/sql.adoc#data.sql.datasource.production[#boot-features-connect-to-production-database] +* xref:reference:data/sql.adoc#data.sql.datasource.production[#data.sql.datasource.production] +* xref:reference:data/sql.adoc#data.sql.h2-web-console[#data.sql.h2-web-console] +* xref:reference:data/sql.adoc#data.sql.h2-web-console[#boot-features-sql-h2-console] +* xref:reference:data/sql.adoc#data.sql.h2-web-console.custom-path[#boot-features-sql-h2-console-custom-path] +* xref:reference:data/sql.adoc#data.sql.h2-web-console.custom-path[#data.sql.h2-web-console.custom-path] +* xref:reference:data/sql.adoc#data.sql.h2-web-console.spring-security[#data.sql.h2-web-console.spring-security] +* xref:reference:data/sql.adoc#data.sql.jdbc[#data.sql.jdbc] +* xref:reference:data/sql.adoc#data.sql.jdbc[#boot-features-data-jdbc] +* xref:reference:data/sql.adoc#data.sql.jdbc-client[#data.sql.jdbc-client] +* xref:reference:data/sql.adoc#data.sql.jdbc-template[#boot-features-using-jdbc-template] +* xref:reference:data/sql.adoc#data.sql.jdbc-template[#data.sql.jdbc-template] +* xref:reference:data/sql.adoc#data.sql.jooq[#data.sql.jooq] +* xref:reference:data/sql.adoc#data.sql.jooq[#boot-features-jooq] +* xref:reference:data/sql.adoc#data.sql.jooq.codegen[#data.sql.jooq.codegen] +* xref:reference:data/sql.adoc#data.sql.jooq.codegen[#boot-features-jooq-codegen] +* xref:reference:data/sql.adoc#data.sql.jooq.customizing[#boot-features-jooq-customizing] +* xref:reference:data/sql.adoc#data.sql.jooq.customizing[#data.sql.jooq.customizing] +* xref:reference:data/sql.adoc#data.sql.jooq.dslcontext[#data.sql.jooq.dslcontext] +* xref:reference:data/sql.adoc#data.sql.jooq.dslcontext[#boot-features-jooq-dslcontext] +* xref:reference:data/sql.adoc#data.sql.jooq.sqldialect[#data.sql.jooq.sqldialect] +* xref:reference:data/sql.adoc#data.sql.jooq.sqldialect[#boot-features-jooq-sqldialect] +* xref:reference:data/sql.adoc#data.sql.jpa-and-spring-data[#boot-features-jpa-and-spring-data] +* xref:reference:data/sql.adoc#data.sql.jpa-and-spring-data[#data.sql.jpa-and-spring-data] +* xref:reference:data/sql.adoc#data.sql.jpa-and-spring-data.creating-and-dropping[#boot-features-creating-and-dropping-jpa-databases] +* xref:reference:data/sql.adoc#data.sql.jpa-and-spring-data.creating-and-dropping[#data.sql.jpa-and-spring-data.creating-and-dropping] +* xref:reference:data/sql.adoc#data.sql.jpa-and-spring-data.entity-classes[#data.sql.jpa-and-spring-data.entity-classes] +* xref:reference:data/sql.adoc#data.sql.jpa-and-spring-data.entity-classes[#boot-features-entity-classes] +* xref:reference:data/sql.adoc#data.sql.jpa-and-spring-data.envers-repositories[#data.sql.jpa-and-spring-data.envers-repositories] +* xref:reference:data/sql.adoc#data.sql.jpa-and-spring-data.open-entity-manager-in-view[#data.sql.jpa-and-spring-data.open-entity-manager-in-view] +* xref:reference:data/sql.adoc#data.sql.jpa-and-spring-data.open-entity-manager-in-view[#boot-features-jpa-in-web-environment] +* xref:reference:data/sql.adoc#data.sql.jpa-and-spring-data.repositories[#boot-features-spring-data-jpa-repositories] +* xref:reference:data/sql.adoc#data.sql.jpa-and-spring-data.repositories[#data.sql.jpa-and-spring-data.repositories] +* xref:reference:data/sql.adoc#data.sql.r2dbc[#data.sql.r2dbc] +* xref:reference:data/sql.adoc#data.sql.r2dbc[#boot-features-r2dbc] +* xref:reference:data/sql.adoc#data.sql.r2dbc.embedded[#data.sql.r2dbc.embedded] +* xref:reference:data/sql.adoc#data.sql.r2dbc.embedded[#boot-features-r2dbc-embedded-database] +* xref:reference:data/sql.adoc#data.sql.r2dbc.repositories[#data.sql.r2dbc.repositories] +* xref:reference:data/sql.adoc#data.sql.r2dbc.repositories[#boot-features-spring-data-r2dbc-repositories] +* xref:reference:data/sql.adoc#data.sql.r2dbc.using-database-client[#data.sql.r2dbc.using-database-client] +* xref:reference:data/sql.adoc#data.sql.r2dbc.using-database-client[#boot-features-r2dbc-using-database-client] +* xref:how-to:deployment/cloud.adoc#howto.deployment.cloud[#deployment.cloud] +* xref:how-to:deployment/cloud.adoc#howto.deployment.cloud.aws[#deployment.cloud.aws] +* xref:how-to:deployment/cloud.adoc#howto.deployment.cloud.aws.beanstalk[#deployment.cloud.aws.beanstalk] +* xref:how-to:deployment/cloud.adoc#howto.deployment.cloud.aws.beanstalk.java-se-platform[#deployment.cloud.aws.beanstalk.java-se-platform] +* xref:how-to:deployment/cloud.adoc#howto.deployment.cloud.aws.beanstalk.tomcat-platform[#deployment.cloud.aws.beanstalk.tomcat-platform] +* xref:how-to:deployment/cloud.adoc#howto.deployment.cloud.aws.summary[#deployment.cloud.aws.summary] +* xref:how-to:deployment/cloud.adoc#howto.deployment.cloud.azure[#deployment.cloud.azure] +* xref:how-to:deployment/cloud.adoc#howto.deployment.cloud.boxfuse[#deployment.cloud.boxfuse] +* xref:how-to:deployment/cloud.adoc#howto.deployment.cloud.cloud-foundry[#deployment.cloud.cloud-foundry] +* xref:how-to:deployment/cloud.adoc#howto.deployment.cloud.cloud-foundry.binding-to-services[#deployment.cloud.cloud-foundry.binding-to-services] +* xref:how-to:deployment/cloud.adoc#howto.deployment.cloud.google[#deployment.cloud.google] +* xref:how-to:deployment/cloud.adoc#howto.deployment.cloud.heroku[#deployment.cloud.heroku] +* xref:how-to:deployment/cloud.adoc#howto.deployment.cloud.kubernetes[#deployment.cloud.kubernetes] +* xref:how-to:deployment/cloud.adoc#howto.deployment.cloud.kubernetes.container-lifecycle[#deployment.cloud.kubernetes.container-lifecycle] +* xref:how-to:deployment/cloud.adoc#howto.deployment.cloud.openshift[#deployment.cloud.openshift] +* xref:reference:packaging/efficient.adoc#packaging.efficient[#deployment.efficient] +* xref:reference:packaging/aot.adoc#packaging.aot[#deployment.efficient.aot] +* xref:reference:packaging/checkpoint-restore.adoc#packaging.checkpoint-restore[#deployment.efficient.checkpoint-restore] +* xref:reference:packaging/efficient.adoc#packaging.efficient.unpacking[#containers-deployment] +* xref:reference:packaging/efficient.adoc#packaging.efficient.unpacking[#deployment.containers] +* xref:reference:packaging/efficient.adoc#packaging.efficient.unpacking[#deployment.efficient.unpacking] +* xref:how-to:deployment/index.adoc#howto.deployment[#deployment] +* xref:how-to:deployment/installing.adoc#howto.deployment.installing[#deployment-install-supported-operating-systems] +* xref:how-to:deployment/installing.adoc#howto.deployment.installing[#deployment.installing] +* xref:how-to:deployment/installing.adoc#howto.deployment.installing[#deployment-service] +* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d[#deployment.installing.init-d] +* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d[#deployment-initd-service] +* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization[#deployment-script-customization] +* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization[#deployment.installing.init-d.script-customization] +* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization.when-running[#deployment-script-customization-when-it-runs] +* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization.when-running[#deployment.installing.init-d.script-customization.when-running] +* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization.when-running.conf-file[#deployment.installing.init-d.script-customization.when-running.conf-file] +* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization.when-written[#deployment-script-customization-when-it-written] +* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization.when-written[#deployment.installing.init-d.script-customization.when-written] +* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.securing[#deployment.installing.init-d.securing] +* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.securing[#deployment-initd-service-securing] +* xref:how-to:deployment/installing.adoc#howto.deployment.installing.system-d[#deployment-systemd-service] +* xref:how-to:deployment/installing.adoc#howto.deployment.installing.system-d[#deployment.installing.system-d] +* xref:how-to:deployment/installing.adoc#howto.deployment.installing.windows-services[#deployment.installing.windows-services] +* xref:reference:features/aop.adoc#features.aop[#features.aop] +* xref:reference:features/developing-auto-configuration.adoc#features.developing-auto-configuration[#features.developing-auto-configuration] +* xref:reference:features/developing-auto-configuration.adoc#features.developing-auto-configuration.condition-annotations[#features.developing-auto-configuration.condition-annotations] +* xref:reference:features/developing-auto-configuration.adoc#features.developing-auto-configuration.condition-annotations.bean-conditions[#features.developing-auto-configuration.condition-annotations.bean-conditions] +* xref:reference:features/developing-auto-configuration.adoc#features.developing-auto-configuration.condition-annotations.class-conditions[#features.developing-auto-configuration.condition-annotations.class-conditions] +* xref:reference:features/developing-auto-configuration.adoc#features.developing-auto-configuration.condition-annotations.property-conditions[#features.developing-auto-configuration.condition-annotations.property-conditions] +* xref:reference:features/developing-auto-configuration.adoc#features.developing-auto-configuration.condition-annotations.resource-conditions[#features.developing-auto-configuration.condition-annotations.resource-conditions] +* xref:reference:features/developing-auto-configuration.adoc#features.developing-auto-configuration.condition-annotations.spel-conditions[#features.developing-auto-configuration.condition-annotations.spel-conditions] +* xref:reference:features/developing-auto-configuration.adoc#features.developing-auto-configuration.condition-annotations.web-application-conditions[#features.developing-auto-configuration.condition-annotations.web-application-conditions] +* xref:reference:features/developing-auto-configuration.adoc#features.developing-auto-configuration.custom-starter[#features.developing-auto-configuration.custom-starter] +* xref:reference:features/developing-auto-configuration.adoc#features.developing-auto-configuration.custom-starter.autoconfigure-module[#features.developing-auto-configuration.custom-starter.autoconfigure-module] +* xref:reference:features/developing-auto-configuration.adoc#features.developing-auto-configuration.custom-starter.configuration-keys[#features.developing-auto-configuration.custom-starter.configuration-keys] +* xref:reference:features/developing-auto-configuration.adoc#features.developing-auto-configuration.custom-starter.naming[#features.developing-auto-configuration.custom-starter.naming] +* xref:reference:features/developing-auto-configuration.adoc#features.developing-auto-configuration.custom-starter.starter-module[#features.developing-auto-configuration.custom-starter.starter-module] +* xref:reference:features/developing-auto-configuration.adoc#features.developing-auto-configuration.locating-auto-configuration-candidates[#features.developing-auto-configuration.locating-auto-configuration-candidates] +* xref:reference:features/developing-auto-configuration.adoc#features.developing-auto-configuration.testing[#features.developing-auto-configuration.testing] +* xref:reference:features/developing-auto-configuration.adoc#features.developing-auto-configuration.testing.overriding-classpath[#features.developing-auto-configuration.testing.overriding-classpath] +* xref:reference:features/developing-auto-configuration.adoc#features.developing-auto-configuration.testing.simulating-a-web-context[#features.developing-auto-configuration.testing.simulating-a-web-context] +* xref:reference:features/developing-auto-configuration.adoc#features.developing-auto-configuration.understanding-auto-configured-beans[#features.developing-auto-configuration.understanding-auto-configured-beans] +* xref:reference:features/dev-services.adoc#features.dev-services.docker-compose[#features.docker-compose] +* xref:reference:features/dev-services.adoc#features.dev-services.docker-compose.custom-images[#features.docker-compose.custom-images] +* xref:reference:features/dev-services.adoc#features.dev-services.docker-compose.lifecycle[#features.docker-compose.lifecycle] +* xref:reference:features/dev-services.adoc#features.dev-services.docker-compose.prerequisites[#features.docker-compose.prerequisites] +* xref:reference:features/dev-services.adoc#features.dev-services.docker-compose.profiles[#features.docker-compose.profiles] +* xref:reference:features/dev-services.adoc#features.dev-services.docker-compose.readiness[#features.docker-compose.readiness] +* xref:reference:features/dev-services.adoc#features.dev-services.docker-compose.service-connections[#features.docker-compose.service-connections] +* xref:reference:features/dev-services.adoc#features.dev-services.docker-compose.skipping[#features.docker-compose.skipping] +* xref:reference:features/dev-services.adoc#features.dev-services.docker-compose.specific-file[#features.docker-compose.specific-file] +* xref:reference:features/external-config.adoc#features.external-config[#features.external-config] +* xref:reference:features/external-config.adoc#features.external-config.application-json[#features.external-config.application-json] +* xref:reference:features/external-config.adoc#features.external-config.command-line-args[#features.external-config.command-line-args] +* xref:reference:features/external-config.adoc#features.external-config.encrypting[#features.external-config.encrypting] +* xref:reference:features/external-config.adoc#features.external-config.files[#features.external-config.files] +* xref:reference:features/external-config.adoc#features.external-config.files.activation-properties[#features.external-config.files.activation-properties] +* xref:reference:features/external-config.adoc#features.external-config.files.configtree[#features.external-config.files.configtree] +* xref:reference:features/external-config.adoc#features.external-config.files.importing[#features.external-config.files.importing] +* xref:reference:features/external-config.adoc#features.external-config.files.importing-extensionless[#features.external-config.files.importing-extensionless] +* xref:reference:features/external-config.adoc#features.external-config.files.location-groups[#features.external-config.files.location-groups] +* xref:reference:features/external-config.adoc#features.external-config.files.multi-document[#features.external-config.files.multi-document] +* xref:reference:features/external-config.adoc#features.external-config.files.optional-prefix[#features.external-config.files.optional-prefix] +* xref:reference:features/external-config.adoc#features.external-config.files.profile-specific[#features.external-config.files.profile-specific] +* xref:reference:features/external-config.adoc#features.external-config.files.property-placeholders[#features.external-config.files.property-placeholders] +* xref:reference:features/external-config.adoc#features.external-config.files.wildcard-locations[#features.external-config.files.wildcard-locations] +* xref:reference:features/external-config.adoc#features.external-config.random-values[#features.external-config.random-values] +* xref:reference:features/external-config.adoc#features.external-config.system-environment[#features.external-config.system-environment] +* xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties[#features.external-config.typesafe-configuration-properties] +* xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.constructor-binding[#features.external-config.typesafe-configuration-properties.constructor-binding] +* xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.conversion[#features.external-config.typesafe-configuration-properties.conversion] +* xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.conversion.data-sizes[#features.external-config.typesafe-configuration-properties.conversion.data-sizes] +* xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.conversion.durations[#features.external-config.typesafe-configuration-properties.conversion.durations] +* xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.conversion.periods[#features.external-config.typesafe-configuration-properties.conversion.periods] +* xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.enabling-annotated-types[#features.external-config.typesafe-configuration-properties.enabling-annotated-types] +* xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.java-bean-binding[#features.external-config.typesafe-configuration-properties.java-bean-binding] +* xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.merging-complex-types[#features.external-config.typesafe-configuration-properties.merging-complex-types] +* xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.relaxed-binding[#features.external-config.typesafe-configuration-properties.relaxed-binding] +* xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.relaxed-binding.caching[#features.external-config.typesafe-configuration-properties.relaxed-binding.caching] +* xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.relaxed-binding.environment-variables[#features.external-config.typesafe-configuration-properties.relaxed-binding.environment-variables] +* xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.relaxed-binding.maps[#features.external-config.typesafe-configuration-properties.relaxed-binding.maps] +* xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.third-party-configuration[#features.external-config.typesafe-configuration-properties.third-party-configuration] +* xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.using-annotated-types[#features.external-config.typesafe-configuration-properties.using-annotated-types] +* xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.validation[#features.external-config.typesafe-configuration-properties.validation] +* xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.vs-value-annotation[#features.external-config.typesafe-configuration-properties.vs-value-annotation] +* xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.vs-value-annotation.note[#features.external-config.typesafe-configuration-properties.vs-value-annotation.note] +* xref:reference:features/external-config.adoc#features.external-config.yaml[#features.external-config.yaml] +* xref:reference:features/external-config.adoc#features.external-config.yaml.directly-loading[#features.external-config.yaml.directly-loading] +* xref:reference:features/external-config.adoc#features.external-config.yaml.mapping-to-properties[#features.external-config.yaml.mapping-to-properties] +* xref:reference:features/index.adoc#features[#features] +* xref:reference:features/internationalization.adoc#features.internationalization[#features.internationalization] +* xref:reference:features/json.adoc#features.json[#features.json] +* xref:reference:features/json.adoc#features.json.gson[#features.json.gson] +* xref:reference:features/json.adoc#features.json.jackson[#features.json.jackson] +* xref:reference:features/json.adoc#features.json.jackson.custom-serializers-and-deserializers[#boot-features-json-components] +* xref:reference:features/json.adoc#features.json.jackson.custom-serializers-and-deserializers[#features.developing-web-applications.spring-mvc.json] +* xref:reference:features/json.adoc#features.json.jackson.custom-serializers-and-deserializers[#features.json.jackson.custom-serializers-and-deserializers] +* xref:reference:features/json.adoc#features.json.jackson.mixins[#features.json.jackson.mixins] +* xref:reference:features/json.adoc#features.json.json-b[#features.json.json-b] +* xref:reference:features/kotlin.adoc#features.kotlin[#features.kotlin] +* xref:reference:features/kotlin.adoc#features.kotlin.api[#features.kotlin.api] +* xref:reference:features/kotlin.adoc#features.kotlin.api.extensions[#features.kotlin.api.extensions] +* xref:reference:features/kotlin.adoc#features.kotlin.api.run-application[#features.kotlin.api.run-application] +* xref:reference:features/kotlin.adoc#features.kotlin.configuration-properties[#features.kotlin.configuration-properties] +* xref:reference:features/kotlin.adoc#features.kotlin.dependency-management[#features.kotlin.dependency-management] +* xref:reference:features/kotlin.adoc#features.kotlin.null-safety[#features.kotlin.null-safety] +* xref:reference:features/kotlin.adoc#features.kotlin.requirements[#features.kotlin.requirements] +* xref:reference:features/kotlin.adoc#features.kotlin.resources[#features.kotlin.resources] +* xref:reference:features/kotlin.adoc#features.kotlin.resources.examples[#features.kotlin.resources.examples] +* xref:reference:features/kotlin.adoc#features.kotlin.resources.further-reading[#features.kotlin.resources.further-reading] +* xref:reference:features/kotlin.adoc#features.kotlin.testing[#features.kotlin.testing] +* xref:reference:features/logging.adoc#features.logging[#features.logging] +* xref:reference:features/logging.adoc#features.logging.console-output[#features.logging.console-output] +* xref:reference:features/logging.adoc#features.logging.console-output.color-coded[#features.logging.console-output.color-coded] +* xref:reference:features/logging.adoc#features.logging.custom-log-configuration[#features.logging.custom-log-configuration] +* xref:reference:features/logging.adoc#features.logging.file-output[#features.logging.file-output] +* xref:reference:features/logging.adoc#features.logging.file-rotation[#features.logging.file-rotation] +* xref:reference:features/logging.adoc#features.logging.log-format[#features.logging.log-format] +* xref:reference:features/logging.adoc#features.logging.log-groups[#features.logging.log-groups] +* xref:reference:features/logging.adoc#features.logging.log-levels[#features.logging.log-levels] +* xref:reference:features/logging.adoc#features.logging.log4j2-extensions[#features.logging.log4j2-extensions] +* xref:reference:features/logging.adoc#features.logging.log4j2-extensions.environment-properties-lookup[#features.logging.log4j2-extensions.environment-properties-lookup] +* xref:reference:features/logging.adoc#features.logging.log4j2-extensions.environment-property-source[#features.logging.log4j2-extensions.environment-property-source] +* xref:reference:features/logging.adoc#features.logging.log4j2-extensions.profile-specific[#features.logging.log4j2-extensions.profile-specific] +* xref:reference:features/logging.adoc#features.logging.logback-extensions[#features.logging.logback-extensions] +* xref:reference:features/logging.adoc#features.logging.logback-extensions.environment-properties[#features.logging.logback-extensions.environment-properties] +* xref:reference:features/logging.adoc#features.logging.logback-extensions.profile-specific[#features.logging.logback-extensions.profile-specific] +* xref:reference:features/logging.adoc#features.logging.shutdown-hook[#features.logging.shutdown-hook] +* xref:reference:features/profiles.adoc#features.profiles[#features.profiles] +* xref:reference:features/profiles.adoc#features.profiles.adding-active-profiles[#features.profiles.adding-active-profiles] +* xref:reference:features/profiles.adoc#features.profiles.groups[#features.profiles.groups] +* xref:reference:features/profiles.adoc#features.profiles.profile-specific-configuration-files[#features.profiles.profile-specific-configuration-files] +* xref:reference:features/profiles.adoc#features.profiles.programmatically-setting-profiles[#features.profiles.programmatically-setting-profiles] +* xref:reference:features/spring-application.adoc#features.spring-application[#features.spring-application] +* xref:reference:features/spring-application.adoc#features.spring-application.admin[#features.spring-application.admin] +* xref:reference:features/spring-application.adoc#features.spring-application.application-arguments[#features.spring-application.application-arguments] +* xref:reference:features/spring-application.adoc#features.spring-application.application-availability[#features.spring-application.application-availability] +* xref:reference:features/spring-application.adoc#features.spring-application.application-availability.liveness[#features.spring-application.application-availability.liveness] +* xref:reference:features/spring-application.adoc#features.spring-application.application-availability.managing[#features.spring-application.application-availability.managing] +* xref:reference:features/spring-application.adoc#features.spring-application.application-availability.readiness[#features.spring-application.application-availability.readiness] +* xref:reference:features/spring-application.adoc#features.spring-application.application-events-and-listeners[#features.spring-application.application-events-and-listeners] +* xref:reference:features/spring-application.adoc#features.spring-application.application-exit[#features.spring-application.application-exit] +* xref:reference:features/spring-application.adoc#features.spring-application.banner[#features.spring-application.banner] +* xref:reference:features/spring-application.adoc#features.spring-application.command-line-runner[#features.spring-application.command-line-runner] +* xref:reference:features/spring-application.adoc#features.spring-application.customizing-spring-application[#features.spring-application.customizing-spring-application] +* xref:reference:features/spring-application.adoc#features.spring-application.fluent-builder-api[#features.spring-application.fluent-builder-api] +* xref:reference:features/spring-application.adoc#features.spring-application.lazy-initialization[#features.spring-application.lazy-initialization] +* xref:reference:features/spring-application.adoc#features.spring-application.startup-failure[#features.spring-application.startup-failure] +* xref:reference:features/spring-application.adoc#features.spring-application.startup-tracking[#features.spring-application.startup-tracking] +* xref:reference:features/spring-application.adoc#features.spring-application.virtual-threads[#features.spring-application.virtual-threads] +* xref:reference:features/spring-application.adoc#features.spring-application.web-environment[#features.spring-application.web-environment] +* xref:reference:features/ssl.adoc#features.ssl[#features.ssl] +* xref:reference:features/ssl.adoc#features.ssl.applying[#features.ssl.applying] +* xref:reference:features/ssl.adoc#features.ssl.bundles[#features.ssl.bundles] +* xref:reference:features/ssl.adoc#features.ssl.jks[#features.ssl.jks] +* xref:reference:features/ssl.adoc#features.ssl.pem[#features.ssl.pem] +* xref:reference:features/ssl.adoc#features.ssl.reloading[#features.ssl.reloading] +* xref:reference:features/task-execution-and-scheduling.adoc#features.task-execution-and-scheduling[#features.task-execution-and-scheduling] +* xref:reference:features/dev-services.adoc#features.dev-services.testcontainers[#features.testcontainers] +* xref:reference:features/dev-services.adoc#features.dev-services.testcontainers.at-development-time[#features.testcontainers.at-development-time] +* xref:reference:features/dev-services.adoc#features.dev-services.testcontainers.at-development-time.devtools[#features.testcontainers.at-development-time.devtools] +* xref:reference:features/dev-services.adoc#features.dev-services.testcontainers.at-development-time.dynamic-properties[#features.testcontainers.at-development-time.dynamic-properties] +* xref:reference:features/dev-services.adoc#features.dev-services.testcontainers.at-development-time.importing-container-declarations[#features.testcontainers.at-development-time.importing-container-declarations] +* xref:reference:testing/index.adoc#testing[#features.testing] +* xref:reference:testing/spring-applications.adoc#testing.spring-applications[#features.testing.spring-applications] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications[#features.testing.spring-boot-applications] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.additional-autoconfiguration-and-slicing[#features.testing.spring-boot-applications.additional-autoconfiguration-and-slicing] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-jdbc[#features.testing.spring-boot-applications.autoconfigured-jdbc] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-jooq[#features.testing.spring-boot-applications.autoconfigured-jooq] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-rest-client[#features.testing.spring-boot-applications.autoconfigured-rest-client] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-spring-data-cassandra[#features.testing.spring-boot-applications.autoconfigured-spring-data-cassandra] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-spring-data-couchbase[#features.testing.spring-boot-applications.autoconfigured-spring-data-couchbase] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-spring-data-elasticsearch[#features.testing.spring-boot-applications.autoconfigured-spring-data-elasticsearch] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-spring-data-jdbc[#features.testing.spring-boot-applications.autoconfigured-spring-data-jdbc] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-spring-data-jpa[#features.testing.spring-boot-applications.autoconfigured-spring-data-jpa] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-spring-data-ldap[#features.testing.spring-boot-applications.autoconfigured-spring-data-ldap] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-spring-data-mongodb[#features.testing.spring-boot-applications.autoconfigured-spring-data-mongodb] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-spring-data-neo4j[#features.testing.spring-boot-applications.autoconfigured-spring-data-neo4j] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-spring-data-r2dbc[#features.testing.spring-boot-applications.autoconfigured-spring-data-r2dbc] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-spring-data-redis[#features.testing.spring-boot-applications.autoconfigured-spring-data-redis] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-spring-restdocs[#features.testing.spring-boot-applications.autoconfigured-spring-restdocs] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-spring-restdocs.with-mock-mvc[#features.testing.spring-boot-applications.autoconfigured-spring-restdocs.with-mock-mvc] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-spring-restdocs.with-rest-assured[#features.testing.spring-boot-applications.autoconfigured-spring-restdocs.with-rest-assured] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-spring-restdocs.with-web-test-client[#features.testing.spring-boot-applications.autoconfigured-spring-restdocs.with-web-test-client] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-tests[#features.testing.spring-boot-applications.autoconfigured-tests] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-webservices[#features.testing.spring-boot-applications.autoconfigured-webservices] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-webservices.client[#features.testing.spring-boot-applications.autoconfigured-webservices.client] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-webservices.server[#features.testing.spring-boot-applications.autoconfigured-webservices.server] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.customizing-web-test-client[#features.testing.spring-boot-applications.customizing-web-test-client] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.detecting-configuration[#features.testing.spring-boot-applications.detecting-configuration] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.detecting-web-app-type[#features.testing.spring-boot-applications.detecting-web-app-type] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.excluding-configuration[#features.testing.spring-boot-applications.excluding-configuration] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.jmx[#features.testing.spring-boot-applications.jmx] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.json-tests[#features.testing.spring-boot-applications.json-tests] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.metrics[#features.testing.spring-boot-applications.metrics] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.mocking-beans[#features.testing.spring-boot-applications.mocking-beans] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.observations[#features.testing.spring-boot-applications.observations] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.spock[#features.testing.spring-boot-applications.spock] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.spring-graphql-tests[#features.testing.spring-boot-applications.spring-graphql-tests] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.spring-mvc-tests[#features.testing.spring-boot-applications.spring-mvc-tests] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.spring-webflux-tests[#features.testing.spring-boot-applications.spring-webflux-tests] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.tracing[#features.testing.spring-boot-applications.tracing] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.user-configuration-and-slicing[#features.testing.spring-boot-applications.user-configuration-and-slicing] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.using-application-arguments[#features.testing.spring-boot-applications.using-application-arguments] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.using-main[#features.testing.spring-boot-applications.using-main] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.with-mock-environment[#features.testing.spring-boot-applications.with-mock-environment] +* xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.with-running-server[#features.testing.spring-boot-applications.with-running-server] +* xref:reference:testing/test-scope-dependencies.adoc#testing.test-scope-dependencies[#features.testing.test-scope-dependencies] +* xref:reference:testing/testcontainers.adoc#testing.testcontainers[#features.testing.testcontainers] +* xref:reference:testing/testcontainers.adoc#testing.testcontainers[#howto-testcontainers] +* xref:reference:testing/testcontainers.adoc#testing.testcontainers.dynamic-properties[#features.testing.testcontainers.dynamic-properties] +* xref:reference:testing/testcontainers.adoc#testing.testcontainers.service-connections[#features.testing.testcontainers.service-connections] +* xref:reference:testing/test-utilities.adoc#testing.utilities[#features.testing.utilities] +* xref:reference:testing/test-utilities.adoc#testing.utilities.config-data-application-context-initializer[#features.testing.utilities.config-data-application-context-initializer] +* xref:reference:testing/test-utilities.adoc#testing.utilities.output-capture[#features.testing.utilities.output-capture] +* xref:reference:testing/test-utilities.adoc#testing.utilities.test-property-values[#features.testing.utilities.test-property-values] +* xref:reference:testing/test-utilities.adoc#testing.utilities.test-rest-template[#features.testing.utilities.test-rest-template] +* xref:reference:io/caching.adoc#io.caching[#boot-features-caching] +* xref:reference:io/caching.adoc#io.caching[#io.caching] +* xref:reference:io/caching.adoc#io.caching.provider[#io.caching.provider] +* xref:reference:io/caching.adoc#io.caching.provider[#boot-features-caching-provider] +* xref:reference:io/caching.adoc#io.caching.provider.cache2k[#io.caching.provider.cache2k] +* xref:reference:io/caching.adoc#io.caching.provider.caffeine[#io.caching.provider.caffeine] +* xref:reference:io/caching.adoc#io.caching.provider.caffeine[#boot-features-caching-provider-caffeine] +* xref:reference:io/caching.adoc#io.caching.provider.couchbase[#boot-features-caching-provider-couchbase] +* xref:reference:io/caching.adoc#io.caching.provider.couchbase[#io.caching.provider.couchbase] +* xref:reference:io/caching.adoc#io.caching.provider.generic[#io.caching.provider.generic] +* xref:reference:io/caching.adoc#io.caching.provider.generic[#boot-features-caching-provider-generic] +* xref:reference:io/caching.adoc#io.caching.provider.hazelcast[#io.caching.provider.hazelcast] +* xref:reference:io/caching.adoc#io.caching.provider.hazelcast[#boot-features-caching-provider-hazelcast] +* xref:reference:io/caching.adoc#io.caching.provider.infinispan[#boot-features-caching-provider-infinispan] +* xref:reference:io/caching.adoc#io.caching.provider.infinispan[#io.caching.provider.infinispan] +* xref:reference:io/caching.adoc#io.caching.provider.jcache[#io.caching.provider.jcache] +* xref:reference:io/caching.adoc#io.caching.provider.jcache[#boot-features-caching-provider-jcache] +* xref:reference:io/caching.adoc#io.caching.provider.none[#io.caching.provider.none] +* xref:reference:io/caching.adoc#io.caching.provider.none[#boot-features-caching-provider-none] +* xref:reference:io/caching.adoc#io.caching.provider.redis[#boot-features-caching-provider-redis] +* xref:reference:io/caching.adoc#io.caching.provider.redis[#io.caching.provider.redis] +* xref:reference:io/caching.adoc#io.caching.provider.simple[#io.caching.provider.simple] +* xref:reference:io/caching.adoc#io.caching.provider.simple[#boot-features-caching-provider-simple] +* xref:reference:io/email.adoc#io.email[#boot-features-email] +* xref:reference:io/email.adoc#io.email[#io.email] +* xref:reference:io/hazelcast.adoc#io.hazelcast[#boot-features-hazelcast] +* xref:reference:io/hazelcast.adoc#io.hazelcast[#io.hazelcast] +* xref:reference:io/index.adoc#io[#io] +* xref:reference:io/jta.adoc#io.jta[#boot-features-jta] +* xref:reference:io/jta.adoc#io.jta[#io.jta] +* xref:reference:io/jta.adoc#io.jta.jakartaee[#boot-features-jta-javaee] +* xref:reference:io/jta.adoc#io.jta.jakartaee[#io.jta.jakartaee] +* xref:reference:io/jta.adoc#io.jta.mixing-xa-and-non-xa-connections[#boot-features-jta-mixed-jms] +* xref:reference:io/jta.adoc#io.jta.mixing-xa-and-non-xa-connections[#io.jta.mixing-xa-and-non-xa-connections] +* xref:reference:io/jta.adoc#io.jta.supporting-embedded-transaction-manager[#io.jta.supporting-embedded-transaction-manager] +* xref:reference:io/jta.adoc#io.jta.supporting-embedded-transaction-manager[#boot-features-jta-supporting-alternative-embedded] +* xref:reference:io/quartz.adoc#io.quartz[#io.quartz] +* xref:reference:io/quartz.adoc#io.quartz[#boot-features-quartz] +* xref:reference:io/rest-client.adoc#io.rest-client[#io.rest-client] +* xref:reference:io/rest-client.adoc#io.rest-client.clienthttprequestfactory[#io.rest-client.clienthttprequestfactory] +* xref:reference:io/rest-client.adoc#io.rest-client.restclient[#io.rest-client.restclient] +* xref:reference:io/rest-client.adoc#io.rest-client.restclient.customization[#io.rest-client.restclient.customization] +* xref:reference:io/rest-client.adoc#io.rest-client.restclient.ssl[#io.rest-client.restclient.ssl] +* xref:reference:io/rest-client.adoc#io.rest-client.resttemplate[#io.rest-client.resttemplate] +* xref:reference:io/rest-client.adoc#io.rest-client.resttemplate[#boot-features-resttemplate] +* xref:reference:io/rest-client.adoc#io.rest-client.resttemplate.customization[#io.rest-client.resttemplate.customization] +* xref:reference:io/rest-client.adoc#io.rest-client.resttemplate.customization[#boot-features-resttemplate-customization] +* xref:reference:io/rest-client.adoc#io.rest-client.resttemplate.ssl[#io.rest-client.resttemplate.ssl] +* xref:reference:io/rest-client.adoc#io.rest-client.webclient[#io.rest-client.webclient] +* xref:reference:io/rest-client.adoc#io.rest-client.webclient[#boot-features-webclient] +* xref:reference:io/rest-client.adoc#io.rest-client.webclient.customization[#boot-features-webclient-customization] +* xref:reference:io/rest-client.adoc#io.rest-client.webclient.customization[#io.rest-client.webclient.customization] +* xref:reference:io/rest-client.adoc#io.rest-client.webclient.runtime[#boot-features-webclient-runtime] +* xref:reference:io/rest-client.adoc#io.rest-client.webclient.runtime[#io.rest-client.webclient.runtime] +* xref:reference:io/rest-client.adoc#io.rest-client.webclient.ssl[#io.rest-client.webclient.ssl] +* xref:reference:io/validation.adoc#io.validation[#boot-features-validation] +* xref:reference:io/validation.adoc#io.validation[#io.validation] +* xref:reference:io/webservices.adoc#io.webservices[#boot-features-webservices] +* xref:reference:io/webservices.adoc#io.webservices[#io.webservices] +* xref:reference:io/webservices.adoc#io.webservices.template[#boot-features-webservices-template] +* xref:reference:io/webservices.adoc#io.webservices.template[#io.webservices.template] +* xref:reference:messaging/amqp.adoc#messaging.amqp[#boot-features-amqp] +* xref:reference:messaging/amqp.adoc#messaging.amqp[#messaging.amqp] +* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq[#boot-features-rabbitmq] +* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq[#messaging.amqp.rabbitmq] +* xref:reference:messaging/amqp.adoc#messaging.amqp.receiving[#messaging.amqp.receiving] +* xref:reference:messaging/amqp.adoc#messaging.amqp.receiving[#boot-features-using-amqp-receiving] +* xref:reference:messaging/amqp.adoc#messaging.amqp.sending[#boot-features-using-amqp-sending] +* xref:reference:messaging/amqp.adoc#messaging.amqp.sending[#messaging.amqp.sending] +* xref:reference:messaging/amqp.adoc#messaging.amqp.sending-stream[#messaging.amqp.sending-stream] +* xref:reference:messaging/index.adoc#messaging[#boot-features-messaging] +* xref:reference:messaging/index.adoc#messaging[#messaging] +* xref:reference:messaging/jms.adoc#messaging.jms[#boot-features-jms] +* xref:reference:messaging/jms.adoc#messaging.jms[#messaging.jms] +* xref:reference:messaging/jms.adoc#messaging.jms.activemq[#boot-features-activemq] +* xref:reference:messaging/jms.adoc#messaging.jms.activemq[#messaging.jms.activemq] +* xref:reference:messaging/jms.adoc#messaging.jms.artemis[#messaging.jms.artemis] +* xref:reference:messaging/jms.adoc#messaging.jms.artemis[#boot-features-artemis] +* xref:reference:messaging/jms.adoc#messaging.jms.jndi[#boot-features-jms-jndi] +* xref:reference:messaging/jms.adoc#messaging.jms.jndi[#messaging.jms.jndi] +* xref:reference:messaging/jms.adoc#messaging.jms.receiving[#boot-features-using-jms-receiving] +* xref:reference:messaging/jms.adoc#messaging.jms.receiving[#messaging.jms.receiving] +* xref:reference:messaging/jms.adoc#messaging.jms.sending[#boot-features-using-jms-sending] +* xref:reference:messaging/jms.adoc#messaging.jms.sending[#messaging.jms.sending] +* xref:reference:messaging/kafka.adoc#messaging.kafka[#messaging.kafka] +* xref:reference:messaging/kafka.adoc#messaging.kafka[#boot-features-kafka] +* xref:reference:messaging/kafka.adoc#messaging.kafka.additional-properties[#boot-features-kafka-extra-props] +* xref:reference:messaging/kafka.adoc#messaging.kafka.additional-properties[#messaging.kafka.additional-properties] +* xref:reference:messaging/kafka.adoc#messaging.kafka.embedded[#boot-features-embedded-kafka] +* xref:reference:messaging/kafka.adoc#messaging.kafka.embedded[#messaging.kafka.embedded] +* xref:reference:messaging/kafka.adoc#messaging.kafka.receiving[#messaging.kafka.receiving] +* xref:reference:messaging/kafka.adoc#messaging.kafka.receiving[#boot-features-kafka-receiving-a-message] +* xref:reference:messaging/kafka.adoc#messaging.kafka.sending[#messaging.kafka.sending] +* xref:reference:messaging/kafka.adoc#messaging.kafka.sending[#boot-features-kafka-sending-a-message] +* xref:reference:messaging/kafka.adoc#messaging.kafka.streams[#messaging.kafka.streams] +* xref:reference:messaging/kafka.adoc#messaging.kafka.streams[#boot-features-kafka-streams] +* xref:reference:messaging/pulsar.adoc#messaging.pulsar[#messaging.pulsar] +* xref:reference:messaging/pulsar.adoc#messaging.pulsar.additional-properties[#messaging.pulsar.additional-properties] +* xref:reference:messaging/pulsar.adoc#messaging.pulsar.admin[#messaging.pulsar.admin] +* xref:reference:messaging/pulsar.adoc#messaging.pulsar.admin.auth[#messaging.pulsar.admin.auth] +* xref:reference:messaging/pulsar.adoc#messaging.pulsar.connecting[#messaging.pulsar.connecting] +* xref:reference:messaging/pulsar.adoc#messaging.pulsar.connecting-reactive[#messaging.pulsar.connecting-reactive] +* xref:reference:messaging/pulsar.adoc#messaging.pulsar.connecting.auth[#messaging.pulsar.connecting.auth] +* xref:reference:messaging/pulsar.adoc#messaging.pulsar.connecting.ssl[#messaging.pulsar.connecting.ssl] +* xref:reference:messaging/pulsar.adoc#messaging.pulsar.reading[#messaging.pulsar.reading] +* xref:reference:messaging/pulsar.adoc#messaging.pulsar.reading-reactive[#messaging.pulsar.reading-reactive] +* xref:reference:messaging/pulsar.adoc#messaging.pulsar.receiving[#messaging.pulsar.receiving] +* xref:reference:messaging/pulsar.adoc#messaging.pulsar.receiving-reactive[#messaging.pulsar.receiving-reactive] +* xref:reference:messaging/pulsar.adoc#messaging.pulsar.sending[#messaging.pulsar.sending] +* xref:reference:messaging/pulsar.adoc#messaging.pulsar.sending-reactive[#messaging.pulsar.sending-reactive] +* xref:reference:messaging/rsocket.adoc#messaging.rsocket[#messaging.rsocket] +* xref:reference:messaging/rsocket.adoc#messaging.rsocket[#boot-features-rsocket] +* xref:reference:messaging/rsocket.adoc#messaging.rsocket.messaging[#boot-features-rsocket-messaging] +* xref:reference:messaging/rsocket.adoc#messaging.rsocket.messaging[#messaging.rsocket.messaging] +* xref:reference:messaging/rsocket.adoc#messaging.rsocket.requester[#messaging.rsocket.requester] +* xref:reference:messaging/rsocket.adoc#messaging.rsocket.requester[#boot-features-rsocket-requester] +* xref:reference:messaging/rsocket.adoc#messaging.rsocket.server-auto-configuration[#messaging.rsocket.server-auto-configuration] +* xref:reference:messaging/rsocket.adoc#messaging.rsocket.server-auto-configuration[#boot-features-rsocket-server-auto-configuration] +* xref:reference:messaging/rsocket.adoc#messaging.rsocket.strategies-auto-configuration[#messaging.rsocket.strategies-auto-configuration] +* xref:reference:messaging/rsocket.adoc#messaging.rsocket.strategies-auto-configuration[#boot-features-rsocket-strategies-auto-configuration] +* xref:reference:messaging/spring-integration.adoc#messaging.spring-integration[#messaging.spring-integration] +* xref:reference:messaging/spring-integration.adoc#messaging.spring-integration[#boot-features-integration] +* xref:reference:messaging/websockets.adoc#messaging.websockets[#boot-features-websockets] +* xref:reference:messaging/websockets.adoc#messaging.websockets[#messaging.websockets] +* xref:reference:packaging/native-image/advanced-topics.adoc#packaging.native-image.advanced[#native-image.advanced] +* xref:reference:packaging/native-image/advanced-topics.adoc#packaging.native-image.advanced.converting-executable-jars[#native-image.advanced.converting-executable-jars] +* xref:reference:packaging/native-image/advanced-topics.adoc#packaging.native-image.advanced.converting-executable-jars.buildpacks[#native-image.advanced.converting-executable-jars.buildpacks] +* xref:reference:packaging/native-image/advanced-topics.adoc#packaging.native-image.advanced.converting-executable-jars.native-image[#native-image.advanced.converting-executable-jars.native-image] +* xref:reference:packaging/native-image/advanced-topics.adoc#packaging.native-image.advanced.custom-hints[#native-image.advanced.custom-hints] +* xref:reference:packaging/native-image/advanced-topics.adoc#packaging.native-image.advanced.custom-hints.testing[#native-image.advanced.custom-hints.testing] +* xref:reference:packaging/native-image/advanced-topics.adoc#packaging.native-image.advanced.known-limitations[#native-image.advanced.known-limitations] +* xref:reference:packaging/native-image/advanced-topics.adoc#packaging.native-image.advanced.nested-configuration-properties[#native-image.advanced.nested-configuration-properties] +* xref:reference:packaging/native-image/advanced-topics.adoc#packaging.native-image.advanced.using-the-tracing-agent[#native-image.advanced.using-the-tracing-agent] +* xref:reference:packaging/native-image/advanced-topics.adoc#packaging.native-image.advanced.using-the-tracing-agent.launch[#native-image.advanced.using-the-tracing-agent.launch] +* xref:how-to:native-image/developing-your-first-application.adoc#howto.native-image.developing-your-first-application[#native-image.developing-your-first-application] +* xref:how-to:native-image/developing-your-first-application.adoc#howto.native-image.developing-your-first-application.buildpacks[#native-image.developing-your-first-application.buildpacks] +* xref:how-to:native-image/developing-your-first-application.adoc#howto.native-image.developing-your-first-application.buildpacks.gradle[#native-image.developing-your-first-application.buildpacks.gradle] +* xref:how-to:native-image/developing-your-first-application.adoc#howto.native-image.developing-your-first-application.buildpacks.maven[#native-image.developing-your-first-application.buildpacks.maven] +* xref:how-to:native-image/developing-your-first-application.adoc#howto.native-image.developing-your-first-application.buildpacks.running[#native-image.developing-your-first-application.buildpacks.running] +* xref:how-to:native-image/developing-your-first-application.adoc#howto.native-image.developing-your-first-application.buildpacks.system-requirements[#native-image.developing-your-first-application.buildpacks.system-requirements] +* xref:how-to:native-image/developing-your-first-application.adoc#howto.native-image.developing-your-first-application.native-build-tools[#native-image.developing-your-first-application.native-build-tools] +* xref:how-to:native-image/developing-your-first-application.adoc#howto.native-image.developing-your-first-application.native-build-tools.gradle[#native-image.developing-your-first-application.native-build-tools.gradle] +* xref:how-to:native-image/developing-your-first-application.adoc#howto.native-image.developing-your-first-application.native-build-tools.maven[#native-image.developing-your-first-application.native-build-tools.maven] +* xref:how-to:native-image/developing-your-first-application.adoc#howto.native-image.developing-your-first-application.native-build-tools.prerequisites[#native-image.developing-your-first-application.native-build-tools.prerequisites] +* xref:how-to:native-image/developing-your-first-application.adoc#howto.native-image.developing-your-first-application.native-build-tools.prerequisites.linux-macos[#native-image.developing-your-first-application.native-build-tools.prerequisites.linux-macos] +* xref:how-to:native-image/developing-your-first-application.adoc#howto.native-image.developing-your-first-application.native-build-tools.prerequisites.windows[#native-image.developing-your-first-application.native-build-tools.prerequisites.windows] +* xref:how-to:native-image/developing-your-first-application.adoc#howto.native-image.developing-your-first-application.native-build-tools.running[#native-image.developing-your-first-application.native-build-tools.running] +* xref:how-to:native-image/developing-your-first-application.adoc#howto.native-image.developing-your-first-application.sample-application[#native-image.developing-your-first-application.sample-application] +* xref:reference:packaging/native-image/index.adoc#packaging.native-image[#native-image] +* xref:reference:packaging/native-image/introducing-graalvm-native-images.adoc#packaging.native-image.introducing-graalvm-native-images[#native-image.introducing-graalvm-native-images] +* xref:reference:packaging/native-image/introducing-graalvm-native-images.adoc#packaging.native-image.introducing-graalvm-native-images.key-differences-with-jvm-deployments[#packaging.native-image.introducing-graalvm-native-images.key-differences-with-jvm-deployments] +* xref:reference:packaging/native-image/introducing-graalvm-native-images.adoc#packaging.native-image.introducing-graalvm-native-images.understanding-aot-processing[#packaging.native-image.introducing-graalvm-native-images.understanding-aot-processing] +* xref:reference:packaging/native-image/introducing-graalvm-native-images.adoc#packaging.native-image.introducing-graalvm-native-images.understanding-aot-processing.hint-file-generation[#packaging.native-image.introducing-graalvm-native-images.understanding-aot-processing.hint-file-generation] +* xref:reference:packaging/native-image/introducing-graalvm-native-images.adoc#packaging.native-image.introducing-graalvm-native-images.understanding-aot-processing.proxy-class-generation[#packaging.native-image.introducing-graalvm-native-images.understanding-aot-processing.proxy-class-generation] +* xref:reference:packaging/native-image/introducing-graalvm-native-images.adoc#packaging.native-image.introducing-graalvm-native-images.understanding-aot-processing.source-code-generation[#packaging.native-image.introducing-graalvm-native-images.understanding-aot-processing.source-code-generation] +* xref:how-to:native-image/testing-native-applications.adoc#howto.native-image.testing[#native-image.testing] +* xref:how-to:native-image/testing-native-applications.adoc#howto.native-image.testing.with-native-build-tools[#native-image.testing.with-native-build-tools] +* xref:how-to:native-image/testing-native-applications.adoc#howto.native-image.testing.with-native-build-tools.gradle[#native-image.testing.with-native-build-tools.gradle] +* xref:how-to:native-image/testing-native-applications.adoc#howto.native-image.testing.with-native-build-tools.maven[#native-image.testing.with-native-build-tools.maven] +* xref:how-to:native-image/testing-native-applications.adoc#howto.native-image.testing.with-the-jvm[#native-image.testing.with-the-jvm] +* xref:reference:using/auto-configuration.adoc#using.auto-configuration[#using.auto-configuration] +* xref:reference:using/auto-configuration.adoc#using.auto-configuration.disabling-specific[#using.auto-configuration.disabling-specific] +* xref:reference:using/auto-configuration.adoc#using.auto-configuration.packages[#using.auto-configuration.packages] +* xref:reference:using/auto-configuration.adoc#using.auto-configuration.replacing[#using.auto-configuration.replacing] +* xref:reference:using/build-systems.adoc#using.build-systems[#using.build-systems] +* xref:reference:using/build-systems.adoc#using.build-systems.ant[#using.build-systems.ant] +* xref:reference:using/build-systems.adoc#using.build-systems.dependency-management[#using.build-systems.dependency-management] +* xref:reference:using/build-systems.adoc#using.build-systems.gradle[#using.build-systems.gradle] +* xref:reference:using/build-systems.adoc#using.build-systems.maven[#using.build-systems.maven] +* xref:reference:using/build-systems.adoc#using.build-systems.starters[#using.build-systems.starters] +* xref:reference:using/configuration-classes.adoc#using.configuration-classes[#using.configuration-classes] +* xref:reference:using/configuration-classes.adoc#using.configuration-classes.importing-additional-configuration[#using.configuration-classes.importing-additional-configuration] +* xref:reference:using/configuration-classes.adoc#using.configuration-classes.importing-xml-configuration[#using.configuration-classes.importing-xml-configuration] +* xref:reference:using/devtools.adoc#using.devtools[#using.devtools] +* xref:reference:using/devtools.adoc#using.devtools.diagnosing-classloading-issues[#using.devtools.diagnosing-classloading-issues] +* xref:reference:using/devtools.adoc#using.devtools.globalsettings[#using.devtools.globalsettings] +* xref:reference:using/devtools.adoc#using.devtools.globalsettings.configuring-file-system-watcher[#using.devtools.globalsettings.configuring-file-system-watcher] +* xref:reference:using/devtools.adoc#using.devtools.livereload[#using.devtools.livereload] +* xref:reference:using/devtools.adoc#using.devtools.property-defaults[#using.devtools.property-defaults] +* xref:reference:using/devtools.adoc#using.devtools.remote-applications[#using.devtools.remote-applications] +* xref:reference:using/devtools.adoc#using.devtools.remote-applications.client[#using.devtools.remote-applications.client] +* xref:reference:using/devtools.adoc#using.devtools.remote-applications.update[#using.devtools.remote-applications.update] +* xref:reference:using/devtools.adoc#using.devtools.restart[#using.devtools.restart] +* xref:reference:using/devtools.adoc#using.devtools.restart.customizing-the-classload[#using.devtools.restart.customizing-the-classload] +* xref:reference:using/devtools.adoc#using.devtools.restart.disable[#using.devtools.restart.disable] +* xref:reference:using/devtools.adoc#using.devtools.restart.excluding-resources[#using.devtools.restart.excluding-resources] +* xref:reference:using/devtools.adoc#using.devtools.restart.limitations[#using.devtools.restart.limitations] +* xref:reference:using/devtools.adoc#using.devtools.restart.logging-condition-delta[#using.devtools.restart.logging-condition-delta] +* xref:reference:using/devtools.adoc#using.devtools.restart.restart-vs-reload[#using.devtools.restart.restart-vs-reload] +* xref:reference:using/devtools.adoc#using.devtools.restart.triggerfile[#using.devtools.restart.triggerfile] +* xref:reference:using/devtools.adoc#using.devtools.restart.watching-additional-paths[#using.devtools.restart.watching-additional-paths] +* xref:reference:using/index.adoc#using[#using] +* xref:reference:using/packaging-for-production.adoc#using.packaging-for-production[#using.packaging-for-production] +* xref:reference:using/running-your-application.adoc#using.running-your-application[#using.running-your-application] +* xref:reference:using/running-your-application.adoc#using.running-your-application.as-a-packaged-application[#using.running-your-application.as-a-packaged-application] +* xref:reference:using/running-your-application.adoc#using.running-your-application.from-an-ide[#using.running-your-application.from-an-ide] +* xref:reference:using/running-your-application.adoc#using.running-your-application.hot-swapping[#using.running-your-application.hot-swapping] +* xref:reference:using/running-your-application.adoc#using.running-your-application.with-the-gradle-plugin[#using.running-your-application.with-the-gradle-plugin] +* xref:reference:using/running-your-application.adoc#using.running-your-application.with-the-maven-plugin[#using.running-your-application.with-the-maven-plugin] +* xref:reference:using/spring-beans-and-dependency-injection.adoc#using.spring-beans-and-dependency-injection[#using.spring-beans-and-dependency-injection] +* xref:reference:using/structuring-your-code.adoc#using.structuring-your-code[#using.structuring-your-code] +* xref:reference:using/structuring-your-code.adoc#using.structuring-your-code.locating-the-main-class[#using.structuring-your-code.locating-the-main-class] +* xref:reference:using/structuring-your-code.adoc#using.structuring-your-code.using-the-default-package[#using.structuring-your-code.using-the-default-package] +* xref:reference:using/using-the-springbootapplication-annotation.adoc#using.using-the-springbootapplication-annotation[#using.using-the-springbootapplication-annotation] +* xref:reference:web/graceful-shutdown.adoc#web.graceful-shutdown[#boot-features-graceful-shutdown] +* xref:reference:web/graceful-shutdown.adoc#web.graceful-shutdown[#web.graceful-shutdown] +* xref:reference:web/index.adoc#web[#boot-features-developing-web-applications] +* xref:reference:web/index.adoc#web[#web] +* xref:reference:web/reactive.adoc#web.reactive[#web.reactive] +* xref:reference:web/reactive.adoc#web.reactive.reactive-server[#web.reactive.reactive-server] +* xref:reference:web/reactive.adoc#web.reactive.reactive-server[#boot-features-reactive-server] +* xref:reference:web/reactive.adoc#web.reactive.reactive-server-resources-configuration[#web.reactive.reactive-server-resources-configuration] +* xref:reference:web/reactive.adoc#web.reactive.reactive-server-resources-configuration[#boot-features-reactive-server-resources] +* xref:reference:web/reactive.adoc#web.reactive.reactive-server.customizing[#web.reactive.reactive-server.customizing] +* xref:reference:web/reactive.adoc#web.reactive.reactive-server.customizing.direct[#web.reactive.reactive-server.customizing.direct] +* xref:reference:web/reactive.adoc#web.reactive.reactive-server.customizing.programmatic[#web.reactive.reactive-server.customizing.programmatic] +* xref:reference:web/reactive.adoc#web.reactive.webflux[#boot-features-webflux] +* xref:reference:web/reactive.adoc#web.reactive.webflux[#web.reactive.webflux] +* xref:reference:web/reactive.adoc#web.reactive.webflux.auto-configuration[#web.reactive.webflux.auto-configuration] +* xref:reference:web/reactive.adoc#web.reactive.webflux.auto-configuration[#boot-features-webflux-auto-configuration] +* xref:reference:web/reactive.adoc#web.reactive.webflux.conversion-service[#web.reactive.webflux.conversion-service] +* xref:reference:web/reactive.adoc#web.reactive.webflux.error-handling[#boot-features-webflux-error-handling] +* xref:reference:web/reactive.adoc#web.reactive.webflux.error-handling[#web.reactive.webflux.error-handling] +* xref:reference:web/reactive.adoc#web.reactive.webflux.error-handling.error-pages[#boot-features-webflux-error-handling-custom-error-pages] +* xref:reference:web/reactive.adoc#web.reactive.webflux.error-handling.error-pages[#web.reactive.webflux.error-handling.error-pages] +* xref:reference:web/reactive.adoc#web.reactive.webflux.httpcodecs[#boot-features-webflux-httpcodecs] +* xref:reference:web/reactive.adoc#web.reactive.webflux.httpcodecs[#web.reactive.webflux.httpcodecs] +* xref:reference:web/reactive.adoc#web.reactive.webflux.static-content[#web.reactive.webflux.static-content] +* xref:reference:web/reactive.adoc#web.reactive.webflux.static-content[#boot-features-webflux-static-content] +* xref:reference:web/reactive.adoc#web.reactive.webflux.template-engines[#web.reactive.webflux.template-engines] +* xref:reference:web/reactive.adoc#web.reactive.webflux.template-engines[#boot-features-webflux-template-engines] +* xref:reference:web/reactive.adoc#web.reactive.webflux.web-filters[#boot-features-webflux-web-filters] +* xref:reference:web/reactive.adoc#web.reactive.webflux.web-filters[#web.reactive.webflux.web-filters] +* xref:reference:web/reactive.adoc#web.reactive.webflux.welcome-page[#boot-features-webflux-welcome-page] +* xref:reference:web/reactive.adoc#web.reactive.webflux.welcome-page[#web.reactive.webflux.welcome-page] +* xref:reference:web/servlet.adoc#web.servlet[#web.servlet] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container[#boot-features-embedded-container] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container[#web.servlet.embedded-container] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.application-context[#web.servlet.embedded-container.application-context] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.application-context[#boot-features-embedded-container-application-context] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.context-initializer[#web.servlet.embedded-container.context-initializer] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.context-initializer[#boot-features-embedded-container-context-initializer] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.context-initializer.scanning[#boot-features-embedded-container-servlets-filters-listeners-scanning] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.context-initializer.scanning[#web.servlet.embedded-container.context-initializer.scanning] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.customizing[#boot-features-customizing-embedded-containers] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.customizing[#web.servlet.embedded-container.customizing] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.customizing.direct[#boot-features-customizing-configurableservletwebserverfactory-directly] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.customizing.direct[#web.servlet.embedded-container.customizing.direct] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.customizing.encoding[#web.servlet.embedded-container.customizing.encoding] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.customizing.programmatic[#boot-features-programmatic-embedded-container-customization] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.customizing.programmatic[#web.servlet.embedded-container.customizing.programmatic] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.customizing.samesite[#web.servlet.embedded-container.customizing.samesite] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.jsp-limitations[#web.servlet.embedded-container.jsp-limitations] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.jsp-limitations[#boot-features-jsp-limitations] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.servlets-filters-listeners[#boot-features-embedded-container-servlets-filters-listeners] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.servlets-filters-listeners[#web.servlet.embedded-container.servlets-filters-listeners] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.servlets-filters-listeners.beans[#boot-features-embedded-container-servlets-filters-listeners-beans] +* xref:reference:web/servlet.adoc#web.servlet.embedded-container.servlets-filters-listeners.beans[#web.servlet.embedded-container.servlets-filters-listeners.beans] +* xref:reference:web/servlet.adoc#web.servlet.jersey[#web.servlet.jersey] +* xref:reference:web/servlet.adoc#web.servlet.jersey[#boot-features-jersey] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc[#boot-features-spring-mvc] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc[#web.servlet.spring-mvc] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.auto-configuration[#web.servlet.spring-mvc.auto-configuration] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.auto-configuration[#boot-features-spring-mvc-auto-configuration] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.binding-initializer[#web.servlet.spring-mvc.binding-initializer] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.binding-initializer[#boot-features-spring-mvc-web-binding-initializer] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.content-negotiation[#web.servlet.spring-mvc.content-negotiation] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.content-negotiation[#boot-features-spring-mvc-pathmatch] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.conversion-service[#web.servlet.spring-mvc.conversion-service] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.cors[#web.servlet.spring-mvc.cors] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.cors[#boot-features-cors] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.error-handling[#web.servlet.spring-mvc.error-handling] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.error-handling[#boot-features-error-handling] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.error-handling.error-pages[#boot-features-error-handling-custom-error-pages] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.error-handling.error-pages[#web.servlet.spring-mvc.error-handling.error-pages] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.error-handling.error-pages-without-spring-mvc[#web.servlet.spring-mvc.error-handling.error-pages-without-spring-mvc] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.error-handling.error-pages-without-spring-mvc[#boot-features-error-handling-mapping-error-pages-without-mvc] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.error-handling.in-a-war-deployment[#web.servlet.spring-mvc.error-handling.in-a-war-deployment] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.error-handling.in-a-war-deployment[#boot-features-error-handling-war-deployment] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.favicon[#web.servlet.spring-mvc.favicon] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.message-codes[#web.servlet.spring-mvc.message-codes] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.message-codes[#boot-features-spring-message-codes] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.message-converters[#boot-features-spring-mvc-message-converters] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.message-converters[#web.servlet.spring-mvc.message-converters] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.static-content[#boot-features-spring-mvc-static-content] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.static-content[#web.servlet.spring-mvc.static-content] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.template-engines[#boot-features-spring-mvc-template-engines] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.template-engines[#web.servlet.spring-mvc.template-engines] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.welcome-page[#web.servlet.spring-mvc.welcome-page] +* xref:reference:web/servlet.adoc#web.servlet.spring-mvc.welcome-page[#boot-features-spring-mvc-welcome-page] +* xref:reference:web/spring-graphql.adoc#web.graphql[#web.graphql] +* xref:reference:web/spring-graphql.adoc#web.graphql.data-query[#web.graphql.data-query] +* xref:reference:web/spring-graphql.adoc#web.graphql.exception-handling[#web.graphql.exception-handling] +* xref:reference:web/spring-graphql.adoc#web.graphql.graphiql[#web.graphql.graphiql] +* xref:reference:web/spring-graphql.adoc#web.graphql.runtimewiring[#web.graphql.runtimewiring] +* xref:reference:web/spring-graphql.adoc#web.graphql.schema[#web.graphql.schema] +* xref:reference:web/spring-graphql.adoc#web.graphql.transports[#web.graphql.transports] +* xref:reference:web/spring-graphql.adoc#web.graphql.transports.http-websocket[#web.graphql.transports.http-websocket] +* xref:reference:web/spring-graphql.adoc#web.graphql.transports.rsocket[#web.graphql.transports.rsocket] +* xref:reference:web/spring-hateoas.adoc#web.spring-hateoas[#boot-features-spring-hateoas] +* xref:reference:web/spring-hateoas.adoc#web.spring-hateoas[#web.spring-hateoas] +* xref:reference:web/spring-security.adoc#web.security[#boot-features-security] +* xref:reference:web/spring-security.adoc#web.security[#web.security] +* xref:reference:web/spring-security.adoc#web.security.oauth2[#web.security.oauth2] +* xref:reference:web/spring-security.adoc#web.security.oauth2[#boot-features-security-oauth2] +* xref:reference:web/spring-security.adoc#web.security.oauth2.authorization-server[#web.security.oauth2.authorization-server] +* xref:reference:web/spring-security.adoc#web.security.oauth2.authorization-server[#boot-features-security-authorization-server] +* xref:reference:web/spring-security.adoc#web.security.oauth2.client[#boot-features-security-oauth2-client] +* xref:reference:web/spring-security.adoc#web.security.oauth2.client[#web.security.oauth2.client] +* xref:reference:web/spring-security.adoc#web.security.oauth2.client.common-providers[#boot-features-security-oauth2-common-providers] +* xref:reference:web/spring-security.adoc#web.security.oauth2.client.common-providers[#web.security.oauth2.client.common-providers] +* xref:reference:web/spring-security.adoc#web.security.oauth2.server[#web.security.oauth2.server] +* xref:reference:web/spring-security.adoc#web.security.oauth2.server[#boot-features-security-oauth2-server] +* xref:reference:web/spring-security.adoc#web.security.saml2[#boot-features-security-saml] +* xref:reference:web/spring-security.adoc#web.security.saml2[#web.security.saml2] +* xref:reference:web/spring-security.adoc#web.security.saml2.relying-party[#web.security.saml2.relying-party] +* xref:reference:web/spring-security.adoc#web.security.saml2.relying-party[#boot-features-security-saml2-relying-party] +* xref:reference:web/spring-security.adoc#web.security.spring-mvc[#boot-features-security-mvc] +* xref:reference:web/spring-security.adoc#web.security.spring-mvc[#web.security.spring-mvc] +* xref:reference:web/spring-security.adoc#web.security.spring-webflux[#web.security.spring-webflux] +* xref:reference:web/spring-security.adoc#web.security.spring-webflux[#boot-features-security-webflux] +* xref:reference:web/spring-session.adoc#web.spring-session[#boot-features-session] +* xref:reference:web/spring-session.adoc#web.spring-session[#web.spring-session] +* xref:specification:configuration-metadata/annotation-processor.adoc#appendix.configuration-metadata.annotation-processor[#appendix.configuration-metadata.annotation-processor] +* xref:specification:configuration-metadata/annotation-processor.adoc#appendix.configuration-metadata.annotation-processor.adding-additional-metadata[#appendix.configuration-metadata.annotation-processor.adding-additional-metadata] +* xref:specification:configuration-metadata/annotation-processor.adoc#appendix.configuration-metadata.annotation-processor.automatic-metadata-generation[#appendix.configuration-metadata.annotation-processor.automatic-metadata-generation] +* xref:specification:configuration-metadata/annotation-processor.adoc#appendix.configuration-metadata.annotation-processor.automatic-metadata-generation.nested-properties[#appendix.configuration-metadata.annotation-processor.automatic-metadata-generation.nested-properties] +* xref:specification:configuration-metadata/annotation-processor.adoc#appendix.configuration-metadata.annotation-processor.configuring[#appendix.configuration-metadata.annotation-processor.configuring] +* xref:specification:configuration-metadata/format.adoc#appendix.configuration-metadata.format[#appendix.configuration-metadata.format] +* xref:specification:configuration-metadata/format.adoc#appendix.configuration-metadata.format.group[#appendix.configuration-metadata.format.group] +* xref:specification:configuration-metadata/format.adoc#appendix.configuration-metadata.format.hints[#appendix.configuration-metadata.format.hints] +* xref:specification:configuration-metadata/format.adoc#appendix.configuration-metadata.format.property[#appendix.configuration-metadata.format.property] +* xref:specification:configuration-metadata/format.adoc#appendix.configuration-metadata.format.repeated-items[#appendix.configuration-metadata.format.repeated-items] +* xref:specification:configuration-metadata/index.adoc#appendix.configuration-metadata[#appendix.configuration-metadata] +* xref:specification:configuration-metadata/manual-hints.adoc#appendix.configuration-metadata.manual-hints[#appendix.configuration-metadata.manual-hints] +* xref:specification:configuration-metadata/manual-hints.adoc#appendix.configuration-metadata.manual-hints.value-hint[#appendix.configuration-metadata.manual-hints.value-hint] +* xref:specification:configuration-metadata/manual-hints.adoc#appendix.configuration-metadata.manual-hints.value-providers[#appendix.configuration-metadata.manual-hints.value-providers] +* xref:specification:configuration-metadata/manual-hints.adoc#appendix.configuration-metadata.manual-hints.value-providers.any[#appendix.configuration-metadata.manual-hints.value-providers.any] +* xref:specification:configuration-metadata/manual-hints.adoc#appendix.configuration-metadata.manual-hints.value-providers.class-reference[#appendix.configuration-metadata.manual-hints.value-providers.class-reference] +* xref:specification:configuration-metadata/manual-hints.adoc#appendix.configuration-metadata.manual-hints.value-providers.handle-as[#appendix.configuration-metadata.manual-hints.value-providers.handle-as] +* xref:specification:configuration-metadata/manual-hints.adoc#appendix.configuration-metadata.manual-hints.value-providers.logger-name[#appendix.configuration-metadata.manual-hints.value-providers.logger-name] +* xref:specification:configuration-metadata/manual-hints.adoc#appendix.configuration-metadata.manual-hints.value-providers.spring-bean-reference[#appendix.configuration-metadata.manual-hints.value-providers.spring-bean-reference] +* xref:specification:configuration-metadata/manual-hints.adoc#appendix.configuration-metadata.manual-hints.value-providers.spring-profile-name[#appendix.configuration-metadata.manual-hints.value-providers.spring-profile-name] +* xref:specification:executable-jar/alternatives.adoc#appendix.executable-jar.alternatives[#appendix.executable-jar.alternatives] +* xref:specification:executable-jar/index.adoc#appendix.executable-jar[#appendix.executable-jar] +* xref:specification:executable-jar/jarfile-class.adoc#appendix.executable-jar.jarfile-class[#appendix.executable-jar.jarfile-class] +* xref:specification:executable-jar/jarfile-class.adoc#appendix.executable-jar.jarfile-class.compatibility[#appendix.executable-jar.jarfile-class.compatibility] +* xref:specification:executable-jar/launching.adoc#appendix.executable-jar.launching[#appendix.executable-jar.launching] +* xref:specification:executable-jar/launching.adoc#appendix.executable-jar.launching.manifest[#appendix.executable-jar.launching.manifest] +* xref:specification:executable-jar/nested-jars.adoc#appendix.executable-jar.nested-jars[#appendix.executable-jar.nested-jars] +* xref:specification:executable-jar/nested-jars.adoc#appendix.executable-jar.nested-jars.classpath-index[#appendix.executable-jar.nested-jars.classpath-index] +* xref:specification:executable-jar/nested-jars.adoc#appendix.executable-jar.nested-jars.index-files[#appendix.executable-jar.nested-jars.index-files] +* xref:specification:executable-jar/nested-jars.adoc#appendix.executable-jar.nested-jars.jar-structure[#appendix.executable-jar.nested-jars.jar-structure] +* xref:specification:executable-jar/nested-jars.adoc#appendix.executable-jar.nested-jars.layer-index[#appendix.executable-jar.nested-jars.layer-index] +* xref:specification:executable-jar/nested-jars.adoc#appendix.executable-jar.nested-jars.war-structure[#appendix.executable-jar.nested-jars.war-structure] +* xref:specification:executable-jar/property-launcher.adoc#appendix.executable-jar.property-launcher[#appendix.executable-jar.property-launcher] +* xref:specification:executable-jar/restrictions.adoc#appendix.executable-jar-system-classloader[#appendix.executable-jar-system-classloader] +* xref:specification:executable-jar/restrictions.adoc#appendix.executable-jar-zip-entry-compression[#appendix.executable-jar-zip-entry-compression] +* xref:specification:executable-jar/restrictions.adoc#appendix.executable-jar.restrictions[#appendix.executable-jar.restrictions] +* xref:tutorial:first-application/index.adoc#getting-started.first-application[#getting-started.first-application] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.code[#getting-started.first-application.code] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.code.main-method[#getting-started.first-application.code.main-method] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.code.mvc-annotations[#getting-started.first-application.code.mvc-annotations] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.code.spring-boot-application[#getting-started-first-application-auto-configuration] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.code.spring-boot-application[#getting-started.first-application.code.spring-boot-application] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.dependencies[#getting-started.first-application.dependencies] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.dependencies.gradle[#getting-started.first-application.dependencies.gradle] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.dependencies.maven[#getting-started.first-application.dependencies.maven] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.executable-jar[#getting-started.first-application.executable-jar] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.executable-jar.gradle[#getting-started.first-application.executable-jar.gradle] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.executable-jar.maven[#getting-started.first-application.executable-jar.maven] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.gradle[#getting-started.first-application.gradle] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.pom[#getting-started.first-application.pom] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.prerequisites[#getting-started.first-application.prerequisites] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.prerequisites.gradle[#getting-started.first-application.prerequisites.gradle] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.prerequisites.maven[#getting-started.first-application.prerequisites.maven] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.run[#getting-started.first-application.run] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.run.gradle[#getting-started.first-application.run.gradle] +* xref:tutorial:first-application/index.adoc#getting-started.first-application.run.maven[#getting-started.first-application.run.maven] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/system-requirements.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/system-requirements.adoc new file mode 100644 index 000000000000..50f42abb6d4f --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/system-requirements.adoc @@ -0,0 +1,61 @@ +[[getting-started.system-requirements]] += System Requirements + +Spring Boot {version-spring-boot} requires https://www.java.com[Java 17] and is compatible up to and including Java 22. +{url-spring-framework-docs}/[Spring Framework {version-spring-framework}] or above is also required. + +Explicit build support is provided for the following build tools: + +|=== +| Build Tool | Version + +| Maven +| 3.6.3 or later + +| Gradle +| Gradle 7.x (7.6.4 or later) or 8.x (8.3 or later) +|=== + + + +[[getting-started.system-requirements.servlet-containers]] +== Servlet Containers + +Spring Boot supports the following embedded servlet containers: + +|=== +| Name | Servlet Version + +| Tomcat 10.1 +| 6.0 + +| Jetty 12.0 +| 6.0 + +| Undertow 2.3 +| 6.0 +|=== + +You can also deploy Spring Boot applications to any servlet 5.0+ compatible container. + + + +[[getting-started.system-requirements.graal]] +== GraalVM Native Images + +Spring Boot applications can be xref:reference:packaging/native-image/introducing-graalvm-native-images.adoc[converted into a Native Image] using GraalVM {version-graal} or above. + +Images can be created using the https://github.com/graalvm/native-build-tools[native build tools] Gradle/Maven plugins or `native-image` tool provided by GraalVM. +You can also create native images using the https://github.com/paketo-buildpacks/native-image[native-image Paketo buildpack]. + +The following versions are supported: + +|=== +| Name | Version + +| GraalVM Community +| {version-graal} + +| Native Build Tools +| {version-native-build-tools} +|=== diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/upgrading.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/upgrading.adoc new file mode 100644 index 000000000000..2d9f3bbff0dd --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/upgrading.adoc @@ -0,0 +1,46 @@ +[[upgrading]] += Upgrading Spring Boot + +Instructions for how to upgrade from earlier versions of Spring Boot are provided on the project {url-github-wiki}[wiki]. +Follow the links in the {url-github-wiki}#release-notes[release notes] section to find the version that you want to upgrade to. + +Upgrading instructions are always the first item in the release notes. +If you are more than one release behind, please make sure that you also review the release notes of the versions that you jumped. + + + +[[upgrading.from-1x]] +== Upgrading From 1.x + +If you are upgrading from the `1.x` release of Spring Boot, check the {url-github-wiki}/Spring-Boot-2.0-Migration-Guide[migration guide] on the project wiki that provides detailed upgrade instructions. +Check also the {url-github-wiki}[release notes] for a list of "`new and noteworthy`" features for each release. + + + +[[upgrading.to-feature]] +== Upgrading to a New Feature Release + +When upgrading to a new feature release, some properties may have been renamed or removed. +Spring Boot provides a way to analyze your application's environment and print diagnostics at startup, but also temporarily migrate properties at runtime for you. +To enable that feature, add the following dependency to your project: + +[source,xml] +---- + + org.springframework.boot + spring-boot-properties-migrator + runtime + +---- + +WARNING: Properties that are added late to the environment, such as when using `@PropertySource`, will not be taken into account. + +NOTE: Once you finish the migration, please make sure to remove this module from your project's dependencies. + + + +[[upgrading.cli]] +== Upgrading the Spring Boot CLI + +To upgrade an existing CLI installation, use the appropriate package manager command (for example, `brew upgrade`). +If you manually installed the CLI, follow the xref:installing.adoc#getting-started.installing.cli.manual-installation[standard instructions], remembering to update your `PATH` environment variable to remove any older references. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/partials/nav-root.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/partials/nav-root.adoc new file mode 100644 index 000000000000..01a331853fae --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/partials/nav-root.adoc @@ -0,0 +1,6 @@ +* xref:index.adoc[,role=navtree-icon-home] +* xref:documentation.adoc[,role=navtree-icon-book] +* xref:community.adoc[,role=navtree-icon-question] +* xref:system-requirements.adoc[,role=navtree-icon-server] +* xref:installing.adoc[,role=navtree-icon-gift] +* xref:upgrading.adoc[,role=navtree-icon-rocket] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/api/partials/nav-java-api.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/api/partials/nav-java-api.adoc new file mode 100644 index 000000000000..88c2f5ea8e81 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/api/partials/nav-java-api.adoc @@ -0,0 +1,4 @@ +* Java APIs +** xref:api:java/index.html[Spring Boot,role=link-external, window=_blank] +** xref:gradle-plugin:api/java/index.html[Gradle Plugin,role=link-external, window=_blank] +** xref:maven-plugin:api/java/index.html[Maven Plugin,role=link-external, window=_blank] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/api/partials/nav-kotlin-api.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/api/partials/nav-kotlin-api.adoc new file mode 100644 index 000000000000..d6ee79d2ecd4 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/api/partials/nav-kotlin-api.adoc @@ -0,0 +1,2 @@ +* Kotlin APIs +** xref:api:kotlin/index.html[Spring Boot,role=link-external, window=_blank] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/api/partials/nav-rest-api.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/api/partials/nav-rest-api.adoc new file mode 100644 index 000000000000..700b7b1f16b5 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/api/partials/nav-rest-api.adoc @@ -0,0 +1,5 @@ +* Rest APIs ++ +-- +include::api:partial$nav-actuator-rest-api.adoc[] +-- diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/application-properties/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/application-properties/index.adoc new file mode 100644 index 000000000000..7bbdb5c3576d --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/application-properties/index.adoc @@ -0,0 +1,49 @@ +[appendix] +[[appendix.application-properties]] += Common Application Properties + +Various properties can be specified inside your `application.properties` file, inside your `application.yaml` file, or as command line switches. +This appendix provides a list of common Spring Boot properties and references to the underlying classes that consume them. + +TIP: Spring Boot provides various conversion mechanisms with advanced value formatting. +Make sure to review xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.conversion[the properties conversion section]. + +NOTE: Property contributions can come from additional jar files on your classpath, so you should not consider this an exhaustive list. +Also, you can define your own properties. + + +include::partial$configuration-properties/core.adoc[] + +include::partial$configuration-properties/cache.adoc[] + +include::partial$configuration-properties/mail.adoc[] + +include::partial$configuration-properties/json.adoc[] + +include::partial$configuration-properties/data.adoc[] + +include::partial$configuration-properties/transaction.adoc[] + +include::partial$configuration-properties/data-migration.adoc[] + +include::partial$configuration-properties/integration.adoc[] + +include::partial$configuration-properties/web.adoc[] + +include::partial$configuration-properties/templating.adoc[] + +include::partial$configuration-properties/server.adoc[] + +include::partial$configuration-properties/security.adoc[] + +include::partial$configuration-properties/rsocket.adoc[] + +include::partial$configuration-properties/actuator.adoc[] + +include::partial$configuration-properties/devtools.adoc[] + +include::partial$configuration-properties/docker-compose.adoc[] + +include::partial$configuration-properties/testcontainers.adoc[] + +include::partial$configuration-properties/testing.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/auto-configuration-classes/actuator.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/auto-configuration-classes/actuator.adoc new file mode 100644 index 000000000000..620c7ea6989a --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/auto-configuration-classes/actuator.adoc @@ -0,0 +1,6 @@ +[[appendix.auto-configuration-classes.actuator]] += spring-boot-actuator-autoconfigure + +The following auto-configuration classes are from the `spring-boot-actuator-autoconfigure` module: + +include::partial$/auto-configuration-classes/spring-boot-actuator-autoconfigure.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/auto-configuration-classes/core.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/auto-configuration-classes/core.adoc new file mode 100644 index 000000000000..53ec8859ad9f --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/auto-configuration-classes/core.adoc @@ -0,0 +1,6 @@ +[[appendix.auto-configuration-classes.core]] += spring-boot-autoconfigure + +The following auto-configuration classes are from the `spring-boot-autoconfigure` module: + +include::partial$/auto-configuration-classes/spring-boot-autoconfigure.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/auto-configuration-classes/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/auto-configuration-classes/index.adoc new file mode 100644 index 000000000000..3da7db1122e6 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/auto-configuration-classes/index.adoc @@ -0,0 +1,7 @@ +[appendix] +[[appendix.auto-configuration-classes]] += Auto-configuration Classes + +This appendix contains details of all of the auto-configuration classes provided by Spring Boot, with links to documentation and source code. +Remember to also look at the conditions report in your application for more details of which features are switched on. +(To do so, start the app with `--debug` or `-Ddebug` or, in an Actuator application, use the `conditions` endpoint). diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/dependency-versions/coordinates.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/dependency-versions/coordinates.adoc similarity index 78% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/dependency-versions/coordinates.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/dependency-versions/coordinates.adoc index 1643a9a6c2dc..59b1e389e06e 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/dependency-versions/coordinates.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/dependency-versions/coordinates.adoc @@ -1,7 +1,7 @@ [[appendix.dependency-versions.coordinates]] -== Managed Dependency Coordinates += Managed Dependency Coordinates The following table provides details of all of the dependency versions that are provided by Spring Boot in its CLI (Command Line Interface), Maven dependency management, and Gradle plugin. When you declare a dependency on one of these artifacts without declaring a version, the version listed in the table is used. -include::documented-coordinates.adoc[] +include::partial$dependency-versions/documented-coordinates.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/dependency-versions/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/dependency-versions/index.adoc new file mode 100644 index 000000000000..163a2058750f --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/dependency-versions/index.adoc @@ -0,0 +1,5 @@ +[appendix] +[[appendix.dependency-versions]] += Dependency Versions + +This appendix provides details of the dependencies that are managed by Spring Boot. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/dependency-versions/properties.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/dependency-versions/properties.adoc new file mode 100644 index 000000000000..8088f0c018ad --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/dependency-versions/properties.adoc @@ -0,0 +1,8 @@ +[[appendix.dependency-versions.properties]] += Version Properties + +The following table provides all properties that can be used to override the versions managed by Spring Boot. +Browse the {code-spring-boot}/spring-boot-project/spring-boot-dependencies/build.gradle[`spring-boot-dependencies` build.gradle] for a complete list of dependencies. +You can learn how to customize these versions in your application in the xref:build-tool-plugin:index.adoc[] documentation. + +include::partial$dependency-versions/documented-properties.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/test-auto-configuration/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/test-auto-configuration/index.adoc new file mode 100644 index 000000000000..cc72fa03fcc9 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/test-auto-configuration/index.adoc @@ -0,0 +1,5 @@ +[appendix] +[[appendix.test-auto-configuration]] += Test Auto-configuration Annotations + +This appendix describes the `@...Test` auto-configuration annotations that Spring Boot provides to test slices of your application. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/test-auto-configuration/slices.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/test-auto-configuration/slices.adoc similarity index 77% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/test-auto-configuration/slices.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/test-auto-configuration/slices.adoc index 298de249c541..3303e1087c67 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/test-auto-configuration/slices.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/test-auto-configuration/slices.adoc @@ -1,6 +1,6 @@ [[appendix.test-auto-configuration.slices]] -== Test Slices += Test Slices The following table lists the various `@...Test` annotations that can be used to test slices of your application and the auto-configuration that they import by default: -include::documented-slices.adoc[] +include::partial$slices/documented-slices.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/partials/nav-appendix.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/partials/nav-appendix.adoc new file mode 100644 index 000000000000..564da76c9c77 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/partials/nav-appendix.adoc @@ -0,0 +1,13 @@ +* Appendix + +** xref:appendix:application-properties/index.adoc[] +** xref:appendix:auto-configuration-classes/index.adoc[] +*** xref:appendix:auto-configuration-classes/core.adoc[] +*** xref:appendix:auto-configuration-classes/actuator.adoc[] + +** xref:appendix:test-auto-configuration/index.adoc[] +*** xref:appendix:test-auto-configuration/slices.adoc[] + +** xref:appendix:dependency-versions/index.adoc[] +*** xref:appendix:dependency-versions/coordinates.adoc[] +*** xref:appendix:dependency-versions/properties.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/build-tool-plugin/pages/antlib.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/build-tool-plugin/pages/antlib.adoc new file mode 100644 index 000000000000..a2462eba1d54 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/build-tool-plugin/pages/antlib.adoc @@ -0,0 +1,154 @@ +[[build-tool-plugins.antlib]] += Spring Boot AntLib Module + +The Spring Boot AntLib module provides basic Spring Boot support for Apache Ant. +You can use the module to create executable jars. +To use the module, you need to declare an additional `spring-boot` namespace in your `build.xml`, as shown in the following example: + +[source,xml] +---- + + ... + +---- + +You need to remember to start Ant using the `-lib` option, as shown in the following example: + +[source,shell,subs="verbatim,attributes"] +---- +$ ant -lib +---- + +TIP: The "`Using Spring Boot`" section includes a more complete example of xref:reference:using/build-systems.adoc#using.build-systems.ant[using Apache Ant with `spring-boot-antlib`]. + + + +[[build-tool-plugins.antlib.tasks]] +== Spring Boot Ant Tasks + +Once the `spring-boot-antlib` namespace has been declared, the following additional tasks are available: + +* xref:antlib.adoc#build-tool-plugins.antlib.tasks.exejar[] +* xref:antlib.adoc#build-tool-plugins.antlib.findmainclass[] + + + +[[build-tool-plugins.antlib.tasks.exejar]] +=== Using the "`exejar`" Task + +You can use the `exejar` task to create a Spring Boot executable jar. +The following attributes are supported by the task: + +[cols="1,2,2"] +|==== +| Attribute | Description | Required + +| `destfile` +| The destination jar file to create +| Yes + +| `classes` +| The root directory of Java class files +| Yes + +| `start-class` +| The main application class to run +| No _(the default is the first class found that declares a `main` method)_ +|==== + +The following nested elements can be used with the task: + +[cols="1,4"] +|==== +| Element | Description + +| `resources` +| One or more {url-ant-docs}/Types/resources.html#collection[Resource Collections] describing a set of {url-ant-docs}/Types/resources.html[Resources] that should be added to the content of the created +jar+ file. + +| `lib` +| One or more {url-ant-docs}/Types/resources.html#collection[Resource Collections] that should be added to the set of jar libraries that make up the runtime dependency classpath of the application. +|==== + + + +[[build-tool-plugins.antlib.tasks.examples]] +=== Examples + +This section shows two examples of Ant tasks. + +.Specify +start-class+ +[source,xml] +---- + + + + + + + + +---- + +.Detect +start-class+ +[source,xml] +---- + + + + + +---- + + + +[[build-tool-plugins.antlib.findmainclass]] +== Using the "`findmainclass`" Task + +The `findmainclass` task is used internally by `exejar` to locate a class declaring a `main`. +If necessary, you can also use this task directly in your build. +The following attributes are supported: + +[cols="1,2,2"] +|==== +| Attribute | Description | Required + +| `classesroot` +| The root directory of Java class files +| Yes _(unless `mainclass` is specified)_ + +| `mainclass` +| Can be used to short-circuit the `main` class search +| No + +| `property` +| The Ant property that should be set with the result +| No _(result will be logged if unspecified)_ +|==== + + + +[[build-tool-plugins.antlib.findmainclass.examples]] +=== Examples + +This section contains three examples of using `findmainclass`. + +.Find and log +[source,xml] +---- + +---- + +.Find and set +[source,xml] +---- + +---- + +.Override and set +[source,xml] +---- + +---- diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/build-tool-plugin/pages/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/build-tool-plugin/pages/index.adoc new file mode 100644 index 000000000000..335a68436078 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/build-tool-plugin/pages/index.adoc @@ -0,0 +1,8 @@ +[[build-tool-plugins]] += Build Tool Plugins + +Spring Boot provides build tool plugins for Maven and Gradle. +The plugins offer a variety of features, including the packaging of executable jars. +This section provides more details on both plugins as well as some help should you need to extend an unsupported build system. +If you are just getting started, you might want to read xref:reference:using/build-systems.adoc[] from the xref:reference:using/index.adoc[] section first. + diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/build-tool-plugins/other-build-systems.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/build-tool-plugin/pages/other-build-systems.adoc similarity index 84% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/build-tool-plugins/other-build-systems.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/build-tool-plugin/pages/other-build-systems.adoc index 749778376d4a..5d7e90f243af 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/build-tool-plugins/other-build-systems.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/build-tool-plugin/pages/other-build-systems.adoc @@ -1,7 +1,8 @@ [[build-tool-plugins.other-build-systems]] -== Supporting Other Build Systems += Supporting Other Build Systems + If you want to use a build tool other than Maven, Gradle, or Ant, you likely need to develop your own plugin. -Executable jars need to follow a specific format and certain entries need to be written in an uncompressed form (see the "`<>`" section in the appendix for details). +Executable jars need to follow a specific format and certain entries need to be written in an uncompressed form (see the xref:specification:/executable-jar/index.adoc[executable jar format] section in the appendix for details). The Spring Boot Maven and Gradle plugins both make use of `spring-boot-loader-tools` to actually generate jars. If you need to, you may use this library directly. @@ -9,7 +10,8 @@ If you need to, you may use this library directly. [[build-tool-plugins.other-build-systems.repackaging-archives]] -=== Repackaging Archives +== Repackaging Archives + To repackage an existing archive so that it becomes a self-contained executable archive, use `org.springframework.boot.loader.tools.Repackager`. The `Repackager` class takes a single constructor argument that refers to an existing jar or war archive. Use one of the two available `repackage()` methods to either replace the original file or write to a new destination. @@ -18,7 +20,8 @@ Various settings can also be configured on the repackager before it is run. [[build-tool-plugins.other-build-systems.nested-libraries]] -=== Nested Libraries +== Nested Libraries + When repackaging an archive, you can include references to dependency files by using the `org.springframework.boot.loader.tools.Libraries` interface. We do not provide any concrete implementations of `Libraries` here as they are usually build-system-specific. @@ -27,14 +30,16 @@ If your archive already includes libraries, you can use `Libraries.NONE`. [[build-tool-plugins.other-build-systems.finding-main-class]] -=== Finding a Main Class +== Finding a Main Class + If you do not use `Repackager.setMainClass()` to specify a main class, the repackager uses https://asm.ow2.io/[ASM] to read class files and tries to find a suitable class with a `public static void main(String[] args)` method. An exception is thrown if more than one candidate is found. [[build-tool-plugins.other-build-systems.example-repackage-implementation]] -=== Example Repackage Implementation +== Example Repackage Implementation + The following example shows a typical repackage implementation: -include::code:MyBuildTool[] +include-code::MyBuildTool[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/build-tool-plugin/partials/nav-build-tool-plugin.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/build-tool-plugin/partials/nav-build-tool-plugin.adoc new file mode 100644 index 000000000000..dd2706c1080b --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/build-tool-plugin/partials/nav-build-tool-plugin.adoc @@ -0,0 +1,11 @@ +* xref:build-tool-plugin:index.adoc[] ++ +-- +include::maven-plugin:partial$nav-maven-plugin.adoc[] +-- ++ +-- +include::gradle-plugin:partial$nav-gradle-plugin.adoc[] +-- +** xref:build-tool-plugin:antlib.adoc[] +** xref:build-tool-plugin:other-build-systems.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/cli/pages/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/cli/pages/index.adoc new file mode 100644 index 000000000000..f30483a158e7 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/cli/pages/index.adoc @@ -0,0 +1,5 @@ +[[cli]] += Spring Boot CLI + +The Spring Boot CLI is a command line tool that you can use to bootstrap a new project from https://start.spring.io or encode a password. + diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/cli/pages/installation.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/cli/pages/installation.adoc new file mode 100644 index 000000000000..f18cd6db6153 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/cli/pages/installation.adoc @@ -0,0 +1,5 @@ +[[cli.installation]] += Installing the CLI + +The Spring Boot CLI (Command-Line Interface) can be installed manually by using SDKMAN! (the SDK Manager) or by using Homebrew or MacPorts if you are an OSX user. +See xref:ROOT:installing.adoc#getting-started.installing.cli[] in the "`Getting Started`" section for comprehensive installation instructions. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/cli/pages/using-the-cli.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/cli/pages/using-the-cli.adoc new file mode 100644 index 000000000000..9204522d3b53 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/cli/pages/using-the-cli.adoc @@ -0,0 +1,177 @@ +[[cli.using-the-cli]] += Using the CLI + +Once you have installed the CLI, you can run it by typing `spring` and pressing Enter at the command line. +If you run `spring` without any arguments, a help screen is displayed, as follows: + +[source,shell] +---- +$ spring +usage: spring [--help] [--version] + [] + +Available commands are: + + init [options] [location] + Initialize a new project using Spring Initializr (start.spring.io) + + encodepassword [options] + Encode a password for use with Spring Security + + shell + Start a nested shell + +Common options: + + --debug Verbose mode + Print additional status information for the command you are running + + +See 'spring help ' for more information on a specific command. +---- + +You can type `spring help` to get more details about any of the supported commands, as shown in the following example: + +[source,shell] +---- +$ spring help init +spring init - Initialize a new project using Spring Initializr (start.spring.io) + +usage: spring init [options] [location] + +Option Description +------ ----------- +-a, --artifact-id Project coordinates; infer archive name (for + example 'test') +-b, --boot-version Spring Boot version (for example '1.2.0.RELEASE') +--build Build system to use (for example 'maven' or + 'gradle') (default: maven) +-d, --dependencies Comma-separated list of dependency identifiers to + include in the generated project +--description Project description +-f, --force Force overwrite of existing files +--format Format of the generated content (for example + 'build' for a build file, 'project' for a + project archive) (default: project) +-g, --group-id Project coordinates (for example 'org.test') +-j, --java-version Language level (for example '1.8') +-l, --language Programming language (for example 'java') +--list List the capabilities of the service. Use it to + discover the dependencies and the types that are + available +-n, --name Project name; infer application name +-p, --packaging Project packaging (for example 'jar') +--package-name Package name +-t, --type Project type. Not normally needed if you use -- + build and/or --format. Check the capabilities of + the service (--list) for more details +--target URL of the service to use (default: https://start. + spring.io) +-v, --version Project version (for example '0.0.1-SNAPSHOT') +-x, --extract Extract the project archive. Inferred if a + location is specified without an extension + +examples: + + To list all the capabilities of the service: + $ spring init --list + + To creates a default project: + $ spring init + + To create a web my-app.zip: + $ spring init -d=web my-app.zip + + To create a web/data-jpa gradle project unpacked: + $ spring init -d=web,jpa --build=gradle my-dir +---- + +The `version` command provides a quick way to check which version of Spring Boot you are using, as follows: + +[source,shell,subs="verbatim,attributes"] +---- +$ spring version +Spring CLI v{version-spring-boot} +---- + + + +[[cli.using-the-cli.initialize-new-project]] +== Initialize a New Project + +The `init` command lets you create a new project by using https://start.spring.io without leaving the shell, as shown in the following example: + +[source,shell] +---- +$ spring init --dependencies=web,data-jpa my-project +Using service at https://start.spring.io +Project extracted to '/Users/developer/example/my-project' +---- + +The preceding example creates a `my-project` directory with a Maven-based project that uses `spring-boot-starter-web` and `spring-boot-starter-data-jpa`. +You can list the capabilities of the service by using the `--list` flag, as shown in the following example: + +[source,shell] +---- +$ spring init --list +======================================= +Capabilities of https://start.spring.io +======================================= + +Available dependencies: +----------------------- +actuator - Actuator: Production ready features to help you monitor and manage your application +... +web - Web: Support for full-stack web development, including Tomcat and spring-webmvc +websocket - Websocket: Support for WebSocket development +ws - WS: Support for Spring Web Services + +Available project types: +------------------------ +gradle-build - Gradle Config [format:build, build:gradle] +gradle-project - Gradle Project [format:project, build:gradle] +maven-build - Maven POM [format:build, build:maven] +maven-project - Maven Project [format:project, build:maven] (default) + +... +---- + +The `init` command supports many options. +See the `help` output for more details. +For instance, the following command creates a Gradle project that uses Java 17 and `war` packaging: + +[source,shell] +---- +$ spring init --build=gradle --java-version=17 --dependencies=websocket --packaging=war sample-app.zip +Using service at https://start.spring.io +Content saved to 'sample-app.zip' +---- + + + +[[cli.using-the-cli.embedded-shell]] +== Using the Embedded Shell + +Spring Boot includes command-line completion scripts for the BASH and zsh shells. +If you do not use either of these shells (perhaps you are a Windows user), you can use the `shell` command to launch an integrated shell, as shown in the following example: + +[source,shell,subs="verbatim,quotes,attributes"] +---- +$ spring shell +*Spring Boot* (v{version-spring-boot}) +Hit TAB to complete. Type \'help' and hit RETURN for help, and \'exit' to quit. +---- + +From inside the embedded shell, you can run other commands directly: + +[source,shell,subs="verbatim,attributes"] +---- +$ version +Spring CLI v{version-spring-boot} +---- + +The embedded shell supports ANSI color output as well as `tab` completion. +If you need to run a native command, you can use the `!` prefix. +To exit the embedded shell, press `ctrl-c`. + + diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/cli/partials/nav-cli.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/cli/partials/nav-cli.adoc new file mode 100644 index 000000000000..b24e0fcf0022 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/cli/partials/nav-cli.adoc @@ -0,0 +1,4 @@ +* xref:cli:index.adoc[] + +** xref:cli:installation.adoc[] +** xref:cli:using-the-cli.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/actuator.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/actuator.adoc new file mode 100644 index 000000000000..00108093686c --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/actuator.adoc @@ -0,0 +1,60 @@ +[[howto.actuator]] += Actuator + +Spring Boot includes the Spring Boot Actuator. +This section answers questions that often arise from its use. + + + +[[howto.actuator.change-http-port-or-address]] +== Change the HTTP Port or Address of the Actuator Endpoints + +In a standalone application, the Actuator HTTP port defaults to the same as the main HTTP port. +To make the application listen on a different port, set the external property: configprop:management.server.port[]. +To listen on a completely different network address (such as when you have an internal network for management and an external one for user applications), you can also set `management.server.address` to a valid IP address to which the server is able to bind. + +For more detail, see the xref:api:java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementServerProperties.html[`ManagementServerProperties`] source code and xref:reference:actuator/monitoring.adoc#actuator.monitoring.customizing-management-server-port[Customizing the Management Server Port] in the "`Production-Ready Features`" section. + + + +[[howto.actuator.customize-whitelabel-error-page]] +== Customize the '`whitelabel`' Error Page + +Spring Boot installs a '`whitelabel`' error page that you see in a browser client if you encounter a server error (machine clients consuming JSON and other media types should see a sensible response with the right error code). + +NOTE: Set `server.error.whitelabel.enabled=false` to switch the default error page off. +Doing so restores the default of the servlet container that you are using. +Note that Spring Boot still tries to resolve the error view, so you should probably add your own error page rather than disabling it completely. + +Overriding the error page with your own depends on the templating technology that you use. +For example, if you use Thymeleaf, you can add an `error.html` template. +If you use FreeMarker, you can add an `error.ftlh` template. +In general, you need a `View` that resolves with a name of `error` or a `@Controller` that handles the `/error` path. +Unless you replaced some of the default configuration, you should find a `BeanNameViewResolver` in your `ApplicationContext`, so a `@Bean` named `error` would be one way of doing that. +See {code-spring-boot-autoconfigure-src}/web/servlet/error/ErrorMvcAutoConfiguration.java[`ErrorMvcAutoConfiguration`] for more options. + +See also the section on xref:reference:web/servlet.adoc#web.servlet.spring-mvc.error-handling[] for details of how to register handlers in the servlet container. + + + +[[howto.actuator.customizing-sanitization]] +== Customizing Sanitization + +To take control over the sanitization, define a `SanitizingFunction` bean. +The `SanitizableData` with which the function is called provides access to the key and value as well as the `PropertySource` from which they came. +This allows you to, for example, sanitize every value that comes from a particular property source. +Each `SanitizingFunction` is called in order until a function changes the value of the sanitizable data. + + + +[[howto.actuator.map-health-indicators-to-metrics]] +== Map Health Indicators to Micrometer Metrics + +Spring Boot health indicators return a `Status` type to indicate the overall system health. +If you want to monitor or alert on levels of health for a particular application, you can export these statuses as metrics with Micrometer. +By default, the status codes "`UP`", "`DOWN`", "`OUT_OF_SERVICE`" and "`UNKNOWN`" are used by Spring Boot. +To export these, you will need to convert these states to some set of numbers so that they can be used with a Micrometer `Gauge`. + +The following example shows one way to write such an exporter: + +include-code::MyHealthMetricsExportConfiguration[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/aot.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/aot.adoc new file mode 100644 index 000000000000..02f1e8e6666e --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/aot.adoc @@ -0,0 +1,55 @@ +[[howto.aot]] += Ahead-of-Time Processing + +A number of questions often arise when people use the ahead-of-time processing of Spring Boot applications. +This section addresses those questions. + + + +[[howto.aot.conditions]] +== Conditions + +Ahead-of-time processing optimizes the application and evaluates {url-spring-framework-javadoc}/org/springframework/context/annotation/Conditional.html[conditions] based on the environment at build time. +xref:reference:features/profiles.adoc[Profiles] are implemented through conditions and are therefore affected, too. + +If you want beans that are created based on a condition in an ahead-of-time optimized application, you have to set up the environment when building the application. +The beans which are created while ahead-of-time processing at build time are then always created when running the application and can't be switched off. +To do this, you can set the profiles which should be used when building the application. + +For Maven, this works by setting the `profiles` configuration of the `spring-boot-maven-plugin:process-aot` execution: + +[source,xml] +---- + + native + + + + + org.springframework.boot + spring-boot-maven-plugin + + + process-aot + + profile-a,profile-b + + + + + + + + +---- + +For Gradle, you need to configure the `ProcessAot` task: + +[source,gradle] +---- +tasks.withType(org.springframework.boot.gradle.tasks.aot.ProcessAot).configureEach { + args('--spring.profiles.active=profile-a,profile-b') +} +---- + +Profiles which only change configuration properties that don't influence conditions are supported without limitations when running ahead-of-time optimized applications. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/application.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/application.adoc new file mode 100644 index 000000000000..a581868fa4ed --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/application.adoc @@ -0,0 +1,113 @@ +[[howto.application]] += Spring Boot Application + +This section includes topics relating directly to Spring Boot applications. + + + +[[howto.application.failure-analyzer]] +== Create Your Own FailureAnalyzer + +xref:api:java/org/springframework/boot/diagnostics/FailureAnalyzer.html[`FailureAnalyzer`] is a great way to intercept an exception on startup and turn it into a human-readable message, wrapped in a xref:api:java/org/springframework/boot/diagnostics/FailureAnalysis.html[`FailureAnalysis`]. +Spring Boot provides such an analyzer for application-context-related exceptions, JSR-303 validations, and more. +You can also create your own. + +`AbstractFailureAnalyzer` is a convenient extension of `FailureAnalyzer` that checks the presence of a specified exception type in the exception to handle. +You can extend from that so that your implementation gets a chance to handle the exception only when it is actually present. +If, for whatever reason, you cannot handle the exception, return `null` to give another implementation a chance to handle the exception. + +`FailureAnalyzer` implementations must be registered in `META-INF/spring.factories`. +The following example registers `ProjectConstraintViolationFailureAnalyzer`: + +[source,properties] +---- +org.springframework.boot.diagnostics.FailureAnalyzer=\ +com.example.ProjectConstraintViolationFailureAnalyzer +---- + +NOTE: If you need access to the `BeanFactory` or the `Environment`, declare them as constructor arguments in your `FailureAnalyzer` implementation. + + + +[[howto.application.troubleshoot-auto-configuration]] +== Troubleshoot Auto-configuration + +The Spring Boot auto-configuration tries its best to "`do the right thing`", but sometimes things fail, and it can be hard to tell why. + +There is a really useful `ConditionEvaluationReport` available in any Spring Boot `ApplicationContext`. +You can see it if you enable `DEBUG` logging output. +If you use the `spring-boot-actuator` (see the xref:actuator.adoc[] section), there is also a `conditions` endpoint that renders the report in JSON. +Use that endpoint to debug the application and see what features have been added (and which have not been added) by Spring Boot at runtime. + +Many more questions can be answered by looking at the source code and the API documentation. +When reading the code, remember the following rules of thumb: + +* Look for classes called `+*AutoConfiguration+` and read their sources. + Pay special attention to the `+@Conditional*+` annotations to find out what features they enable and when. + Add `--debug` to the command line or the System property `-Ddebug` to get a log on the console of all the auto-configuration decisions that were made in your app. + In a running application with actuator enabled, look at the `conditions` endpoint (`/actuator/conditions` or the JMX equivalent) for the same information. +* Look for classes that are `@ConfigurationProperties` (such as xref:api:java/org/springframework/boot/autoconfigure/web/ServerProperties.html[`ServerProperties`]) and read from there the available external configuration options. + The `@ConfigurationProperties` annotation has a `name` attribute that acts as a prefix to external properties. + Thus, `ServerProperties` has `prefix="server"` and its configuration properties are `server.port`, `server.address`, and others. + In a running application with actuator enabled, look at the `configprops` endpoint. +* Look for uses of the `bind` method on the `Binder` to pull configuration values explicitly out of the `Environment` in a relaxed manner. + It is often used with a prefix. +* Look for `@Value` annotations that bind directly to the `Environment`. +* Look for `@ConditionalOnExpression` annotations that switch features on and off in response to SpEL expressions, normally evaluated with placeholders resolved from the `Environment`. + + + +[[howto.application.customize-the-environment-or-application-context]] +== Customize the Environment or ApplicationContext Before It Starts + +A `SpringApplication` has `ApplicationListeners` and `ApplicationContextInitializers` that are used to apply customizations to the context or environment. +Spring Boot loads a number of such customizations for use internally from `META-INF/spring.factories`. +There is more than one way to register additional customizations: + +* Programmatically, per application, by calling the `addListeners` and `addInitializers` methods on `SpringApplication` before you run it. +* Declaratively, for all applications, by adding a `META-INF/spring.factories` and packaging a jar file that the applications all use as a library. + +The `SpringApplication` sends some special `ApplicationEvents` to the listeners (some even before the context is created) and then registers the listeners for events published by the `ApplicationContext` as well. +See xref:reference:features/spring-application.adoc#features.spring-application.application-events-and-listeners[] in the "`Spring Boot Features`" section for a complete list. + +It is also possible to customize the `Environment` before the application context is refreshed by using `EnvironmentPostProcessor`. +Each implementation should be registered in `META-INF/spring.factories`, as shown in the following example: + +[source] +---- +org.springframework.boot.env.EnvironmentPostProcessor=com.example.YourEnvironmentPostProcessor +---- + +The implementation can load arbitrary files and add them to the `Environment`. +For instance, the following example loads a YAML configuration file from the classpath: + +include-code::MyEnvironmentPostProcessor[] + +TIP: The `Environment` has already been prepared with all the usual property sources that Spring Boot loads by default. +It is therefore possible to get the location of the file from the environment. +The preceding example adds the `custom-resource` property source at the end of the list so that a key defined in any of the usual other locations takes precedence. +A custom implementation may define another order. + +CAUTION: While using `@PropertySource` on your `@SpringBootApplication` may seem to be a convenient way to load a custom resource in the `Environment`, we do not recommend it. +Such property sources are not added to the `Environment` until the application context is being refreshed. +This is too late to configure certain properties such as `+logging.*+` and `+spring.main.*+` which are read before refresh begins. + + + +[[howto.application.context-hierarchy]] +== Build an ApplicationContext Hierarchy (Adding a Parent or Root Context) + +You can use the `ApplicationBuilder` class to create parent/child `ApplicationContext` hierarchies. +See xref:reference:features/spring-application.adoc#features.spring-application.fluent-builder-api[] in the "`Spring Boot Features`" section for more information. + + + +[[howto.application.non-web-application]] +== Create a Non-web Application + +Not all Spring applications have to be web applications (or web services). +If you want to execute some code in a `main` method but also bootstrap a Spring application to set up the infrastructure to use, you can use the `SpringApplication` features of Spring Boot. +A `SpringApplication` changes its `ApplicationContext` class, depending on whether it thinks it needs a web application or not. +The first thing you can do to help it is to leave server-related dependencies (such as the servlet API) off the classpath. +If you cannot do that (for example, if you run two applications from the same code base) then you can explicitly call `setWebApplicationType(WebApplicationType.NONE)` on your `SpringApplication` instance or set the `applicationContextClass` property (through the Java API or with external properties). +Application code that you want to run as your business logic can be implemented as a `CommandLineRunner` and dropped into the context as a `@Bean` definition. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/batch.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/batch.adoc new file mode 100644 index 000000000000..6fc45c23eb72 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/batch.adoc @@ -0,0 +1,86 @@ +[[howto.batch]] += Batch Applications + +A number of questions often arise when people use Spring Batch from within a Spring Boot application. +This section addresses those questions. + + + +[[howto.batch.specifying-a-data-source]] +== Specifying a Batch Data Source + +By default, batch applications require a `DataSource` to store job details. +Spring Batch expects a single `DataSource` by default. +To have it use a `DataSource` other than the application’s main `DataSource`, declare a `DataSource` bean, annotating its `@Bean` method with `@BatchDataSource`. +If you do so and want two data sources, remember to mark the other one `@Primary`. +To take greater control, add `@EnableBatchProcessing` to one of your `@Configuration` classes or extend `DefaultBatchConfiguration`. +See the API documentation of {url-spring-batch-javadoc}/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.html[`@EnableBatchProcessing`] +and {url-spring-batch-javadoc}/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.html[`DefaultBatchConfiguration`] for more details. + +For more info about Spring Batch, see the {url-spring-batch-site}[Spring Batch project page]. + + + +[[howto.batch.specifying-a-transaction-manager]] +== Specifying a Batch Transaction Manager + +Similar to xref:batch.adoc#howto.batch.specifying-a-data-source[], you can define a `PlatformTransactionManager` for use in the batch processing by marking it as `@BatchTransactionManager`. +If you do so and want two transaction managers, remember to mark the other one as `@Primary`. + + + +[[howto.batch.running-jobs-on-startup]] +== Running Spring Batch Jobs on Startup + +Spring Batch auto-configuration is enabled by adding `spring-boot-starter-batch` to your application's classpath. + +If a single `Job` bean is found in the application context, it is executed on startup (see xref:api:java/org/springframework/boot/autoconfigure/batch/JobLauncherApplicationRunner.html[`JobLauncherApplicationRunner`] for details). +If multiple `Job` beans are found, the job that should be executed must be specified using configprop:spring.batch.job.name[]. + +To disable running a `Job` found in the application context, set the configprop:spring.batch.job.enabled[] to `false`. + +See {code-spring-boot-autoconfigure-src}/batch/BatchAutoConfiguration.java[`BatchAutoConfiguration`] for more details. + + + +[[howto.batch.running-from-the-command-line]] +== Running From the Command Line + +Spring Boot converts any command line argument starting with `--` to a property to add to the `Environment`, see xref:reference:features/external-config.adoc#features.external-config.command-line-args[accessing command line properties]. +This should not be used to pass arguments to batch jobs. +To specify batch arguments on the command line, use the regular format (that is without `--`), as shown in the following example: + +[source,shell] +---- +$ java -jar myapp.jar someParameter=someValue anotherParameter=anotherValue +---- + +If you specify a property of the `Environment` on the command line, it is ignored by the job. +Consider the following command: + +[source,shell] +---- +$ java -jar myapp.jar --server.port=7070 someParameter=someValue +---- + +This provides only one argument to the batch job: `someParameter=someValue`. + + + +[[howto.batch.restarting-a-failed-job]] +== Restarting a Stopped or Failed Job + +To restart a failed `Job`, all parameters (identifying and non-identifying) must be re-specified on the command line. +Non-identifying parameters are *not* copied from the previous execution. +This allows them to be modified or removed. + +NOTE: When you're using a custom `JobParametersIncrementer`, you have to gather all parameters managed by the incrementer to restart a failed execution. + + + +[[howto.batch.storing-job-repository]] +== Storing the Job Repository + +Spring Batch requires a data store for the `Job` repository. +If you use Spring Boot, you must use an actual database. +Note that it can be an in-memory database, see {url-spring-batch-docs}/job.html#configuringJobRepository[Configuring a Job Repository]. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/build.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/build.adoc new file mode 100644 index 000000000000..49c67dd550fe --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/build.adoc @@ -0,0 +1,308 @@ +[[howto.build]] += Build + +Spring Boot includes build plugins for Maven and Gradle. +This section answers common questions about these plugins. + + + +[[howto.build.generate-info]] +== Generate Build Information + +Both the Maven plugin and the Gradle plugin allow generating build information containing the coordinates, name, and version of the project. +The plugins can also be configured to add additional properties through configuration. +When such a file is present, Spring Boot auto-configures a `BuildProperties` bean. + +To generate build information with Maven, add an execution for the `build-info` goal, as shown in the following example: + +[source,xml,subs="verbatim,attributes"] +---- + + + + org.springframework.boot + spring-boot-maven-plugin + {version-spring-boot} + + + + build-info + + + + + + +---- + +TIP: See the xref:maven-plugin:build-info.adoc[Spring Boot Maven Plugin documentation] for more details. + +The following example does the same with Gradle: + +[source,gradle] +---- +springBoot { + buildInfo() +} +---- + +TIP: See the xref:gradle-plugin:integrating-with-actuator.adoc[Spring Boot Gradle Plugin documentation] for more details. + + + +[[howto.build.generate-git-info]] +== Generate Git Information + +Both Maven and Gradle allow generating a `git.properties` file containing information about the state of your `git` source code repository when the project was built. + +For Maven users, the `spring-boot-starter-parent` POM includes a pre-configured plugin to generate a `git.properties` file. +To use it, add the following declaration for the https://github.com/git-commit-id/git-commit-id-maven-plugin[`Git Commit Id Plugin`] to your POM: + +[source,xml] +---- + + + + io.github.git-commit-id + git-commit-id-maven-plugin + + + +---- + +Gradle users can achieve the same result by using the https://plugins.gradle.org/plugin/com.gorylenko.gradle-git-properties[`gradle-git-properties`] plugin, as shown in the following example: + +[source,gradle] +---- +plugins { + id "com.gorylenko.gradle-git-properties" version "2.4.1" +} +---- + +Both the Maven and Gradle plugins allow the properties that are included in `git.properties` to be configured. + +TIP: The commit time in `git.properties` is expected to match the following format: `yyyy-MM-dd'T'HH:mm:ssZ`. +This is the default format for both plugins listed above. +Using this format lets the time be parsed into a `Date` and its format, when serialized to JSON, to be controlled by Jackson's date serialization configuration settings. + + + +[[howto.build.customize-dependency-versions]] +== Customize Dependency Versions + +The `spring-boot-dependencies` POM manages the versions of common dependencies. +The Spring Boot plugins for Maven and Gradle allow these managed dependency versions to be customized using build properties. + +WARNING: Each Spring Boot release is designed and tested against this specific set of third-party dependencies. +Overriding versions may cause compatibility issues. + +To override dependency versions with Maven, see xref:maven-plugin:using.adoc[] in the Maven plugin's documentation. + +To override dependency versions in Gradle, see xref:gradle-plugin:managing-dependencies.adoc#managing-dependencies.dependency-management-plugin.customizing[] in the Gradle plugin's documentation. + + + +[[howto.build.create-an-executable-jar-with-maven]] +== Create an Executable JAR with Maven + +The `spring-boot-maven-plugin` can be used to create an executable "`fat`" JAR. +If you use the `spring-boot-starter-parent` POM, you can declare the plugin and your jars are repackaged as follows: + +[source,xml] +---- + + + + org.springframework.boot + spring-boot-maven-plugin + + + +---- + +If you do not use the parent POM, you can still use the plugin. +However, you must additionally add an `` section, as follows: + +[source,xml,subs="verbatim,attributes"] +---- + + + + org.springframework.boot + spring-boot-maven-plugin + {version-spring-boot} + + + + repackage + + + + + + +---- + +See the xref:maven-plugin:packaging.adoc#packaging.repackage-goal[plugin documentation] for full usage details. + + + +[[howto.build.use-a-spring-boot-application-as-dependency]] +== Use a Spring Boot Application as a Dependency + +Like a war file, a Spring Boot application is not intended to be used as a dependency. +If your application contains classes that you want to share with other projects, the recommended approach is to move that code into a separate module. +The separate module can then be depended upon by your application and other projects. + +If you cannot rearrange your code as recommended above, Spring Boot's Maven and Gradle plugins must be configured to produce a separate artifact that is suitable for use as a dependency. +The executable archive cannot be used as a dependency as the xref:specification:executable-jar/nested-jars.adoc#appendix.executable-jar.nested-jars.jar-structure[executable jar format] packages application classes in `BOOT-INF/classes`. +This means that they cannot be found when the executable jar is used as a dependency. + +To produce the two artifacts, one that can be used as a dependency and one that is executable, a classifier must be specified. +This classifier is applied to the name of the executable archive, leaving the default archive for use as a dependency. + +To configure a classifier of `exec` in Maven, you can use the following configuration: + +[source,xml] +---- + + + + org.springframework.boot + spring-boot-maven-plugin + + exec + + + + +---- + + + +[[howto.build.extract-specific-libraries-when-an-executable-jar-runs]] +== Extract Specific Libraries When an Executable Jar Runs + +Most nested libraries in an executable jar do not need to be unpacked in order to run. +However, certain libraries can have problems. +For example, JRuby includes its own nested jar support, which assumes that the `jruby-complete.jar` is always directly available as a file in its own right. + +To deal with any problematic libraries, you can flag that specific nested jars should be automatically unpacked when the executable jar first runs. +Such nested jars are written beneath the temporary directory identified by the `java.io.tmpdir` system property. + +WARNING: Care should be taken to ensure that your operating system is configured so that it will not delete the jars that have been unpacked to the temporary directory while the application is still running. + +For example, to indicate that JRuby should be flagged for unpacking by using the Maven Plugin, you would add the following configuration: + +[source,xml] +---- + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.jruby + jruby-complete + + + + + + +---- + + + +[[howto.build.create-a-nonexecutable-jar]] +== Create a Non-executable JAR with Exclusions + +Often, if you have an executable and a non-executable jar as two separate build products, the executable version has additional configuration files that are not needed in a library jar. +For example, the `application.yaml` configuration file might be excluded from the non-executable JAR. + +In Maven, the executable jar must be the main artifact and you can add a classified jar for the library, as follows: + +[source,xml] +---- + + + + org.springframework.boot + spring-boot-maven-plugin + + + maven-jar-plugin + + + lib + package + + jar + + + lib + + application.yaml + + + + + + + +---- + + + +[[howto.build.remote-debug-maven]] +== Remote Debug a Spring Boot Application Started with Maven + +To attach a remote debugger to a Spring Boot application that was started with Maven, you can use the `jvmArguments` property of the xref:maven-plugin:index.adoc[maven plugin]. + +See xref:maven-plugin:run.adoc#run.examples.debug[this example] for more details. + + + +[[howto.build.build-an-executable-archive-with-ant-without-using-spring-boot-antlib]] +== Build an Executable Archive From Ant without Using spring-boot-antlib + +To build with Ant, you need to grab dependencies, compile, and then create a jar or war archive. +To make it executable, you can either use the `spring-boot-antlib` module or you can follow these instructions: + +. If you are building a jar, package the application's classes and resources in a nested `BOOT-INF/classes` directory. + If you are building a war, package the application's classes in a nested `WEB-INF/classes` directory as usual. +. Add the runtime dependencies in a nested `BOOT-INF/lib` directory for a jar or `WEB-INF/lib` for a war. + Remember *not* to compress the entries in the archive. +. Add the `provided` (embedded container) dependencies in a nested `BOOT-INF/lib` directory for a jar or `WEB-INF/lib-provided` for a war. + Remember *not* to compress the entries in the archive. +. Add the `spring-boot-loader` classes at the root of the archive (so that the `Main-Class` is available). +. Use the appropriate launcher (such as `JarLauncher` for a jar file) as a `Main-Class` attribute in the manifest and specify the other properties it needs as manifest entries -- principally, by setting a `Start-Class` property. + +The following example shows how to build an executable archive with Ant: + +[source,xml] +---- + + + + + + + + + + + + + + + + + + + + + +---- diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/class-data-sharing.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/class-data-sharing.adoc new file mode 100644 index 000000000000..7b94fec7a464 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/class-data-sharing.adoc @@ -0,0 +1,22 @@ +[[howto.class-data-sharing]] += Class Data Sharing + +This section includes information about using Class Data Sharing (CDS) with Spring Boot applications. +For an overview of Spring Boot support for CDS, see xref:reference:packaging/class-data-sharing.adoc[]. + + +[[howto.class-data-sharing.buildpacks]] +== Packaging an Application Using CDS and Buildpacks + +Spring Boot's xref:reference:packaging/container-images/cloud-native-buildpacks.adoc[support for Cloud Native Buildpacks] along with the https://paketo.io/docs/reference/java-reference[Paketo Java buildpack] and its https://paketo.io/docs/reference/java-reference/#spring-boot-applications[Spring Boot support] can be used to generate a Docker image containing a CDS-optimized application. + +To enable CDS optimization in a generated Docker image, the buildpack environment variable `BP_JVM_CDS_ENABLED` should be set to `true` when building the image as described in the xref:maven-plugin:build-image.adoc#build-image.examples.builder-configuration[Maven plugin] and xref:gradle-plugin:packaging-oci-image.adoc#build-image.examples.builder-configuration[Gradle plugin] documentation. +This will cause the buildpack to do a training run of the application, save the CDS archive in the image, and use the CDS archive when launching the application. + +The Paketo Buildpack for Spring Boot https://github.com/paketo-buildpacks/spring-boot?tab=readme-ov-file#configuration[documentation] has information on other configuration options that can be enabled with builder environment variables, like `CDS_TRAINING_JAVA_TOOL_OPTIONS` that allows to override the default `JAVA_TOOL_OPTIONS`, only for the CDS training run. + +[[howto.class-data-sharing.training-run-configuration]] +== Preventing Remote Services Interaction During the Training Run + +When performing the training run, it may be needed to customize the Spring Boot application configuration to prevent connections to remote services that may happen before the Spring lifecycle is started. +This can typically happen with early database interactions and can be handled via related configuration that can be applied by default to your application (or specifically to the training run) to prevent such interactions, see https://github.com/spring-projects/spring-lifecycle-smoke-tests/blob/main/README.adoc#training-run-configuration[related documentation]. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/data-access.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/data-access.adoc new file mode 100644 index 000000000000..d3fb68cbfa07 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/data-access.adoc @@ -0,0 +1,397 @@ +[[howto.data-access]] += Data Access + +Spring Boot includes a number of starters for working with data sources. +This section answers questions related to doing so. + + + +[[howto.data-access.configure-custom-datasource]] +== Configure a Custom DataSource + +To configure your own `DataSource`, define a `@Bean` of that type in your configuration. +Spring Boot reuses your `DataSource` anywhere one is required, including database initialization. +If you need to externalize some settings, you can bind your `DataSource` to the environment (see xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.third-party-configuration[]). + +The following example shows how to define a data source in a bean: + +include-code::custom/MyDataSourceConfiguration[] + +The following example shows how to define a data source by setting its properties: + +[configprops%novalidate,yaml] +---- +app: + datasource: + url: "jdbc:h2:mem:mydb" + username: "sa" + pool-size: 30 +---- + +Assuming that `SomeDataSource` has regular JavaBean properties for the URL, the username, and the pool size, these settings are bound automatically before the `DataSource` is made available to other components. + +Spring Boot also provides a utility builder class, called `DataSourceBuilder`, that can be used to create one of the standard data sources (if it is on the classpath). +The builder can detect which one to use based on what is available on the classpath. +It also auto-detects the driver based on the JDBC URL. + +The following example shows how to create a data source by using a `DataSourceBuilder`: + +include-code::builder/MyDataSourceConfiguration[] + +To run an app with that `DataSource`, all you need is the connection information. +Pool-specific settings can also be provided. +Check the implementation that is going to be used at runtime for more details. + +The following example shows how to define a JDBC data source by setting properties: + +[configprops%novalidate,yaml] +---- +app: + datasource: + url: "jdbc:mysql://localhost/test" + username: "dbuser" + password: "dbpass" + pool-size: 30 +---- + +However, there is a catch. +Because the actual type of the connection pool is not exposed, no keys are generated in the metadata for your custom `DataSource` and no completion is available in your IDE (because the `DataSource` interface exposes no properties). +Also, if you happen to have Hikari on the classpath, this basic setup does not work, because Hikari has no `url` property (but does have a `jdbcUrl` property). +In that case, you must rewrite your configuration as follows: + +[configprops%novalidate,yaml] +---- +app: + datasource: + jdbc-url: "jdbc:mysql://localhost/test" + username: "dbuser" + password: "dbpass" + pool-size: 30 +---- + +You can fix that by forcing the connection pool to use and return a dedicated implementation rather than `DataSource`. +You cannot change the implementation at runtime, but the list of options will be explicit. + +The following example shows how to create a `HikariDataSource` with `DataSourceBuilder`: + +include-code::simple/MyDataSourceConfiguration[] + +You can even go further by leveraging what `DataSourceProperties` does for you -- that is, by providing a default embedded database with a sensible username and password if no URL is provided. +You can easily initialize a `DataSourceBuilder` from the state of any `DataSourceProperties` object, so you could also inject the DataSource that Spring Boot creates automatically. +However, that would split your configuration into two namespaces: `url`, `username`, `password`, `type`, and `driver` on `spring.datasource` and the rest on your custom namespace (`app.datasource`). +To avoid that, you can redefine a custom `DataSourceProperties` on your custom namespace, as shown in the following example: + +include-code::configurable/MyDataSourceConfiguration[] + +This setup puts you _in sync_ with what Spring Boot does for you by default, except that a dedicated connection pool is chosen (in code) and its settings are exposed in the `app.datasource.configuration` sub namespace. +Because `DataSourceProperties` is taking care of the `url`/`jdbcUrl` translation for you, you can configure it as follows: + +[configprops%novalidate,yaml] +---- +app: + datasource: + url: "jdbc:mysql://localhost/test" + username: "dbuser" + password: "dbpass" + configuration: + maximum-pool-size: 30 +---- + +TIP: Spring Boot will expose Hikari-specific settings to `spring.datasource.hikari`. +This example uses a more generic `configuration` sub namespace as the example does not support multiple datasource implementations. + +NOTE: Because your custom configuration chooses to go with Hikari, `app.datasource.type` has no effect. +In practice, the builder is initialized with whatever value you might set there and then overridden by the call to `.type()`. + +See xref:reference:data/sql.adoc#data.sql.datasource[] in the "`Spring Boot Features`" section and the {code-spring-boot-autoconfigure-src}/jdbc/DataSourceAutoConfiguration.java[`DataSourceAutoConfiguration`] class for more details. + + + +[[howto.data-access.configure-two-datasources]] +== Configure Two DataSources + +If you need to configure multiple data sources, you can apply the same tricks that were described in the previous section. +You must, however, mark one of the `DataSource` instances as `@Primary`, because various auto-configurations down the road expect to be able to get one by type. + +If you create your own `DataSource`, the auto-configuration backs off. +In the following example, we provide the _exact_ same feature set as the auto-configuration provides on the primary data source: + +include-code::MyDataSourcesConfiguration[] + +TIP: `firstDataSourceProperties` has to be flagged as `@Primary` so that the database initializer feature uses your copy (if you use the initializer). + +Both data sources are also bound for advanced customizations. +For instance, you could configure them as follows: + +[configprops%novalidate,yaml] +---- +app: + datasource: + first: + url: "jdbc:mysql://localhost/first" + username: "dbuser" + password: "dbpass" + configuration: + maximum-pool-size: 30 + + second: + url: "jdbc:mysql://localhost/second" + username: "dbuser" + password: "dbpass" + max-total: 30 +---- + +You can apply the same concept to the secondary `DataSource` as well, as shown in the following example: + +include-code::MyCompleteDataSourcesConfiguration[] + +The preceding example configures two data sources on custom namespaces with the same logic as Spring Boot would use in auto-configuration. +Note that each `configuration` sub namespace provides advanced settings based on the chosen implementation. + + + +[[howto.data-access.spring-data-repositories]] +== Use Spring Data Repositories + +Spring Data can create implementations of `@Repository` interfaces of various flavors. +Spring Boot handles all of that for you, as long as those `@Repository` annotations are included in one of the xref:reference:using/auto-configuration.adoc#using.auto-configuration.packages[auto-configuration packages], typically the package (or a sub-package) of your main application class that is annotated with `@SpringBootApplication` or `@EnableAutoConfiguration`. + +For many applications, all you need is to put the right Spring Data dependencies on your classpath. +There is a `spring-boot-starter-data-jpa` for JPA, `spring-boot-starter-data-mongodb` for Mongodb, and various other starters for supported technologies. +To get started, create some repository interfaces to handle your `@Entity` objects. + +Spring Boot determines the location of your `@Repository` definitions by scanning the xref:reference:using/auto-configuration.adoc#using.auto-configuration.packages[auto-configuration packages]. +For more control, use the `@Enable…Repositories` annotations from Spring Data. + +For more about Spring Data, see the {url-spring-data-site}[Spring Data project page]. + + + +[[howto.data-access.separate-entity-definitions-from-spring-configuration]] +== Separate @Entity Definitions from Spring Configuration + +Spring Boot determines the location of your `@Entity` definitions by scanning the xref:reference:using/auto-configuration.adoc#using.auto-configuration.packages[auto-configuration packages]. +For more control, use the `@EntityScan` annotation, as shown in the following example: + +include-code::MyApplication[] + + + +[[howto.data-access.filter-scanned-entity-definitions]] +== Filter Scanned @Entity Definitions + +It is possible to filter the `@Entity` definitions using a `ManagedClassNameFilter` bean. +This can be useful in tests when only a sub-set of the available entities should be considered. +In the following example, only entities from the `com.example.app.customer` package are included: + +include-code::MyEntityScanConfiguration[] + + + +[[howto.data-access.jpa-properties]] +== Configure JPA Properties + +Spring Data JPA already provides some vendor-independent configuration options (such as those for SQL logging), and Spring Boot exposes those options and a few more for Hibernate as external configuration properties. +Some of them are automatically detected according to the context so you should not have to set them. + +The `spring.jpa.hibernate.ddl-auto` is a special case, because, depending on runtime conditions, it has different defaults. +If an embedded database is used and no schema manager (such as Liquibase or Flyway) is handling the `DataSource`, it defaults to `create-drop`. +In all other cases, it defaults to `none`. + +The dialect to use is detected by the JPA provider. +If you prefer to set the dialect yourself, set the configprop:spring.jpa.database-platform[] property. + +The most common options to set are shown in the following example: + +[configprops,yaml] +---- +spring: + jpa: + hibernate: + naming: + physical-strategy: "com.example.MyPhysicalNamingStrategy" + show-sql: true +---- + +In addition, all properties in `+spring.jpa.properties.*+` are passed through as normal JPA properties (with the prefix stripped) when the local `EntityManagerFactory` is created. + +[WARNING] +==== +You need to ensure that names defined under `+spring.jpa.properties.*+` exactly match those expected by your JPA provider. +Spring Boot will not attempt any kind of relaxed binding for these entries. + +For example, if you want to configure Hibernate's batch size you must use `+spring.jpa.properties.hibernate.jdbc.batch_size+`. +If you use other forms, such as `batchSize` or `batch-size`, Hibernate will not apply the setting. +==== + +TIP: If you need to apply advanced customization to Hibernate properties, consider registering a `HibernatePropertiesCustomizer` bean that will be invoked prior to creating the `EntityManagerFactory`. +This takes precedence over anything that is applied by the auto-configuration. + + + +[[howto.data-access.configure-hibernate-naming-strategy]] +== Configure Hibernate Naming Strategy + +Hibernate uses {url-hibernate-userguide}#naming[two different naming strategies] to map names from the object model to the corresponding database names. +The fully qualified class name of the physical and the implicit strategy implementations can be configured by setting the `spring.jpa.hibernate.naming.physical-strategy` and `spring.jpa.hibernate.naming.implicit-strategy` properties, respectively. +Alternatively, if `ImplicitNamingStrategy` or `PhysicalNamingStrategy` beans are available in the application context, Hibernate will be automatically configured to use them. + +By default, Spring Boot configures the physical naming strategy with `CamelCaseToUnderscoresNamingStrategy`. +Using this strategy, all dots are replaced by underscores and camel casing is replaced by underscores as well. +Additionally, by default, all table names are generated in lower case. +For example, a `TelephoneNumber` entity is mapped to the `telephone_number` table. +If your schema requires mixed-case identifiers, define a custom `CamelCaseToUnderscoresNamingStrategy` bean, as shown in the following example: + +include-code::spring/MyHibernateConfiguration[] + +If you prefer to use Hibernate's default instead, set the following property: + +[configprops,yaml] +---- +spring: + jpa: + hibernate: + naming: + physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl +---- + +Alternatively, you can configure the following bean: + +include-code::standard/MyHibernateConfiguration[] + +See {code-spring-boot-autoconfigure-src}/orm/jpa/HibernateJpaAutoConfiguration.java[`HibernateJpaAutoConfiguration`] and {code-spring-boot-autoconfigure-src}/orm/jpa/JpaBaseConfiguration.java[`JpaBaseConfiguration`] for more details. + + + +[[howto.data-access.configure-hibernate-second-level-caching]] +== Configure Hibernate Second-Level Caching + +Hibernate {url-hibernate-userguide}#caching[second-level cache] can be configured for a range of cache providers. +Rather than configuring Hibernate to lookup the cache provider again, it is better to provide the one that is available in the context whenever possible. + +To do this with JCache, first make sure that `org.hibernate.orm:hibernate-jcache` is available on the classpath. +Then, add a `HibernatePropertiesCustomizer` bean as shown in the following example: + +include-code::MyHibernateSecondLevelCacheConfiguration[] + +This customizer will configure Hibernate to use the same `CacheManager` as the one that the application uses. +It is also possible to use separate `CacheManager` instances. +For details, see {url-hibernate-userguide}#caching-provider-jcache[the Hibernate user guide]. + + + +[[howto.data-access.dependency-injection-in-hibernate-components]] +== Use Dependency Injection in Hibernate Components + +By default, Spring Boot registers a `BeanContainer` implementation that uses the `BeanFactory` so that converters and entity listeners can use regular dependency injection. + +You can disable or tune this behavior by registering a `HibernatePropertiesCustomizer` that removes or changes the `hibernate.resource.beans.container` property. + + + +[[howto.data-access.use-custom-entity-manager]] +== Use a Custom EntityManagerFactory + +To take full control of the configuration of the `EntityManagerFactory`, you need to add a `@Bean` named '`entityManagerFactory`'. +Spring Boot auto-configuration switches off its entity manager in the presence of a bean of that type. + + + +[[howto.data-access.use-multiple-entity-managers]] +== Using Multiple EntityManagerFactories + +If you need to use JPA against multiple data sources, you likely need one `EntityManagerFactory` per data source. +The `LocalContainerEntityManagerFactoryBean` from Spring ORM allows you to configure an `EntityManagerFactory` for your needs. +You can also reuse `JpaProperties` to bind settings for each `EntityManagerFactory`, as shown in the following example: + +include-code::MyEntityManagerFactoryConfiguration[] + +The example above creates an `EntityManagerFactory` using a `DataSource` bean named `firstDataSource`. +It scans entities located in the same package as `Order`. +It is possible to map additional JPA properties using the `app.first.jpa` namespace. + +NOTE: When you create a bean for `LocalContainerEntityManagerFactoryBean` yourself, any customization that was applied during the creation of the auto-configured `LocalContainerEntityManagerFactoryBean` is lost. +For example, in the case of Hibernate, any properties under the `spring.jpa.hibernate` prefix will not be automatically applied to your `LocalContainerEntityManagerFactoryBean`. +If you were relying on these properties for configuring things like the naming strategy or the DDL mode, you will need to explicitly configure that when creating the `LocalContainerEntityManagerFactoryBean` bean. + +You should provide a similar configuration for any additional data sources for which you need JPA access. +To complete the picture, you need to configure a `JpaTransactionManager` for each `EntityManagerFactory` as well. +Alternatively, you might be able to use a JTA transaction manager that spans both. + +If you use Spring Data, you need to configure `@EnableJpaRepositories` accordingly, as shown in the following examples: + +include-code::OrderConfiguration[] + +include-code::CustomerConfiguration[] + + + +[[howto.data-access.use-traditional-persistence-xml]] +== Use a Traditional persistence.xml File + +Spring Boot will not search for or use a `META-INF/persistence.xml` by default. +If you prefer to use a traditional `persistence.xml`, you need to define your own `@Bean` of type `LocalEntityManagerFactoryBean` (with an ID of '`entityManagerFactory`') and set the persistence unit name there. + +See {code-spring-boot-autoconfigure-src}/orm/jpa/JpaBaseConfiguration.java[`JpaBaseConfiguration`] for the default settings. + + + +[[howto.data-access.use-spring-data-jpa-and-mongo-repositories]] +== Use Spring Data JPA and Mongo Repositories + +Spring Data JPA and Spring Data Mongo can both automatically create `Repository` implementations for you. +If they are both present on the classpath, you might have to do some extra configuration to tell Spring Boot which repositories to create. +The most explicit way to do that is to use the standard Spring Data `+@EnableJpaRepositories+` and `+@EnableMongoRepositories+` annotations and provide the location of your `Repository` interfaces. + +There are also flags (`+spring.data.*.repositories.enabled+` and `+spring.data.*.repositories.type+`) that you can use to switch the auto-configured repositories on and off in external configuration. +Doing so is useful, for instance, in case you want to switch off the Mongo repositories and still use the auto-configured `MongoTemplate`. + +The same obstacle and the same features exist for other auto-configured Spring Data repository types (Elasticsearch, Redis, and others). +To work with them, change the names of the annotations and flags accordingly. + + + +[[howto.data-access.customize-spring-data-web-support]] +== Customize Spring Data's Web Support + +Spring Data provides web support that simplifies the use of Spring Data repositories in a web application. +Spring Boot provides properties in the `spring.data.web` namespace for customizing its configuration. +Note that if you are using Spring Data REST, you must use the properties in the `spring.data.rest` namespace instead. + + + +[[howto.data-access.exposing-spring-data-repositories-as-rest]] +== Expose Spring Data Repositories as REST Endpoint + +Spring Data REST can expose the `Repository` implementations as REST endpoints for you, +provided Spring MVC has been enabled for the application. + +Spring Boot exposes a set of useful properties (from the `spring.data.rest` namespace) that customize the {url-spring-data-rest-javadoc}/org/springframework/data/rest/core/config/RepositoryRestConfiguration.html[`RepositoryRestConfiguration`]. +If you need to provide additional customization, you should use a {url-spring-data-rest-javadoc}/org/springframework/data/rest/webmvc/config/RepositoryRestConfigurer.html[`RepositoryRestConfigurer`] bean. + +NOTE: If you do not specify any order on your custom `RepositoryRestConfigurer`, it runs after the one Spring Boot uses internally. +If you need to specify an order, make sure it is higher than 0. + + + +[[howto.data-access.configure-a-component-that-is-used-by-jpa]] +== Configure a Component that is Used by JPA + +If you want to configure a component that JPA uses, then you need to ensure that the component is initialized before JPA. +When the component is auto-configured, Spring Boot takes care of this for you. +For example, when Flyway is auto-configured, Hibernate is configured to depend on Flyway so that Flyway has a chance to initialize the database before Hibernate tries to use it. + +If you are configuring a component yourself, you can use an `EntityManagerFactoryDependsOnPostProcessor` subclass as a convenient way of setting up the necessary dependencies. +For example, if you use Hibernate Search with Elasticsearch as its index manager, any `EntityManagerFactory` beans must be configured to depend on the `elasticsearchClient` bean, as shown in the following example: + +include-code::ElasticsearchEntityManagerFactoryDependsOnPostProcessor[] + + + +[[howto.data-access.configure-jooq-with-multiple-datasources]] +== Configure jOOQ with Two DataSources + +If you need to use jOOQ with multiple data sources, you should create your own `DSLContext` for each one. +See {code-spring-boot-autoconfigure-src}/jooq/JooqAutoConfiguration.java[`JooqAutoConfiguration`] for more details. + +TIP: In particular, `JooqExceptionTranslator` and `SpringTransactionProvider` can be reused to provide similar features to what the auto-configuration does with a single `DataSource`. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/data-initialization.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/data-initialization.adoc similarity index 84% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/data-initialization.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/data-initialization.adoc index 12d0c0af1364..b5f5a72be81c 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/data-initialization.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/data-initialization.adoc @@ -1,14 +1,15 @@ [[howto.data-initialization]] -== Database Initialization += Database Initialization + An SQL database can be initialized in different ways depending on what your stack is. Of course, you can also do it manually, provided the database is a separate process. It is recommended to use a single mechanism for schema generation. - [[howto.data-initialization.using-hibernate]] -=== Initialize a Database Using Hibernate +== Initialize a Database Using Hibernate + You can set configprop:spring.jpa.hibernate.ddl-auto[] to control Hibernate's database initialization. Supported values are `none`, `validate`, `update`, `create`, and `create-drop`. Spring Boot chooses a default value for you based on whether you are using an embedded database. @@ -21,7 +22,7 @@ Be careful when switching from in-memory to a '`real`' database that you do not You either have to set `ddl-auto` explicitly or use one of the other mechanisms to initialize the database. NOTE: You can output the schema creation by enabling the `org.hibernate.SQL` logger. -This is done for you automatically if you enable the <>. +This is done for you automatically if you enable the xref:reference:features/logging.adoc#features.logging.console-output[debug mode]. In addition, a file named `import.sql` in the root of the classpath is executed on startup if Hibernate creates the schema from scratch (that is, if the `ddl-auto` property is set to `create` or `create-drop`). This can be useful for demos and for testing if you are careful but is probably not something you want to be on the classpath in production. @@ -30,12 +31,13 @@ It is a Hibernate feature (and has nothing to do with Spring). [[howto.data-initialization.using-basic-sql-scripts]] -=== Initialize a Database Using Basic SQL Scripts +== Initialize a Database Using Basic SQL Scripts + Spring Boot can automatically create the schema (DDL scripts) of your JDBC `DataSource` or R2DBC `ConnectionFactory` and initialize its data (DML scripts). By default, it loads schema scripts from `optional:classpath*:schema.sql` and data scripts from `optional:classpath*:data.sql`. -The locations of these schema and data scripts can customized using configprop:spring.sql.init.schema-locations[] and configprop:spring.sql.init.data-locations[] respectively. -The `optional:` prefix means that the application will start when the files do not exist. +The locations of these schema and data scripts can be customized using configprop:spring.sql.init.schema-locations[] and configprop:spring.sql.init.data-locations[] respectively. +The `optional:` prefix means that the application will start even when the files do not exist. To have the application fail to start when the files are absent, remove the `optional:` prefix. In addition, Spring Boot processes the `optional:classpath*:schema-$\{platform}.sql` and `optional:classpath*:data-$\{platform}.sql` files (if present), where `$\{platform}` is the value of configprop:spring.sql.init.platform[]. @@ -58,26 +60,27 @@ This will defer data source initialization until after any `EntityManagerFactory NOTE: The initialization scripts support `--` for single line comments and `/++*++ ++*++/` for block comments. Other comment formats are not supported. -If you are using a <>, like Flyway or Liquibase, you should use them alone to create and initialize the schema. +If you are using a xref:data-initialization.adoc#howto.data-initialization.migration-tool[higher-level database migration tool], like Flyway or Liquibase, you should use them alone to create and initialize the schema. Using the basic `schema.sql` and `data.sql` scripts alongside Flyway or Liquibase is not recommended and support will be removed in a future release. -If you need to initialize test data using a higher-level database migration tool, please see the sections about <> and <>. +If you need to initialize test data using a higher-level database migration tool, please see the sections about xref:data-initialization.adoc#howto.data-initialization.migration-tool.flyway-tests[Flyway] and xref:data-initialization.adoc#howto.data-initialization.migration-tool.liquibase-tests[Liquibase]. [[howto.data-initialization.batch]] -=== Initialize a Spring Batch Database +== Initialize a Spring Batch Database + If you use Spring Batch, it comes pre-packaged with SQL initialization scripts for most popular database platforms. Spring Boot can detect your database type and execute those scripts on startup. If you use an embedded database, this happens by default. You can also enable it for any database type, as shown in the following example: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - batch: - jdbc: - initialize-schema: "always" +spring: + batch: + jdbc: + initialize-schema: "always" ---- You can also switch off the initialization explicitly by setting `spring.batch.jdbc.initialize-schema` to `never`. @@ -85,13 +88,15 @@ You can also switch off the initialization explicitly by setting `spring.batch.j [[howto.data-initialization.migration-tool]] -=== Use a Higher-level Database Migration Tool +== Use a Higher-level Database Migration Tool + Spring Boot supports two higher-level migration tools: https://flywaydb.org/[Flyway] and https://www.liquibase.org/[Liquibase]. [[howto.data-initialization.migration-tool.flyway]] -==== Execute Flyway Database Migrations on Startup +=== Execute Flyway Database Migrations on Startup + To automatically run Flyway database migrations on startup, add the `org.flywaydb:flyway-core` to your classpath. Typically, migrations are scripts in the form `V__.sql` (with `` an underscore-separated version, such as '`1`' or '`2_1`'). @@ -99,34 +104,34 @@ By default, they are in a directory called `classpath:db/migration`, but you can This is a comma-separated list of one or more `classpath:` or `filesystem:` locations. For example, the following configuration would search for scripts in both the default classpath location and the `/opt/migration` directory: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - flyway: - locations: "classpath:db/migration,filesystem:/opt/migration" +spring: + flyway: + locations: "classpath:db/migration,filesystem:/opt/migration" ---- You can also add a special `\{vendor}` placeholder to use vendor-specific scripts. Assume the following: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - flyway: - locations: "classpath:db/migration/{vendor}" +spring: + flyway: + locations: "classpath:db/migration/{vendor}" ---- Rather than using `db/migration`, the preceding configuration sets the directory to use according to the type of the database (such as `db/migration/mysql` for MySQL). -The list of supported databases is available in {spring-boot-module-code}/jdbc/DatabaseDriver.java[`DatabaseDriver`]. +The list of supported databases is available in xref:api:java/org/springframework/boot/jdbc/DatabaseDriver.html[`DatabaseDriver`]. Migrations can also be written in Java. Flyway will be auto-configured with any beans that implement `JavaMigration`. -{spring-boot-autoconfigure-module-code}/flyway/FlywayProperties.java[`FlywayProperties`] provides most of Flyway's settings and a small set of additional properties that can be used to disable the migrations or switch off the location checking. +xref:api:java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.html[`FlywayProperties`] provides most of Flyway's settings and a small set of additional properties that can be used to disable the migrations or switch off the location checking. If you need more control over the configuration, consider registering a `FlywayConfigurationCustomizer` bean. Spring Boot calls `Flyway.migrate()` to perform the database migration. -If you would like more control, provide a `@Bean` that implements {spring-boot-autoconfigure-module-code}/flyway/FlywayMigrationStrategy.java[`FlywayMigrationStrategy`]. +If you would like more control, provide a `@Bean` that implements xref:api:java/org/springframework/boot/autoconfigure/flyway/FlywayMigrationStrategy.html[`FlywayMigrationStrategy`]. Flyway supports SQL and Java https://flywaydb.org/documentation/concepts/callbacks[callbacks]. To use SQL-based callbacks, place the callback scripts in the `classpath:db/migration` directory. @@ -147,11 +152,11 @@ For example, you can place test-specific migrations in `src/test/resources` and Also, you can use profile-specific configuration to customize `spring.flyway.locations` so that certain migrations run only when a particular profile is active. For example, in `application-dev.properties`, you might specify the following setting: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - flyway: - locations: "classpath:/db/migration,classpath:/dev/db/migration" +spring: + flyway: + locations: "classpath:/db/migration,classpath:/dev/db/migration" ---- With that setup, migrations in `dev/db/migration` run only when the `dev` profile is active. @@ -159,7 +164,8 @@ With that setup, migrations in `dev/db/migration` run only when the `dev` profil [[howto.data-initialization.migration-tool.liquibase]] -==== Execute Liquibase Database Migrations on Startup +=== Execute Liquibase Database Migrations on Startup + To automatically run Liquibase database migrations on startup, add the `org.liquibase:liquibase-core` to your classpath. [NOTE] @@ -179,12 +185,15 @@ Alternatively, you can use Liquibase's native `DataSource` by setting `spring.li Setting either `spring.liquibase.url` or `spring.liquibase.user` is sufficient to cause Liquibase to use its own `DataSource`. If any of the three properties has not been set, the value of its equivalent `spring.datasource` property will be used. -See {spring-boot-autoconfigure-module-code}/liquibase/LiquibaseProperties.java[`LiquibaseProperties`] for details about available settings such as contexts, the default schema, and others. +See xref:api:java/org/springframework/boot/autoconfigure/liquibase/LiquibaseProperties.html[`LiquibaseProperties`] for details about available settings such as contexts, the default schema, and others. + +You can also use a `Customizer` bean if you want to customize the `Liquibase` instance before it is being used. [[howto.data-initialization.migration-tool.flyway-tests]] -==== Use Flyway for test-only migrations +=== Use Flyway for Test-only Migrations + If you want to create Flyway migrations which populate your test database, place them in `src/test/resources/db/migration`. A file named, for example, `src/test/resources/db/migration/V9999__test-data.sql` will be executed after your production migrations and only if you're running the tests. You can use this file to create the needed test data. @@ -193,14 +202,15 @@ This file will not be packaged in your uber jar or your container. [[howto.data-initialization.migration-tool.liquibase-tests]] -==== Use Liquibase for test-only migrations +=== Use Liquibase for Test-only Migrations + If you want to create Liquibase migrations which populate your test database, you have to create a test changelog which also includes the production changelog. First, you need to configure Liquibase to use a different changelog when running the tests. One way to do this is to create a Spring Boot `test` profile and put the Liquibase properties in there. For that, create a file named `src/test/resources/application-test.properties` and put the following property in there: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- spring: liquibase: @@ -211,7 +221,7 @@ This configures Liquibase to use a different changelog when running in the `test Now create the changelog file at `src/test/resources/db/changelog/db.changelog-test.yaml`: -[source,yaml,indent=0,subs="verbatim"] +[source,yaml] ---- databaseChangeLog: - include: @@ -233,7 +243,8 @@ To do this, you can add the `@ActiveProfiles("test")` annotation to your `@Sprin [[howto.data-initialization.dependencies]] -=== Depend Upon an Initialized Database +== Depend Upon an Initialized Database + Database initialization is performed while the application is starting up as part of application context refresh. To allow an initialized database to be accessed during startup, beans that act as database initializers and beans that require that database to have been initialized are detected automatically. Beans whose initialization depends upon the database having been initialized are configured to depend upon those that initialize it. @@ -242,7 +253,8 @@ If, during startup, your application tries to access the database and it has not [[howto.data-initialization.dependencies.initializer-detection]] -==== Detect a Database Initializer +=== Detect a Database Initializer + Spring Boot will automatically detect beans of the following types that initialize an SQL database: - `DataSourceScriptDatabaseInitializer` @@ -258,12 +270,14 @@ To have other beans be detected, register an implementation of `DatabaseInitiali [[howto.data-initialization.dependencies.depends-on-initialization-detection]] -==== Detect a Bean That Depends On Database Initialization +=== Detect a Bean That Depends On Database Initialization + Spring Boot will automatically detect beans of the following types that depends upon database initialization: - `AbstractEntityManagerFactoryBean` (unless configprop:spring.jpa.defer-datasource-initialization[] is set to `true`) - `DSLContext` (jOOQ) - `EntityManagerFactory` (unless configprop:spring.jpa.defer-datasource-initialization[] is set to `true`) +- `JdbcClient` - `JdbcOperations` - `NamedParameterJdbcOperations` diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/deployment/cloud.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/deployment/cloud.adoc new file mode 100644 index 000000000000..cc4247c115f8 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/deployment/cloud.adoc @@ -0,0 +1,424 @@ +[[howto.deployment.cloud]] += Deploying to the Cloud + +Spring Boot's executable jars are ready-made for most popular cloud PaaS (Platform-as-a-Service) providers. +These providers tend to require that you "`bring your own container`". +They manage application processes (not Java applications specifically), so they need an intermediary layer that adapts _your_ application to the _cloud's_ notion of a running process. + +Two popular cloud providers, Heroku and Cloud Foundry, employ a "`buildpack`" approach. +The buildpack wraps your deployed code in whatever is needed to _start_ your application. +It might be a JDK and a call to `java`, an embedded web server, or a full-fledged application server. +A buildpack is pluggable, but ideally you should be able to get by with as few customizations to it as possible. +This reduces the footprint of functionality that is not under your control. +It minimizes divergence between development and production environments. + +Ideally, your application, like a Spring Boot executable jar, has everything that it needs to run packaged within it. + +In this section, we look at what it takes to get the xref:tutorial:first-application/index.adoc[application that we developed] in the "`Getting Started`" section up and running in the Cloud. + + + +[[howto.deployment.cloud.cloud-foundry]] +== Cloud Foundry + +Cloud Foundry provides default buildpacks that come into play if no other buildpack is specified. +The Cloud Foundry https://github.com/cloudfoundry/java-buildpack[Java buildpack] has excellent support for Spring applications, including Spring Boot. +You can deploy stand-alone executable jar applications as well as traditional `.war` packaged applications. + +Once you have built your application (by using, for example, `mvn clean package`) and have https://docs.cloudfoundry.org/cf-cli/install-go-cli.html[installed the `cf` command line tool], deploy your application by using the `cf push` command, substituting the path to your compiled `.jar`. +Be sure to have https://docs.cloudfoundry.org/cf-cli/getting-started.html#login[logged in with your `cf` command line client] before pushing an application. +The following line shows using the `cf push` command to deploy an application: + +[source,shell] +---- +$ cf push acloudyspringtime -p target/demo-0.0.1-SNAPSHOT.jar +---- + +NOTE: In the preceding example, we substitute `acloudyspringtime` for whatever value you give `cf` as the name of your application. + +See the https://docs.cloudfoundry.org/cf-cli/getting-started.html#push[`cf push` documentation] for more options. +If there is a Cloud Foundry https://docs.cloudfoundry.org/devguide/deploy-apps/manifest.html[`manifest.yml`] file present in the same directory, it is considered. + +At this point, `cf` starts uploading your application, producing output similar to the following example: + +[source,subs="verbatim,quotes"] +---- +Uploading acloudyspringtime... *OK* +Preparing to start acloudyspringtime... *OK* +-----> Downloaded app package (*8.9M*) +-----> Java Buildpack Version: v3.12 (offline) | https://github.com/cloudfoundry/java-buildpack.git#6f25b7e +-----> Downloading Open Jdk JRE + Expanding Open Jdk JRE to .java-buildpack/open_jdk_jre (1.6s) +-----> Downloading Open JDK Like Memory Calculator 2.0.2_RELEASE from https://java-buildpack.cloudfoundry.org/memory-calculator/trusty/x86_64/memory-calculator-2.0.2_RELEASE.tar.gz (found in cache) + Memory Settings: -Xss349K -Xmx681574K -XX:MaxMetaspaceSize=104857K -Xms681574K -XX:MetaspaceSize=104857K +-----> Downloading Container Certificate Trust Store 1.0.0_RELEASE from https://java-buildpack.cloudfoundry.org/container-certificate-trust-store/container-certificate-trust-store-1.0.0_RELEASE.jar (found in cache) + Adding certificates to .java-buildpack/container_certificate_trust_store/truststore.jks (0.6s) +-----> Downloading Spring Auto Reconfiguration 1.10.0_RELEASE from https://java-buildpack.cloudfoundry.org/auto-reconfiguration/auto-reconfiguration-1.10.0_RELEASE.jar (found in cache) +Checking status of app 'acloudyspringtime'... + 0 of 1 instances running (1 starting) + ... + 0 of 1 instances running (1 starting) + ... + 0 of 1 instances running (1 starting) + ... + 1 of 1 instances running (1 running) + +App started +---- + +Congratulations! The application is now live! + +Once your application is live, you can verify the status of the deployed application by using the `cf apps` command, as shown in the following example: + +[source,shell] +---- +$ cf apps +Getting applications in ... +OK + +name requested state instances memory disk urls +... +acloudyspringtime started 1/1 512M 1G acloudyspringtime.cfapps.io +... +---- + +Once Cloud Foundry acknowledges that your application has been deployed, you should be able to find the application at the URI given. +In the preceding example, you could find it at `\https://acloudyspringtime.cfapps.io/`. + + + +[[howto.deployment.cloud.cloud-foundry.binding-to-services]] +=== Binding to Services + +By default, metadata about the running application as well as service connection information is exposed to the application as environment variables (for example: `$VCAP_SERVICES`). +This architecture decision is due to Cloud Foundry's polyglot (any language and platform can be supported as a buildpack) nature. +Process-scoped environment variables are language agnostic. + +Environment variables do not always make for the easiest API, so Spring Boot automatically extracts them and flattens the data into properties that can be accessed through Spring's `Environment` abstraction, as shown in the following example: + +include-code::MyBean[] + +All Cloud Foundry properties are prefixed with `vcap`. +You can use `vcap` properties to access application information (such as the public URL of the application) and service information (such as database credentials). +See the xref:api:java/org/springframework/boot/cloud/CloudFoundryVcapEnvironmentPostProcessor.html[`CloudFoundryVcapEnvironmentPostProcessor`] API documentation for complete details. + +TIP: The https://github.com/pivotal-cf/java-cfenv/[Java CFEnv] project is a better fit for tasks such as configuring a DataSource. + + + +[[howto.deployment.cloud.kubernetes]] +== Kubernetes + +Spring Boot auto-detects Kubernetes deployment environments by checking the environment for `"*_SERVICE_HOST"` and `"*_SERVICE_PORT"` variables. +You can override this detection with the configprop:spring.main.cloud-platform[] configuration property. + +Spring Boot helps you to xref:reference:features/spring-application.adoc#features.spring-application.application-availability[manage the state of your application] and export it with xref:reference:actuator/endpoints.adoc#actuator.endpoints.kubernetes-probes[HTTP Kubernetes Probes using Actuator]. + + + +[[howto.deployment.cloud.kubernetes.container-lifecycle]] +=== Kubernetes Container Lifecycle + +When Kubernetes deletes an application instance, the shutdown process involves several subsystems concurrently: shutdown hooks, unregistering the service, removing the instance from the load-balancer... +Because this shutdown processing happens in parallel (and due to the nature of distributed systems), there is a window during which traffic can be routed to a pod that has also begun its shutdown processing. + +You can configure a sleep execution in a preStop handler to avoid requests being routed to a pod that has already begun shutting down. +This sleep should be long enough for new requests to stop being routed to the pod and its duration will vary from deployment to deployment. +The preStop handler can be configured by using the PodSpec in the pod's configuration file as follows: + +[source,yaml] +---- +spec: + containers: + - name: "example-container" + image: "example-image" + lifecycle: + preStop: + exec: + command: ["sh", "-c", "sleep 10"] +---- + +Once the pre-stop hook has completed, SIGTERM will be sent to the container and xref:reference:web/graceful-shutdown.adoc[graceful shutdown] will begin, allowing any remaining in-flight requests to complete. + +NOTE: When Kubernetes sends a SIGTERM signal to the pod, it waits for a specified time called the termination grace period (the default for which is 30 seconds). +If the containers are still running after the grace period, they are sent the SIGKILL signal and forcibly removed. +If the pod takes longer than 30 seconds to shut down, which could be because you have increased configprop:spring.lifecycle.timeout-per-shutdown-phase[], make sure to increase the termination grace period by setting the `terminationGracePeriodSeconds` option in the Pod YAML. + + + +[[howto.deployment.cloud.heroku]] +== Heroku + +Heroku is another popular PaaS platform. +To customize Heroku builds, you provide a `Procfile`, which provides the incantation required to deploy an application. +Heroku assigns a `port` for the Java application to use and then ensures that routing to the external URI works. + +You must configure your application to listen on the correct port. +The following example shows the `Procfile` for our starter REST application: + +[source] +---- +web: java -Dserver.port=$PORT -jar target/demo-0.0.1-SNAPSHOT.jar +---- + +Spring Boot makes `-D` arguments available as properties accessible from a Spring `Environment` instance. +The `server.port` configuration property is fed to the embedded Tomcat, Jetty, or Undertow instance, which then uses the port when it starts up. +The `$PORT` environment variable is assigned to us by the Heroku PaaS. + +This should be everything you need. +The most common deployment workflow for Heroku deployments is to `git push` the code to production, as shown in the following example: + +[source,shell] +---- +$ git push heroku main +---- + +Which will result in the following: + +[source,subs="verbatim,quotes"] +---- +Initializing repository, *done*. +Counting objects: 95, *done*. +Delta compression using up to 8 threads. +Compressing objects: 100% (78/78), *done*. +Writing objects: 100% (95/95), 8.66 MiB | 606.00 KiB/s, *done*. +Total 95 (delta 31), reused 0 (delta 0) + +-----> Java app detected +-----> Installing OpenJDK... *done* +-----> Installing Maven... *done* +-----> Installing settings.xml... *done* +-----> Executing: mvn -B -DskipTests=true clean install + + [INFO] Scanning for projects... + Downloading: https://repo.spring.io/... + Downloaded: https://repo.spring.io/... (818 B at 1.8 KB/sec) + .... + Downloaded: https://s3pository.heroku.com/jvm/... (152 KB at 595.3 KB/sec) + [INFO] Installing /tmp/build_0c35a5d2-a067-4abc-a232-14b1fb7a8229/target/... + [INFO] Installing /tmp/build_0c35a5d2-a067-4abc-a232-14b1fb7a8229/pom.xml ... + [INFO] ------------------------------------------------------------------------ + [INFO] *BUILD SUCCESS* + [INFO] ------------------------------------------------------------------------ + [INFO] Total time: 59.358s + [INFO] Finished at: Fri Mar 07 07:28:25 UTC 2014 + [INFO] Final Memory: 20M/493M + [INFO] ------------------------------------------------------------------------ + +-----> Discovering process types + Procfile declares types -> *web* + +-----> Compressing... *done*, 70.4MB +-----> Launching... *done*, v6 + https://agile-sierra-1405.herokuapp.com/ *deployed to Heroku* + +To git@heroku.com:agile-sierra-1405.git + * [new branch] main -> main +---- + +Your application should now be up and running on Heroku. +For more details, see https://devcenter.heroku.com/articles/deploying-spring-boot-apps-to-heroku[Deploying Spring Boot Applications to Heroku]. + + + +[[howto.deployment.cloud.openshift]] +== OpenShift + +https://www.openshift.com/[OpenShift] has many resources describing how to deploy Spring Boot applications, including: + +* https://blog.openshift.com/using-openshift-enterprise-grade-spring-boot-deployments/[Using the S2I builder] +* https://access.redhat.com/documentation/en-us/reference_architectures/2017/html-single/spring_boot_microservices_on_red_hat_openshift_container_platform_3/[Architecture guide] +* https://blog.openshift.com/using-spring-boot-on-openshift/[Running as a traditional web application on Wildfly] +* https://blog.openshift.com/openshift-commons-briefing-96-cloud-native-applications-spring-rhoar/[OpenShift Commons Briefing] + + + +[[howto.deployment.cloud.aws]] +== Amazon Web Services (AWS) + +Amazon Web Services offers multiple ways to install Spring Boot-based applications, either as traditional web applications (war) or as executable jar files with an embedded web server. +The options include: + +* AWS Elastic Beanstalk +* AWS Code Deploy +* AWS OPS Works +* AWS Cloud Formation +* AWS Container Registry + +Each has different features and pricing models. +In this document, we describe to approach using AWS Elastic Beanstalk. + + + +[[howto.deployment.cloud.aws.beanstalk]] +=== AWS Elastic Beanstalk + +As described in the official https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_Java.html[Elastic Beanstalk Java guide], there are two main options to deploy a Java application. +You can either use the "`Tomcat Platform`" or the "`Java SE platform`". + + + +[[howto.deployment.cloud.aws.beanstalk.tomcat-platform]] +==== Using the Tomcat Platform + +This option applies to Spring Boot projects that produce a war file. +No special configuration is required. +You need only follow the official guide. + + + +[[howto.deployment.cloud.aws.beanstalk.java-se-platform]] +==== Using the Java SE Platform + +This option applies to Spring Boot projects that produce a jar file and run an embedded web container. +Elastic Beanstalk environments run an nginx instance on port 80 to proxy the actual application, running on port 5000. +To configure it, add the following line to your `application.properties` file: + +[configprops,yaml] +---- +server: + port: 5000 +---- + + +[TIP] +.Upload binaries instead of sources +==== +By default, Elastic Beanstalk uploads sources and compiles them in AWS. +However, it is best to upload the binaries instead. +To do so, add lines similar to the following to your `.elasticbeanstalk/config.yml` file: + +[source,xml] +---- +deploy: + artifact: target/demo-0.0.1-SNAPSHOT.jar +---- +==== + +[TIP] +.Reduce costs by setting the environment type +==== +By default an Elastic Beanstalk environment is load balanced. +The load balancer has a significant cost. +To avoid that cost, set the environment type to "`Single instance`", as described in https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/environments-create-wizard.html#environments-create-wizard-capacity[the Amazon documentation]. +You can also create single instance environments by using the CLI and the following command: + +[source] +---- +eb create -s +---- +==== + + + +[[howto.deployment.cloud.aws.summary]] +=== Summary + +This is one of the easiest ways to get to AWS, but there are more things to cover, such as how to integrate Elastic Beanstalk into any CI / CD tool, use the Elastic Beanstalk Maven plugin instead of the CLI, and others. +There is a https://exampledriven.wordpress.com/2017/01/09/spring-boot-aws-elastic-beanstalk-example/[blog post] covering these topics more in detail. + + + +[[howto.deployment.cloud.boxfuse]] +== CloudCaptain and Amazon Web Services + +https://cloudcaptain.sh/[CloudCaptain] works by turning your Spring Boot executable jar or war into a minimal VM image that can be deployed unchanged either on VirtualBox or on AWS. +CloudCaptain comes with deep integration for Spring Boot and uses the information from your Spring Boot configuration file to automatically configure ports and health check URLs. +CloudCaptain leverages this information both for the images it produces as well as for all the resources it provisions (instances, security groups, elastic load balancers, and so on). + +Once you have created a https://console.cloudcaptain.sh[CloudCaptain account], connected it to your AWS account, installed the latest version of the CloudCaptain Client, and ensured that the application has been built by Maven or Gradle (by using, for example, `mvn clean package`), you can deploy your Spring Boot application to AWS with a command similar to the following: + +[source,shell] +---- +$ boxfuse run myapp-1.0.jar -env=prod +---- + +See the https://cloudcaptain.sh/docs/commandline/run.html[`boxfuse run` documentation] for more options. +If there is a https://cloudcaptain.sh/docs/commandline/#configuration[`boxfuse.conf`] file present in the current directory, it is considered. + +TIP: By default, CloudCaptain activates a Spring profile named `boxfuse` on startup. +If your executable jar or war contains an https://cloudcaptain.sh/docs/payloads/springboot.html#configuration[`application-boxfuse.properties`] file, CloudCaptain bases its configuration on the properties it contains. + +At this point, CloudCaptain creates an image for your application, uploads it, and configures and starts the necessary resources on AWS, resulting in output similar to the following example: + +[source] +---- +Fusing Image for myapp-1.0.jar ... +Image fused in 00:06.838s (53937 K) -> axelfontaine/myapp:1.0 +Creating axelfontaine/myapp ... +Pushing axelfontaine/myapp:1.0 ... +Verifying axelfontaine/myapp:1.0 ... +Creating Elastic IP ... +Mapping myapp-axelfontaine.boxfuse.io to 52.28.233.167 ... +Waiting for AWS to create an AMI for axelfontaine/myapp:1.0 in eu-central-1 (this may take up to 50 seconds) ... +AMI created in 00:23.557s -> ami-d23f38cf +Creating security group boxfuse-sg_axelfontaine/myapp:1.0 ... +Launching t2.micro instance of axelfontaine/myapp:1.0 (ami-d23f38cf) in eu-central-1 ... +Instance launched in 00:30.306s -> i-92ef9f53 +Waiting for AWS to boot Instance i-92ef9f53 and Payload to start at https://52.28.235.61/ ... +Payload started in 00:29.266s -> https://52.28.235.61/ +Remapping Elastic IP 52.28.233.167 to i-92ef9f53 ... +Waiting 15s for AWS to complete Elastic IP Zero Downtime transition ... +Deployment completed successfully. axelfontaine/myapp:1.0 is up and running at https://myapp-axelfontaine.boxfuse.io/ +---- + +Your application should now be up and running on AWS. + +See the blog post on https://cloudcaptain.sh/blog/spring-boot-ec2.html[deploying Spring Boot apps on EC2] as well as the https://cloudcaptain.sh/docs/payloads/springboot.html[documentation for the CloudCaptain Spring Boot integration] to get started with a Maven build to run the app. + + + +[[howto.deployment.cloud.azure]] +== Azure + +This https://spring.io/guides/gs/spring-boot-for-azure/[Getting Started guide] walks you through deploying your Spring Boot application to either https://azure.microsoft.com/en-us/services/spring-cloud/[Azure Spring Cloud] or https://docs.microsoft.com/en-us/azure/app-service/overview[Azure App Service]. + + + +[[howto.deployment.cloud.google]] +== Google Cloud + +Google Cloud has several options that can be used to launch Spring Boot applications. +The easiest to get started with is probably App Engine, but you could also find ways to run Spring Boot in a container with Container Engine or on a virtual machine with Compute Engine. + +To deploy your first app to App Engine standard environment, follow https://codelabs.developers.google.com/codelabs/cloud-app-engine-springboot#0[this tutorial]. + +Alternatively, App Engine Flex requires you to create an `app.yaml` file to describe the resources your app requires. +Normally, you put this file in `src/main/appengine`, and it should resemble the following file: + +[source,yaml] +---- +service: "default" + +runtime: "java17" +env: "flex" + +handlers: +- url: "/.*" + script: "this field is required, but ignored" + +manual_scaling: + instances: 1 + +health_check: + enable_health_check: false + +env_variables: + ENCRYPT_KEY: "your_encryption_key_here" +---- + +You can deploy the app (for example, with a Maven plugin) by adding the project ID to the build configuration, as shown in the following example: + +[source,xml] +---- + + com.google.cloud.tools + appengine-maven-plugin + 2.4.4 + + myproject + + +---- + +Then deploy with `mvn appengine:deploy` (you need to authenticate first, otherwise the build fails). diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/deployment/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/deployment/index.adoc new file mode 100644 index 000000000000..cb8b88a7b6e6 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/deployment/index.adoc @@ -0,0 +1,8 @@ +[[howto.deployment]] += Deploying Spring Boot Applications + +Spring Boot's flexible packaging options provide a great deal of choice when it comes to deploying your application. +You can deploy Spring Boot applications to a variety of cloud platforms, to virtual/real machines, or make them fully executable for Unix systems. + +This section covers some of the more common deployment scenarios. + diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/deployment/installing.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/deployment/installing.adoc new file mode 100644 index 000000000000..92bcf1b3a79d --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/deployment/installing.adoc @@ -0,0 +1,389 @@ +[[howto.deployment.installing]] += Installing Spring Boot Applications + +In addition to running Spring Boot applications by using `java -jar` directly, it is also possible to run them as `systemd`, `init.d` or Windows services. + + + +[[howto.deployment.installing.system-d]] +== Installation as a systemd Service + +`systemd` is the successor of the System V init system and is now being used by many modern Linux distributions. +Spring Boot applications can be launched by using `systemd` '`service`' scripts. + +Assuming that you have a Spring Boot application packaged as an uber jar in `/var/myapp`, to install it as a `systemd` service, create a script named `myapp.service` and place it in `/etc/systemd/system` directory. +The following script offers an example: + +[source] +---- +[Unit] +Description=myapp +After=syslog.target network.target + +[Service] +User=myapp +Group=myapp + +Environment="JAVA_HOME=/path/to/java/home" + +ExecStart=${JAVA_HOME}/bin/java -jar /var/myapp/myapp.jar +ExecStop=/bin/kill -15 $MAINPID +SuccessExitStatus=143 + +[Install] +WantedBy=multi-user.target +---- + +IMPORTANT: Remember to change the `Description`, `User`, `Group`, `Environment` and `ExecStart` fields for your application. + +NOTE: The `ExecStart` field does not declare the script action command, which means that the `run` command is used by default. + +The user that runs the application, the PID file, and the console log file are managed by `systemd` itself and therefore must be configured by using appropriate fields in the '`service`' script. +Consult the https://www.freedesktop.org/software/systemd/man/systemd.service.html[service unit configuration man page] for more details. + +To flag the application to start automatically on system boot, use the following command: + +[source,shell] +---- +$ systemctl enable myapp.service +---- + +Run `man systemctl` for more details. + + + +[[howto.deployment.installing.init-d]] +== Installation as an init.d Service (System V) + +To use your application as `init.d` service, configure its build to produce a xref:deployment/installing.adoc[fully executable jar]. + +CAUTION: Fully executable jars work by embedding an extra script at the front of the file. +Currently, some tools do not accept this format, so you may not always be able to use this technique. +For example, `jar -xf` may silently fail to extract a jar or war that has been made fully executable. +It is recommended that you make your jar or war fully executable only if you intend to execute it directly, rather than running it with `java -jar` or deploying it to a servlet container. + +CAUTION: A zip64-format jar file cannot be made fully executable. +Attempting to do so will result in a jar file that is reported as corrupt when executed directly or with `java -jar`. +A standard-format jar file that contains one or more zip64-format nested jars can be fully executable. + +To create a '`fully executable`' jar with Maven, use the following plugin configuration: + +[source,xml] +---- + + org.springframework.boot + spring-boot-maven-plugin + + true + + +---- + +The following example shows the equivalent Gradle configuration: + +[source,gradle] +---- +tasks.named('bootJar') { + launchScript() +} +---- + +It can then be symlinked to `init.d` to support the standard `start`, `stop`, `restart`, and `status` commands. + +The default launch script that is added to a fully executable jar supports most Linux distributions and is tested on CentOS and Ubuntu. +Other platforms, such as OS X and FreeBSD, require the use of a custom script. +The default scripts supports the following features: + +* Starts the services as the user that owns the jar file +* Tracks the application's PID by using `/var/run//.pid` +* Writes console logs to `/var/log/.log` + +Assuming that you have a Spring Boot application installed in `/var/myapp`, to install a Spring Boot application as an `init.d` service, create a symlink, as follows: + +[source,shell] +---- +$ sudo ln -s /var/myapp/myapp.jar /etc/init.d/myapp +---- + +Once installed, you can start and stop the service in the usual way. +For example, on a Debian-based system, you could start it with the following command: + +[source,shell] +---- +$ service myapp start +---- + +TIP: If your application fails to start, check the log file written to `/var/log/.log` for errors. + +You can also flag the application to start automatically by using your standard operating system tools. +For example, on Debian, you could use the following command: + +[source,shell] +---- +$ update-rc.d myapp defaults +---- + + + +[[howto.deployment.installing.init-d.securing]] +=== Securing an init.d Service + +NOTE: The following is a set of guidelines on how to secure a Spring Boot application that runs as an init.d service. +It is not intended to be an exhaustive list of everything that should be done to harden an application and the environment in which it runs. + +When executed as root, as is the case when root is being used to start an init.d service, the default executable script runs the application as the user specified in the `RUN_AS_USER` environment variable. +When the environment variable is not set, the user who owns the jar file is used instead. +You should never run a Spring Boot application as `root`, so `RUN_AS_USER` should never be root and your application's jar file should never be owned by root. +Instead, create a specific user to run your application and set the `RUN_AS_USER` environment variable or use `chown` to make it the owner of the jar file, as shown in the following example: + +[source,shell] +---- +$ chown bootapp:bootapp your-app.jar +---- + +In this case, the default executable script runs the application as the `bootapp` user. + +TIP: To reduce the chances of the application's user account being compromised, you should consider preventing it from using a login shell. +For example, you can set the account's shell to `/usr/sbin/nologin`. + +You should also take steps to prevent the modification of your application's jar file. +Firstly, configure its permissions so that it cannot be written and can only be read or executed by its owner, as shown in the following example: + +[source,shell] +---- +$ chmod 500 your-app.jar +---- + +Second, you should also take steps to limit the damage if your application or the account that is running it is compromised. +If an attacker does gain access, they could make the jar file writable and change its contents. +One way to protect against this is to make it immutable by using `chattr`, as shown in the following example: + +[source,shell] +---- +$ sudo chattr +i your-app.jar +---- + +This will prevent any user, including root, from modifying the jar. + +If root is used to control the application's service and you xref:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization.when-running.conf-file[use a `.conf` file] to customize its startup, the `.conf` file is read and evaluated by the root user. +It should be secured accordingly. +Use `chmod` so that the file can only be read by the owner and use `chown` to make root the owner, as shown in the following example: + +[source,shell] +---- +$ chmod 400 your-app.conf +$ sudo chown root:root your-app.conf +---- + + + +[[howto.deployment.installing.init-d.script-customization]] +=== Customizing the Startup Script + +The default embedded startup script written by the Maven or Gradle plugin can be customized in a number of ways. +For most people, using the default script along with a few customizations is usually enough. +If you find you cannot customize something that you need to, use the `embeddedLaunchScript` option to write your own file entirely. + + + +[[howto.deployment.installing.init-d.script-customization.when-written]] +==== Customizing the Start Script When It Is Written + +It often makes sense to customize elements of the start script as it is written into the jar file. +For example, init.d scripts can provide a "`description`". +Since you know the description up front (and it need not change), you may as well provide it when the jar is generated. + +To customize written elements, use the `embeddedLaunchScriptProperties` option of the Spring Boot Maven plugin or the xref:gradle-plugin:packaging.adoc#packaging-executable.configuring.launch-script[`properties` property of the Spring Boot Gradle plugin's `launchScript`]. + +The following property substitutions are supported with the default script: + +[cols="1,3,3,3"] +|=== +| Name | Description | Gradle default | Maven default + +| `mode` +| The script mode. +| `auto` +| `auto` + +| `initInfoProvides` +| The `Provides` section of "`INIT INFO`" +| `${task.baseName}` +| `${project.artifactId}` + +| `initInfoRequiredStart` +| `Required-Start` section of "`INIT INFO`". +| `$remote_fs $syslog $network` +| `$remote_fs $syslog $network` + +| `initInfoRequiredStop` +| `Required-Stop` section of "`INIT INFO`". +| `$remote_fs $syslog $network` +| `$remote_fs $syslog $network` + +| `initInfoDefaultStart` +| `Default-Start` section of "`INIT INFO`". +| `2 3 4 5` +| `2 3 4 5` + +| `initInfoDefaultStop` +| `Default-Stop` section of "`INIT INFO`". +| `0 1 6` +| `0 1 6` + +| `initInfoShortDescription` +| `Short-Description` section of "`INIT INFO`". +| Single-line version of `${project.description}` (falling back to `${task.baseName}`) +| `${project.name}` + +| `initInfoDescription` +| `Description` section of "`INIT INFO`". +| `${project.description}` (falling back to `${task.baseName}`) +| `${project.description}` (falling back to `${project.name}`) + +| `initInfoChkconfig` +| `chkconfig` section of "`INIT INFO`" +| `2345 99 01` +| `2345 99 01` + +| `confFolder` +| The default value for `CONF_FOLDER` +| Folder containing the jar +| Folder containing the jar + +| `inlinedConfScript` +| Reference to a file script that should be inlined in the default launch script. + This can be used to set environmental variables such as `JAVA_OPTS` before any external config files are loaded +| +| + +| `logFolder` +| Default value for `LOG_FOLDER`. + Only valid for an `init.d` service +| +| + +| `logFilename` +| Default value for `LOG_FILENAME`. + Only valid for an `init.d` service +| +| + +| `pidFolder` +| Default value for `PID_FOLDER`. + Only valid for an `init.d` service +| +| + +| `pidFilename` +| Default value for the name of the PID file in `PID_FOLDER`. + Only valid for an `init.d` service +| +| + +| `useStartStopDaemon` +| Whether the `start-stop-daemon` command, when it is available, should be used to control the process +| `true` +| `true` + +| `stopWaitTime` +| Default value for `STOP_WAIT_TIME` in seconds. + Only valid for an `init.d` service +| 60 +| 60 +|=== + + + +[[howto.deployment.installing.init-d.script-customization.when-running]] +==== Customizing a Script When It Runs + +For items of the script that need to be customized _after_ the jar has been written, you can use environment variables or a xref:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization.when-running.conf-file[config file]. + +The following environment properties are supported with the default script: + +[cols="1,6"] +|=== +| Variable | Description + +| `MODE` +| The "`mode`" of operation. + The default depends on the way the jar was built but is usually `auto` (meaning it tries to guess if it is an init script by checking if it is a symlink in a directory called `init.d`). + You can explicitly set it to `service` so that the `stop\|start\|status\|restart` commands work or to `run` if you want to run the script in the foreground. + +| `RUN_AS_USER` +| The user that will be used to run the application. + When not set, the user that owns the jar file will be used. + +| `USE_START_STOP_DAEMON` +| Whether the `start-stop-daemon` command, when it is available, should be used to control the process. + Defaults to `true`. + +| `PID_FOLDER` +| The root name of the pid folder (`/var/run` by default). + +| `LOG_FOLDER` +| The name of the folder in which to put log files (`/var/log` by default). + +| `CONF_FOLDER` +| The name of the folder from which to read .conf files (same folder as jar-file by default). + +| `LOG_FILENAME` +| The name of the log file in the `LOG_FOLDER` (`.log` by default). + +| `APP_NAME` +| The name of the app. + If the jar is run from a symlink, the script guesses the app name. + If it is not a symlink or you want to explicitly set the app name, this can be useful. + +| `RUN_ARGS` +| The arguments to pass to the program (the Spring Boot app). + +| `JAVA_HOME` +| The location of the `java` executable is discovered by using the `PATH` by default, but you can set it explicitly if there is an executable file at `$JAVA_HOME/bin/java`. + +| `JAVA_OPTS` +| Options that are passed to the JVM when it is launched. + +| `JARFILE` +| The explicit location of the jar file, in case the script is being used to launch a jar that it is not actually embedded. + +| `DEBUG` +| If not empty, sets the `-x` flag on the shell process, allowing you to see the logic in the script. + +| `STOP_WAIT_TIME` +| The time in seconds to wait when stopping the application before forcing a shutdown (`60` by default). +|=== + +NOTE: The `PID_FOLDER`, `LOG_FOLDER`, and `LOG_FILENAME` variables are only valid for an `init.d` service. +For `systemd`, the equivalent customizations are made by using the '`service`' script. +See the https://www.freedesktop.org/software/systemd/man/systemd.service.html[service unit configuration man page] for more details. + + + +[[howto.deployment.installing.init-d.script-customization.when-running.conf-file]] +===== Using a Conf File + +With the exception of `JARFILE` and `APP_NAME`, the settings listed in the preceding section can be configured by using a `.conf` file. +The file is expected to be next to the jar file and have the same name but suffixed with `.conf` rather than `.jar`. +For example, a jar named `/var/myapp/myapp.jar` uses the configuration file named `/var/myapp/myapp.conf`, as shown in the following example: + +.myapp.conf +[source,properties] +---- +JAVA_OPTS=-Xmx1024M +LOG_FOLDER=/custom/log/folder +---- + +TIP: If you do not like having the config file next to the jar file, you can set a `CONF_FOLDER` environment variable to customize the location of the config file. + +To learn about securing this file appropriately, see xref:deployment/installing.adoc#howto.deployment.installing.init-d.securing[the guidelines for securing an init.d service]. + + + +[[howto.deployment.installing.windows-services]] +== Microsoft Windows Services + +A Spring Boot application can be started as a Windows service by using https://github.com/kohsuke/winsw[`winsw`]. + +A (https://github.com/snicoll/spring-boot-daemon[separately maintained sample]) describes step-by-step how you can create a Windows service for your Spring Boot application. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/traditional-deployment.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/deployment/traditional-deployment.adoc similarity index 78% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/traditional-deployment.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/deployment/traditional-deployment.adoc index a9a29e7c59ac..f7b4c2e651e2 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/traditional-deployment.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/deployment/traditional-deployment.adoc @@ -1,12 +1,13 @@ [[howto.traditional-deployment]] -== Traditional Deployment += Traditional Deployment + Spring Boot supports traditional deployment as well as more modern forms of deployment. This section answers common questions about traditional deployment. [[howto.traditional-deployment.war]] -=== Create a Deployable War File +== Create a Deployable War File WARNING: Because Spring WebFlux does not strictly depend on the servlet API and applications are deployed by default on an embedded Reactor Netty server, War deployment is not supported for WebFlux applications. @@ -14,21 +15,21 @@ The first step in producing a deployable war file is to provide a `SpringBootSer Doing so makes use of Spring Framework's servlet 3.0 support and lets you configure your application when it is launched by the servlet container. Typically, you should update your application's main class to extend `SpringBootServletInitializer`, as shown in the following example: -include::code:MyApplication[] +include-code::MyApplication[] The next step is to update your build configuration such that your project produces a war file rather than a jar file. If you use Maven and `spring-boot-starter-parent` (which configures Maven's war plugin for you), all you need to do is to modify `pom.xml` to change the packaging to war, as follows: -[source,xml,indent=0,subs="verbatim"] +[source,xml] ---- - war +war ---- If you use Gradle, you need to modify `build.gradle` to apply the war plugin to the project, as follows: -[source,gradle,indent=0,subs="verbatim"] +[source,gradle] ---- - apply plugin: 'war' +apply plugin: 'war' ---- The final step in the process is to ensure that the embedded servlet container does not interfere with the servlet container to which the war file is deployed. @@ -36,47 +37,48 @@ To do so, you need to mark the embedded servlet container dependency as being pr If you use Maven, the following example marks the servlet container (Tomcat, in this case) as being provided: -[source,xml,indent=0,subs="verbatim"] +[source,xml] ---- - - - - org.springframework.boot - spring-boot-starter-tomcat - provided - - - + + + + org.springframework.boot + spring-boot-starter-tomcat + provided + + + ---- If you use Gradle, the following example marks the servlet container (Tomcat, in this case) as being provided: -[source,gradle,indent=0,subs="verbatim"] +[source,gradle] ---- - dependencies { - // ... - providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' - // ... - } +dependencies { + // ... + providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' + // ... +} ---- TIP: `providedRuntime` is preferred to Gradle's `compileOnly` configuration. Among other limitations, `compileOnly` dependencies are not on the test classpath, so any web-based integration tests fail. -If you use the <>, marking the embedded servlet container dependency as provided produces an executable war file with the provided dependencies packaged in a `lib-provided` directory. +If you use the Spring Boot xref:build-tool-plugin:index.adoc[], marking the embedded servlet container dependency as provided produces an executable war file with the provided dependencies packaged in a `lib-provided` directory. This means that, in addition to being deployable to a servlet container, you can also run your application by using `java -jar` on the command line. [[howto.traditional-deployment.convert-existing-application]] -=== Convert an Existing Application to Spring Boot +== Convert an Existing Application to Spring Boot + To convert an existing non-web Spring application to a Spring Boot application, replace the code that creates your `ApplicationContext` and replace it with calls to `SpringApplication` or `SpringApplicationBuilder`. Spring MVC web applications are generally amenable to first creating a deployable war application and then migrating it later to an executable war or jar. See the https://spring.io/guides/gs/convert-jar-to-war/[Getting Started Guide on Converting a jar to a war]. To create a deployable war by extending `SpringBootServletInitializer` (for example, in a class called `Application`) and adding the Spring Boot `@SpringBootApplication` annotation, use code similar to that shown in the following example: -include::code:MyApplication[tag=!main] +include-code::MyApplication[tag=!main] Remember that, whatever you put in the `sources` is merely a Spring `ApplicationContext`. Normally, anything that already works should work here. @@ -95,13 +97,13 @@ If you have other features in your application (for instance, using other servle Once the war file is working, you can make it executable by adding a `main` method to your `Application`, as shown in the following example: -include::code:MyApplication[tag=main] +include-code::MyApplication[tag=main] [NOTE] ==== If you intend to start your application as a war or as an executable application, you need to share the customizations of the builder in a method that is both available to the `SpringBootServletInitializer` callback and in the `main` method in a class similar to the following: -include::code:both/MyApplication[] +include-code::both/MyApplication[] ==== Applications can fall into more than one category: @@ -117,7 +119,7 @@ Servlet 3.0+ applications might translate pretty easily if they already use the Normally, all the code from an existing `WebApplicationInitializer` can be moved into a `SpringBootServletInitializer`. If your existing application has more than one `ApplicationContext` (for example, if it uses `AbstractDispatcherServletInitializer`) then you might be able to combine all your context sources into a single `SpringApplication`. The main complication you might encounter is if combining does not work and you need to maintain the context hierarchy. -See the <> for examples. +See the xref:application.adoc#howto.application.context-hierarchy[entry on building a hierarchy] for examples. An existing parent context that contains web-specific features usually needs to be broken up so that all the `ServletContextAware` components are in the child context. Applications that are not already Spring applications might be convertible to Spring Boot applications, and the previously mentioned guidance may help. @@ -127,30 +129,31 @@ In that case, we suggest https://stackoverflow.com/questions/tagged/spring-boot[ [[howto.traditional-deployment.weblogic]] -=== Deploying a WAR to WebLogic +== Deploying a WAR to WebLogic + To deploy a Spring Boot application to WebLogic, you must ensure that your servlet initializer *directly* implements `WebApplicationInitializer` (even if you extend from a base class that already implements it). A typical initializer for WebLogic should resemble the following example: -include::code:MyApplication[] +include-code::MyApplication[] If you use Logback, you also need to tell WebLogic to prefer the packaged version rather than the version that was pre-installed with the server. You can do so by adding a `WEB-INF/weblogic.xml` file with the following contents: -[source,xml,indent=0,subs="verbatim"] +[source,xml] ---- - - - - - org.slf4j - - - + + + + + org.slf4j + + + ---- diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/docker-compose.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/docker-compose.adoc new file mode 100644 index 000000000000..7edaa836be6b --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/docker-compose.adoc @@ -0,0 +1,40 @@ +[[howto.docker-compose]] += Docker Compose + +This section includes topics relating to the Docker Compose support in Spring Boot. + + + +[[howto.docker-compose.jdbc-url]] +== Customizing the JDBC URL + +When using `JdbcConnectionDetails` with Docker Compose, the parameters of the JDBC URL +can be customized by applying the `org.springframework.boot.jdbc.parameters` label to the +service. For example: + +[source,yaml] +---- +services: + postgres: + image: 'postgres:15.3' + environment: + - 'POSTGRES_USER=myuser' + - 'POSTGRES_PASSWORD=secret' + - 'POSTGRES_DB=mydb' + ports: + - '5432:5432' + labels: + org.springframework.boot.jdbc.parameters: 'ssl=true&sslmode=require' +---- + +With this Docker Compose file in place, the JDBC URL used is `jdbc:postgresql://127.0.0.1:5432/mydb?ssl=true&sslmode=require`. + + + +[[howto.docker-compose.sharing-services]] +== Sharing Services Between Multiple Applications + +If you want to share services between multiple applications, create the `compose.yaml` file in one of the applications and then use the configuration property configprop:spring.docker.compose.file[] in the other applications to reference the `compose.yaml` file. +You should also set configprop:spring.docker.compose.lifecycle-management[] to `start-only`, as it defaults to `start-and-stop` and stopping one application would shut down the shared services for the other still running applications as well. +Setting it to `start-only` won't stop the shared services on application stop, but a caveat is that if you shut down all applications, the services remain running. +You can stop the services manually by running `docker compose stop` on the command line in the directory which contains the `compose.yaml` file. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/hotswapping.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/hotswapping.adoc new file mode 100644 index 000000000000..2d786c069353 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/hotswapping.adoc @@ -0,0 +1,75 @@ +[[howto.hotswapping]] += Hot Swapping + +Spring Boot supports hot swapping. +This section answers questions about how it works. + + + +[[howto.hotswapping.reload-static-content]] +== Reload Static Content + +There are several options for hot reloading. +The recommended approach is to use xref:reference:using/devtools.adoc[`spring-boot-devtools`], as it provides additional development-time features, such as support for fast application restarts and LiveReload as well as sensible development-time configuration (such as template caching). +Devtools works by monitoring the classpath for changes. +This means that static resource changes must be "built" for the change to take effect. +By default, this happens automatically in Eclipse when you save your changes. +In IntelliJ IDEA, the Make Project command triggers the necessary build. +Due to the xref:reference:using/devtools.adoc#using.devtools.restart.excluding-resources[default restart exclusions], changes to static resources do not trigger a restart of your application. +They do, however, trigger a live reload. + +Alternatively, running in an IDE (especially with debugging on) is a good way to do development (all modern IDEs allow reloading of static resources and usually also allow hot-swapping of Java class changes). + +Finally, the xref:build-tool-plugin:index.adoc[Maven and Gradle plugins] can be configured (see the `addResources` property) to support running from the command line with reloading of static files directly from source. +You can use that with an external css/js compiler process if you are writing that code with higher-level tools. + + + +[[howto.hotswapping.reload-templates]] +== Reload Templates without Restarting the Container + +Most of the templating technologies supported by Spring Boot include a configuration option to disable caching (described later in this document). +If you use the `spring-boot-devtools` module, these properties are xref:reference:using/devtools.adoc#using.devtools.property-defaults[automatically configured] for you at development time. + + + +[[howto.hotswapping.reload-templates.thymeleaf]] +=== Thymeleaf Templates + +If you use Thymeleaf, set `spring.thymeleaf.cache` to `false`. +See {code-spring-boot-autoconfigure-src}/thymeleaf/ThymeleafAutoConfiguration.java[`ThymeleafAutoConfiguration`] for other Thymeleaf customization options. + + + +[[howto.hotswapping.reload-templates.freemarker]] +=== FreeMarker Templates + +If you use FreeMarker, set `spring.freemarker.cache` to `false`. +See {code-spring-boot-autoconfigure-src}/freemarker/FreeMarkerAutoConfiguration.java[`FreeMarkerAutoConfiguration`] for other FreeMarker customization options. + + + +[[howto.hotswapping.reload-templates.groovy]] +=== Groovy Templates + +If you use Groovy templates, set `spring.groovy.template.cache` to `false`. +See {code-spring-boot-autoconfigure-src}/groovy/template/GroovyTemplateAutoConfiguration.java[`GroovyTemplateAutoConfiguration`] for other Groovy customization options. + + + +[[howto.hotswapping.fast-application-restarts]] +== Fast Application Restarts + +The `spring-boot-devtools` module includes support for automatic application restarts. +While not as fast as technologies such as https://www.jrebel.com/products/jrebel[JRebel] it is usually significantly faster than a "`cold start`". +You should probably give it a try before investigating some of the more complex reload options discussed later in this document. + +For more details, see the xref:reference:using/devtools.adoc[] section. + + + +[[howto.hotswapping.reload-java-classes-without-restarting]] +== Reload Java Classes without Restarting the Container + +Many modern IDEs (Eclipse, IDEA, and others) support hot swapping of bytecode. +Consequently, if you make a change that does not affect class or method signatures, it should reload cleanly with no side effects. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/http-clients.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/http-clients.adoc new file mode 100644 index 000000000000..27d80e6fdedc --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/http-clients.adoc @@ -0,0 +1,29 @@ +[[howto.http-clients]] += HTTP Clients + +Spring Boot offers a number of starters that work with HTTP clients. +This section answers questions related to using them. + + + +[[howto.http-clients.rest-template-proxy-configuration]] +== Configure RestTemplate to Use a Proxy + +As described in xref:reference:io/rest-client.adoc#io.rest-client.resttemplate.customization[RestTemplate Customization], you can use a `RestTemplateCustomizer` with `RestTemplateBuilder` to build a customized `RestTemplate`. +This is the recommended approach for creating a `RestTemplate` configured to use a proxy. + +The exact details of the proxy configuration depend on the underlying client request factory that is being used. + + + +[[howto.http-clients.webclient-reactor-netty-customization]] +== Configure the TcpClient used by a Reactor Netty-based WebClient + +When Reactor Netty is on the classpath a Reactor Netty-based `WebClient` is auto-configured. +To customize the client's handling of network connections, provide a `ClientHttpConnector` bean. +The following example configures a 60 second connect timeout and adds a `ReadTimeoutHandler`: + +include-code::MyReactorNettyClientConfiguration[] + +TIP: Note the use of `ReactorResourceFactory` for the connection provider and event loop resources. +This ensures efficient sharing of resources for the server receiving requests and the client making requests. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/index.adoc new file mode 100644 index 000000000000..d9394c925399 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/index.adoc @@ -0,0 +1,12 @@ +[[howto]] += How-to Guides + +This section provides answers to some common '`how do I do that...`' questions that often arise when using Spring Boot. +Its coverage is not exhaustive, but it does cover quite a lot. + +If you have a specific problem that we do not cover here, you might want to check https://stackoverflow.com/tags/spring-boot[stackoverflow.com] to see if someone has already provided an answer. +This is also a great place to ask new questions (please use the `spring-boot` tag). + +We are also more than happy to extend this section. +If you want to add a '`how-to`', send us a {url-github}[pull request]. + diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/jersey.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/jersey.adoc similarity index 76% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/jersey.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/jersey.adoc index ff1246e543a2..c4ed028ad703 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/jersey.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/jersey.adoc @@ -1,30 +1,26 @@ [[howto.jersey]] -== Jersey += Jersey [[howto.jersey.spring-security]] -=== Secure Jersey endpoints with Spring Security +== Secure Jersey Endpoints with Spring Security + Spring Security can be used to secure a Jersey-based web application in much the same way as it can be used to secure a Spring MVC-based web application. However, if you want to use Spring Security's method-level security with Jersey, you must configure Jersey to use `setStatus(int)` rather `sendError(int)`. This prevents Jersey from committing the response before Spring Security has had an opportunity to report an authentication or authorization failure to the client. The `jersey.config.server.response.setStatusOverSendError` property must be set to `true` on the application's `ResourceConfig` bean, as shown in the following example: -[source,java,indent=0,subs="verbatim"] ----- -include::{docs-java}/howto/jersey/springsecurity/JerseySetStatusOverSendErrorConfig.java[] ----- +include-code::JerseySetStatusOverSendErrorConfig[] [[howto.jersey.alongside-another-web-framework]] -=== Use Jersey Alongside Another Web Framework +== Use Jersey Alongside Another Web Framework + To use Jersey alongside another web framework, such as Spring MVC, it should be configured so that it will allow the other framework to handle requests that it cannot handle. First, configure Jersey to use a filter rather than a servlet by configuring the configprop:spring.jersey.type[] application property with a value of `filter`. Second, configure your `ResourceConfig` to forward requests that would have resulted in a 404, as shown in the following example. -[source,java,indent=0,subs="verbatim"] ----- -include::{docs-java}/howto/jersey/alongsideanotherwebframework/JerseyConfig.java[] ----- +include-code::JerseyConfig[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/logging.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/logging.adoc new file mode 100644 index 000000000000..d9bd04076f73 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/logging.adoc @@ -0,0 +1,203 @@ +[[howto.logging]] += Logging + +Spring Boot has no mandatory logging dependency, except for the Commons Logging API, which is typically provided by Spring Framework's `spring-jcl` module. +To use https://logback.qos.ch[Logback], you need to include it and `spring-jcl` on the classpath. +The recommended way to do that is through the starters, which all depend on `spring-boot-starter-logging`. +For a web application, you only need `spring-boot-starter-web`, since it depends transitively on the logging starter. +If you use Maven, the following dependency adds logging for you: + +[source,xml] +---- + + org.springframework.boot + spring-boot-starter-web + +---- + +Spring Boot has a `LoggingSystem` abstraction that attempts to configure logging based on the content of the classpath. +If Logback is available, it is the first choice. + +If the only change you need to make to logging is to set the levels of various loggers, you can do so in `application.properties` by using the "logging.level" prefix, as shown in the following example: + +[configprops,yaml] +---- +logging: + level: + org.springframework.web: "debug" + org.hibernate: "error" +---- + +You can also set the location of a file to which the log will be written (in addition to the console) by using `logging.file.name`. + +To configure the more fine-grained settings of a logging system, you need to use the native configuration format supported by the `LoggingSystem` in question. +By default, Spring Boot picks up the native configuration from its default location for the system (such as `classpath:logback.xml` for Logback), but you can set the location of the config file by using the configprop:logging.config[] property. + + + +[[howto.logging.logback]] +== Configure Logback for Logging + +If you need to apply customizations to logback beyond those that can be achieved with `application.properties`, you will need to add a standard logback configuration file. +You can add a `logback.xml` file to the root of your classpath for logback to find. +You can also use `logback-spring.xml` if you want to use the Spring Boot xref:reference:features/logging.adoc#features.logging.logback-extensions[]. + +TIP: The Logback documentation has a https://logback.qos.ch/manual/configuration.html[dedicated section that covers configuration] in some detail. + +Spring Boot provides a number of logback configurations that can be `included` in your own configuration. +These includes are designed to allow certain common Spring Boot conventions to be re-applied. + +The following files are provided under `org/springframework/boot/logging/logback/`: + +* `defaults.xml` - Provides conversion rules, pattern properties and common logger configurations. +* `console-appender.xml` - Adds a `ConsoleAppender` using the `CONSOLE_LOG_PATTERN`. +* `structured-console-appender.xml` - Adds a `ConsoleAppender` using structured logging in the `CONSOLE_LOG_STRUCTURED_FORMAT`. +* `file-appender.xml` - Adds a `RollingFileAppender` using the `FILE_LOG_PATTERN` and `ROLLING_FILE_NAME_PATTERN` with appropriate settings. +* `structured-file-appender.xml` - Adds a `RollingFileAppender` using the `ROLLING_FILE_NAME_PATTERN` with structured logging in the `FILE_LOG_STRUCTURED_FORMAT`. + +In addition, a legacy `base.xml` file is provided for compatibility with earlier versions of Spring Boot. + +A typical custom `logback.xml` file would look something like this: + +[source,xml] +---- + + + + + + + + + +---- + +Your logback configuration file can also make use of System properties that the `LoggingSystem` takes care of creating for you: + +* `$\{PID}`: The current process ID. +* `$\{LOG_FILE}`: Whether `logging.file.name` was set in Boot's external configuration. +* `$\{LOG_PATH}`: Whether `logging.file.path` (representing a directory for log files to live in) was set in Boot's external configuration. +* `$\{LOG_EXCEPTION_CONVERSION_WORD}`: Whether `logging.exception-conversion-word` was set in Boot's external configuration. +* `$\{ROLLING_FILE_NAME_PATTERN}`: Whether `logging.pattern.rolling-file-name` was set in Boot's external configuration. + +Spring Boot also provides some nice ANSI color terminal output on a console (but not in a log file) by using a custom Logback converter. +See the `CONSOLE_LOG_PATTERN` in the `defaults.xml` configuration for an example. + +If Groovy is on the classpath, you should be able to configure Logback with `logback.groovy` as well. +If present, this setting is given preference. + +NOTE: Spring extensions are not supported with Groovy configuration. +Any `logback-spring.groovy` files will not be detected. + + + +[[howto.logging.logback.file-only-output]] +=== Configure Logback for File-only Output + +If you want to disable console logging and write output only to a file, you need a custom `logback-spring.xml` that imports `file-appender.xml` but not `console-appender.xml`, as shown in the following example: + +[source,xml] +---- + + + + + + + + + +---- + +You also need to add `logging.file.name` to your `application.properties` or `application.yaml`, as shown in the following example: + +[configprops,yaml] +---- +logging: + file: + name: "myapplication.log" +---- + + + +[[howto.logging.log4j]] +== Configure Log4j for Logging + +Spring Boot supports https://logging.apache.org/log4j/2.x/[Log4j 2] for logging configuration if it is on the classpath. +If you use the starters for assembling dependencies, you have to exclude Logback and then include Log4j 2 instead. +If you do not use the starters, you need to provide (at least) `spring-jcl` in addition to Log4j 2. + +The recommended path is through the starters, even though it requires some jiggling. +The following example shows how to set up the starters in Maven: + +[source,xml] +---- + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + +---- + +Gradle provides a few different ways to set up the starters. +One way is to use a {url-gradle-docs}/resolution_rules.html#sec:module_replacement[module replacement]. +To do so, declare a dependency on the Log4j 2 starter and tell Gradle that any occurrences of the default logging starter should be replaced by the Log4j 2 starter, as shown in the following example: + +[source,gradle] +---- +dependencies { + implementation "org.springframework.boot:spring-boot-starter-log4j2" + modules { + module("org.springframework.boot:spring-boot-starter-logging") { + replacedBy("org.springframework.boot:spring-boot-starter-log4j2", "Use Log4j2 instead of Logback") + } + } +} +---- + +NOTE: The Log4j starters gather together the dependencies for common logging requirements (such as having Tomcat use `java.util.logging` but configuring the output using Log4j 2). + +NOTE: To ensure that debug logging performed using `java.util.logging` is routed into Log4j 2, configure its https://logging.apache.org/log4j/2.x/log4j-jul.html[JDK logging adapter] by setting the `java.util.logging.manager` system property to `org.apache.logging.log4j.jul.LogManager`. + + + +[[howto.logging.log4j.yaml-or-json-config]] +=== Use YAML or JSON to Configure Log4j 2 + +In addition to its default XML configuration format, Log4j 2 also supports YAML and JSON configuration files. +To configure Log4j 2 to use an alternative configuration file format, add the appropriate dependencies to the classpath and name your configuration files to match your chosen file format, as shown in the following example: + +[cols="10,75a,15a"] +|=== +| Format | Dependencies | File names + +|YAML +| `com.fasterxml.jackson.core:jackson-databind` + `com.fasterxml.jackson.dataformat:jackson-dataformat-yaml` +| `log4j2.yaml` + `log4j2.yml` + +|JSON +| `com.fasterxml.jackson.core:jackson-databind` +| `log4j2.json` + `log4j2.jsn` +|=== + + + +[[howto.logging.log4j.composite-configuration]] +=== Use Composite Configuration to Configure Log4j 2 + +Log4j 2 has support for combining multiple configuration files into a single composite configuration. +To use this support in Spring Boot, configure configprop:logging.log4j2.config.override[] with the locations of one or more secondary configuration files. +The secondary configuration files will be merged with the primary configuration, whether the primary's source is Spring Boot's defaults, a standard location such as `log4j.xml`, or the location configured by the configprop:logging.config[] property. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/messaging.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/messaging.adoc new file mode 100644 index 000000000000..79e994b3309e --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/messaging.adoc @@ -0,0 +1,18 @@ +[[howto.messaging]] += Messaging + +Spring Boot offers a number of starters to support messaging. +This section answers questions that arise from using messaging with Spring Boot. + + + +[[howto.messaging.disable-transacted-jms-session]] +== Disable Transacted JMS Session + +If your JMS broker does not support transacted sessions, you have to disable the support of transactions altogether. +If you create your own `JmsListenerContainerFactory`, there is nothing to do, since, by default it cannot be transacted. +If you want to use the `DefaultJmsListenerContainerFactoryConfigurer` to reuse Spring Boot's default, you can disable transacted sessions, as follows: + +include-code::MyJmsConfiguration[] + +The preceding example overrides the default factory, and it should be applied to any other factory that your application defines, if any. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/native-image/developing-your-first-application.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/native-image/developing-your-first-application.adoc new file mode 100644 index 000000000000..fb0362c1e042 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/native-image/developing-your-first-application.adoc @@ -0,0 +1,279 @@ +[[howto.native-image.developing-your-first-application]] += Developing Your First GraalVM Native Application + +There are two main ways to build a Spring Boot native image application: + +* Using Spring Boot xref:reference:packaging/container-images/cloud-native-buildpacks.adoc[support for Cloud Native Buildpacks] with the https://paketo.io/docs/reference/java-native-image-reference/[Paketo Java Native Image buildpack] to generate a lightweight container containing a native executable. +* Using GraalVM Native Build Tools to generate a native executable. + +TIP: The easiest way to start a new native Spring Boot project is to go to https://start.spring.io[start.spring.io], add the `GraalVM Native Support` dependency and generate the project. +The included `HELP.md` file will provide getting started hints. + + + +[[howto.native-image.developing-your-first-application.sample-application]] +== Sample Application + +We need an example application that we can use to create our native image. +For our purposes, the simple "Hello World!" web application that's covered in the xref:tutorial:first-application/index.adoc[] section will suffice. + +To recap, our main application code looks like this: + +include-code::MyApplication[] + +This application uses Spring MVC and embedded Tomcat, both of which have been tested and verified to work with GraalVM native images. + + + +[[howto.native-image.developing-your-first-application.buildpacks]] +== Building a Native Image Using Buildpacks + +Spring Boot supports building Docker images containing native executables, using Cloud Native Buildpacks (CNB) integration with both Maven and Gradle and the https://paketo.io/docs/reference/java-native-image-reference/[Paketo Java Native Image buildpack]. +This means you can just type a single command and quickly get a sensible image into your locally running Docker daemon. +The resulting image doesn't contain a JVM, instead the native image is compiled statically. +This leads to smaller images. + +NOTE: The CNB builder used for the images is `paketobuildpacks/builder-jammy-tiny:latest`. +It has small footprint and reduced attack surface, but you can also use `paketobuildpacks/builder-jammy-base:latest` or `paketobuildpacks/builder-jammy-full:latest` to have more tools available in the image if required. + + + +[[howto.native-image.developing-your-first-application.buildpacks.system-requirements]] +=== System Requirements + +Docker should be installed. See https://docs.docker.com/installation/#installation[Get Docker] for more details. +https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user[Configure it to allow non-root user] if you are on Linux. + +NOTE: You can run `docker run hello-world` (without `sudo`) to check the Docker daemon is reachable as expected. +Check the xref:maven-plugin:build-image.adoc#build-image.docker-daemon[Maven] or xref:gradle-plugin:packaging-oci-image.adoc#build-image.docker-daemon[Gradle] Spring Boot plugin documentation for more details. + +TIP: On macOS, it is recommended to increase the memory allocated to Docker to at least `8GB`, and potentially add more CPUs as well. +See this https://stackoverflow.com/questions/44533319/how-to-assign-more-memory-to-docker-container/44533437#44533437[Stack Overflow answer] for more details. +On Microsoft Windows, make sure to enable the https://docs.docker.com/docker-for-windows/wsl/[Docker WSL 2 backend] for better performance. + + + +[[howto.native-image.developing-your-first-application.buildpacks.maven]] +=== Using Maven + +To build a native image container using Maven you should ensure that your `pom.xml` file uses the `spring-boot-starter-parent` and the `org.graalvm.buildtools:native-maven-plugin`. +You should have a `` section that looks like this: + +[source,xml,subs="verbatim,attributes"] +---- + + org.springframework.boot + spring-boot-starter-parent + {version-spring-boot} + +---- + +You additionally should have this in the ` ` section: + +[source,xml,subs="verbatim,attributes"] +---- + + org.graalvm.buildtools + native-maven-plugin + +---- + +The `spring-boot-starter-parent` declares a `native` profile that configures the executions that need to run in order to create a native image. +You can activate profiles using the `-P` flag on the command line. + +TIP: If you don't want to use `spring-boot-starter-parent` you'll need to configure executions for the `process-aot` goal from Spring Boot's plugin and the `add-reachability-metadata` goal from the Native Build Tools plugin. + +To build the image, you can run the `spring-boot:build-image` goal with the `native` profile active: + +[source,shell] +---- +$ mvn -Pnative spring-boot:build-image +---- + + + +[[howto.native-image.developing-your-first-application.buildpacks.gradle]] +=== Using Gradle + +The Spring Boot Gradle plugin automatically configures AOT tasks when the GraalVM Native Image plugin is applied. +You should check that your Gradle build contains a `plugins` block that includes `org.graalvm.buildtools.native`. + +As long as the `org.graalvm.buildtools.native` plugin is applied, the `bootBuildImage` task will generate a native image rather than a JVM one. +You can run the task using: + +[source,shell] +---- +$ gradle bootBuildImage +---- + + + +[[howto.native-image.developing-your-first-application.buildpacks.running]] +=== Running the example + +Once you have run the appropriate build command, a Docker image should be available. +You can start your application using `docker run`: + +[source,shell] +---- +$ docker run --rm -p 8080:8080 docker.io/library/myproject:0.0.1-SNAPSHOT +---- + +You should see output similar to the following: + +[source,shell] +---- + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: (v{version-spring-boot}) +....... . . . +....... . . . (log output here) +....... . . . +........ Started MyApplication in 0.08 seconds (process running for 0.095) +---- + +NOTE: The startup time differs from machine to machine, but it should be much faster than a Spring Boot application running on a JVM. + +If you open a web browser to `http://localhost:8080`, you should see the following output: + +[source] +---- +Hello World! +---- + +To gracefully exit the application, press `ctrl-c`. + + + +[[howto.native-image.developing-your-first-application.native-build-tools]] +== Building a Native Image using Native Build Tools + +If you want to generate a native executable directly without using Docker, you can use GraalVM Native Build Tools. +Native Build Tools are plugins shipped by GraalVM for both Maven and Gradle. +You can use them to perform a variety of GraalVM tasks, including generating a native image. + + + +[[howto.native-image.developing-your-first-application.native-build-tools.prerequisites]] +=== Prerequisites + +To build a native image using the Native Build Tools, you'll need a GraalVM distribution on your machine. +You can either download it manually on the {url-download-liberica-nik}[Liberica Native Image Kit page], or you can use a download manager like SDKMAN!. + + + +[[howto.native-image.developing-your-first-application.native-build-tools.prerequisites.linux-macos]] +==== Linux and macOS + +To install the native image compiler on macOS or Linux, we recommend using SDKMAN!. +Get SDKMAN! from https://sdkman.io and install the Liberica GraalVM distribution by using the following commands: + +[source,shell,subs="verbatim,attributes"] +---- +$ sdk install java {version-graal}.r17-nik +$ sdk use java {version-graal}.r17-nik +---- + +Verify that the correct version has been configured by checking the output of `java -version`: + +[source,shell,subs="verbatim,attributes"] +---- +$ java -version +openjdk version "17.0.5" 2022-10-18 LTS +OpenJDK Runtime Environment GraalVM 22.3.0 (build 17.0.5+8-LTS) +OpenJDK 64-Bit Server VM GraalVM 22.3.0 (build 17.0.5+8-LTS, mixed mode) +---- + + + +[[howto.native-image.developing-your-first-application.native-build-tools.prerequisites.windows]] +==== Windows + +On Windows, follow https://medium.com/graalvm/using-graalvm-and-native-image-on-windows-10-9954dc071311[these instructions] to install either https://www.graalvm.org/downloads/[GraalVM] or {url-download-liberica-nik}[Liberica Native Image Kit] in version {version-graal}, the Visual Studio Build Tools and the Windows SDK. +Due to the https://docs.microsoft.com/en-US/troubleshoot/windows-client/shell-experience/command-line-string-limitation[Windows related command-line maximum length], make sure to use x64 Native Tools Command Prompt instead of the regular Windows command line to run Maven or Gradle plugins. + + + +[[howto.native-image.developing-your-first-application.native-build-tools.maven]] +=== Using Maven + +As with the xref:native-image/developing-your-first-application.adoc#howto.native-image.developing-your-first-application.buildpacks.maven[buildpacks support], you need to make sure that you're using `spring-boot-starter-parent` in order to inherit the `native` profile and that the `org.graalvm.buildtools:native-maven-plugin` plugin is used. + +With the `native` profile active, you can invoke the `native:compile` goal to trigger `native-image` compilation: + +[source,shell] +---- +$ mvn -Pnative native:compile +---- + +The native image executable can be found in the `target` directory. + + + +[[howto.native-image.developing-your-first-application.native-build-tools.gradle]] +=== Using Gradle + +When the Native Build Tools Gradle plugin is applied to your project, the Spring Boot Gradle plugin will automatically trigger the Spring AOT engine. +Task dependencies are automatically configured, so you can just run the standard `nativeCompile` task to generate a native image: + +[source,shell] +---- +$ gradle nativeCompile +---- + +The native image executable can be found in the `build/native/nativeCompile` directory. + + + +[[howto.native-image.developing-your-first-application.native-build-tools.running]] +=== Running the Example + +At this point, your application should work. You can now start the application by running it directly: + +[tabs] +====== +Maven:: ++ +[source,shell] +---- +$ target/myproject +---- +Gradle:: ++ +[source,shell] +---- +$ build/native/nativeCompile/myproject +---- +====== + +You should see output similar to the following: + +[source,shell,subs="verbatim,attributes"] +---- + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: (v{version-spring-boot}) +....... . . . +....... . . . (log output here) +....... . . . +........ Started MyApplication in 0.08 seconds (process running for 0.095) +---- + +NOTE: The startup time differs from machine to machine, but it should be much faster than a Spring Boot application running on a JVM. + +If you open a web browser to `http://localhost:8080`, you should see the following output: + +[source] +---- +Hello World! +---- + +To gracefully exit the application, press `ctrl-c`. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/native-image/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/native-image/index.adoc new file mode 100644 index 000000000000..c47a2d88aa4e --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/native-image/index.adoc @@ -0,0 +1,7 @@ +[[howto.native-image]] += GraalVM Native Applications + +This section contains details on developing and testing Spring Boot applications as GraalVM native images. + +TIP: For an overview of GraalVM native image concepts, see the xref:reference:packaging/native-image/introducing-graalvm-native-images.adoc[] section. + diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/native-image/testing-native-applications.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/native-image/testing-native-applications.adoc similarity index 83% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/native-image/testing-native-applications.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/native-image/testing-native-applications.adoc index c2f39893b731..16937b28677e 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/native-image/testing-native-applications.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/native-image/testing-native-applications.adoc @@ -1,5 +1,6 @@ -[[native-image.testing]] -== Testing GraalVM Native Images +[[howto.native-image.testing]] += Testing GraalVM Native Images + When writing native image applications, we recommend that you continue to use the JVM whenever possible to develop the majority of your unit and integration tests. This will help keep developer build times down and allow you to use existing IDE integrations. With broad test coverage on the JVM, you can then focus native image testing on the areas that are likely to be different. @@ -12,8 +13,9 @@ For native image testing, you're generally looking to ensure that the following -[[native-image.testing.with-the-jvm]] -=== Testing Ahead-of-time Processing With the JVM +[[howto.native-image.testing.with-the-jvm]] +== Testing Ahead-of-Time Processing With the JVM + When a Spring Boot application runs, it attempts to detect if it is running as a native image. If it is running as a native image, it will initialize the application using the code that was generated during at build-time by the Spring AOT engine. @@ -26,7 +28,7 @@ To run a Spring Boot application on the JVM and have it use AOT generated code y For example: -[source,shell,indent=0,subs="verbatim"] +[source,shell] ---- $ java -Dspring.aot.enabled=true -jar myapplication.jar ---- @@ -43,8 +45,9 @@ Or you might consider using a project like Selenium to check your application's -[[native-image.testing.with-native-build-tools]] -=== Testing With Native Build Tools +[[howto.native-image.testing.with-native-build-tools]] +== Testing With Native Build Tools + GraalVM Native Build Tools includes the ability to run tests inside a native image. This can be helpful when you want to deeply test that the internals of your application work in a GraalVM native image. @@ -55,7 +58,7 @@ For example, you might choose to run native tests once a day. Spring Framework includes ahead-of-time support for running tests. All the usual Spring testing features work with native image tests. For example, you can continue to use the `@SpringBootTest` annotation. -You can also use Spring Boot <> to test only specific parts of your application. +You can also use Spring Boot xref:reference:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-tests[test slices] to test only specific parts of your application. Spring Framework's native testing support works in the following way: @@ -67,18 +70,19 @@ Spring Framework's native testing support works in the following way: -[[native-image.testing.with-native-build-tools.maven]] -==== Using Maven +[[howto.native-image.testing.with-native-build-tools.maven]] +=== Using Maven + To run native tests using Maven, ensure that your `pom.xml` file uses the `spring-boot-starter-parent`. You should have a `` section that looks like this: -[source,xml,indent=0,subs="verbatim,attributes"] +[source,xml,subs="verbatim,attributes"] ---- - - org.springframework.boot - spring-boot-starter-parent - {spring-boot-version} - + + org.springframework.boot + spring-boot-starter-parent + {version-spring-boot} + ---- The `spring-boot-starter-parent` declares a `nativeTest` profile that configures the executions that are needed to run the native tests. @@ -88,22 +92,23 @@ TIP: If you don't want to use `spring-boot-starter-parent` you'll need to config To build the image and run the tests, use the `test` goal with the `nativeTest` profile active: -[indent=0,subs="verbatim"] +[source,shell] ---- - $ mvn -PnativeTest test +$ mvn -PnativeTest test ---- -[[native-image.testing.with-native-build-tools.gradle]] -==== Using Gradle +[[howto.native-image.testing.with-native-build-tools.gradle]] +=== Using Gradle + The Spring Boot Gradle plugin automatically configures AOT test tasks when the GraalVM Native Image plugin is applied. You should check that your Gradle build contains a `plugins` block that includes `org.graalvm.buildtools.native`. To run native tests using Gradle you can use the `nativeTest` task: -[indent=0,subs="verbatim"] +[source,shell] ---- - $ gradle nativeTest +$ gradle nativeTest ---- diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/nosql.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/nosql.adoc new file mode 100644 index 000000000000..bb1e9ef177d9 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/nosql.adoc @@ -0,0 +1,47 @@ +[[howto.nosql]] += NoSQL + +Spring Boot offers a number of starters that support NoSQL technologies. +This section answers questions that arise from using NoSQL with Spring Boot. + + + +[[howto.nosql.jedis-instead-of-lettuce]] +== Use Jedis Instead of Lettuce + +By default, the Spring Boot starter (`spring-boot-starter-data-redis`) uses https://github.com/lettuce-io/lettuce-core/[Lettuce]. +You need to exclude that dependency and include the https://github.com/xetorthio/jedis/[Jedis] one instead. +Spring Boot manages both of these dependencies, allowing you to switch to Jedis without specifying a version. + +The following example shows how to accomplish this in Maven: + +[source,xml] +---- + + org.springframework.boot + spring-boot-starter-data-redis + + + io.lettuce + lettuce-core + + + + + redis.clients + jedis + +---- + +The following example shows how to accomplish this in Gradle: + +[source,gradle] +---- +dependencies { + implementation('org.springframework.boot:spring-boot-starter-data-redis') { + exclude group: 'io.lettuce', module: 'lettuce-core' + } + implementation 'redis.clients:jedis' + // ... +} +---- diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/properties-and-configuration.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/properties-and-configuration.adoc new file mode 100644 index 000000000000..4ba677087272 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/properties-and-configuration.adoc @@ -0,0 +1,309 @@ +[[howto.properties-and-configuration]] += Properties and Configuration + +This section includes topics about setting and reading properties and configuration settings and their interaction with Spring Boot applications. + + + +[[howto.properties-and-configuration.expand-properties]] +== Automatically Expand Properties at Build Time + +Rather than hardcoding some properties that are also specified in your project's build configuration, you can automatically expand them by instead using the existing build configuration. +This is possible in both Maven and Gradle. + + + +[[howto.properties-and-configuration.expand-properties.maven]] +=== Automatic Property Expansion Using Maven + +You can automatically expand properties in the Maven project by using resource filtering. +If you use the `spring-boot-starter-parent`, you can then refer to your Maven '`project properties`' with `@..@` placeholders, as shown in the following example: + +[configprops%novalidate,yaml] +---- +app: + encoding: "@project.build.sourceEncoding@" + java: + version: "@java.version@" +---- + +NOTE: Only production configuration is filtered that way (in other words, no filtering is applied on `src/test/resources`). + +TIP: If you enable the `addResources` flag, the `spring-boot:run` goal can add `src/main/resources` directly to the classpath (for hot reloading purposes). +Doing so circumvents the resource filtering and this feature. +Instead, you can use the `exec:java` goal or customize the plugin's configuration. +See the xref:maven-plugin:using.adoc[plugin usage page] for more details. + +If you do not use the starter parent, you need to include the following element inside the `` element of your `pom.xml`: + +[source,xml] +---- + + + src/main/resources + true + + +---- + +You also need to include the following element inside ``: + +[source,xml] +---- + + org.apache.maven.plugins + maven-resources-plugin + 2.7 + + + @ + + false + + +---- + +NOTE: The `useDefaultDelimiters` property is important if you use standard Spring placeholders (such as `$\{placeholder}`) in your configuration. +If that property is not set to `false`, these may be expanded by the build. + + + +[[howto.properties-and-configuration.expand-properties.gradle]] +=== Automatic Property Expansion Using Gradle + +You can automatically expand properties from the Gradle project by configuring the Java plugin's `processResources` task to do so, as shown in the following example: + +[source,gradle] +---- +tasks.named('processResources') { + expand(project.properties) +} +---- + +You can then refer to your Gradle project's properties by using placeholders, as shown in the following example: + +[configprops%novalidate,yaml] +---- +app: + name: "${name}" + description: "${description}" +---- + +NOTE: Gradle's `expand` method uses Groovy's `SimpleTemplateEngine`, which transforms `${..}` tokens. +The `${..}` style conflicts with Spring's own property placeholder mechanism. +To use Spring property placeholders together with automatic expansion, escape the Spring property placeholders as follows: `\${..}`. + + + +[[howto.properties-and-configuration.externalize-configuration]] +== Externalize the Configuration of SpringApplication + +A `SpringApplication` has bean property setters, so you can use its Java API as you create the application to modify its behavior. +Alternatively, you can externalize the configuration by setting properties in `+spring.main.*+`. +For example, in `application.properties`, you might have the following settings: + +[configprops,yaml] +---- +spring: + main: + web-application-type: "none" + banner-mode: "off" +---- + +Then the Spring Boot banner is not printed on startup, and the application is not starting an embedded web server. + +Properties defined in external configuration override and replace the values specified with the Java API, with the notable exception of the primary sources. +Primary sources are those provided to the `SpringApplication` constructor: + +include-code::application/MyApplication[] + +Or to `sources(...)` method of a `SpringApplicationBuilder`: + +include-code::builder/MyApplication[] + +Given the examples above, if we have the following configuration: + +[configprops,yaml] +---- +spring: + main: + sources: "com.example.MyDatabaseConfig,com.example.MyJmsConfig" + banner-mode: "console" +---- + +The actual application will show the banner (as overridden by configuration) and use three sources for the `ApplicationContext`. +The application sources are: + +. `MyApplication` (from the code) +. `MyDatabaseConfig` (from the external config) +. `MyJmsConfig`(from the external config) + + + +[[howto.properties-and-configuration.external-properties-location]] +== Change the Location of External Properties of an Application + +By default, properties from different sources are added to the Spring `Environment` in a defined order (see xref:reference:features/external-config.adoc[] in the "`Spring Boot Features`" section for the exact order). + +You can also provide the following System properties (or environment variables) to change the behavior: + +* configprop:spring.config.name[] (configprop:spring.config.name[format=envvar]): Defaults to `application` as the root of the file name. +* configprop:spring.config.location[] (configprop:spring.config.location[format=envvar]): The file to load (such as a classpath resource or a URL). + A separate `Environment` property source is set up for this document and it can be overridden by system properties, environment variables, or the command line. + +No matter what you set in the environment, Spring Boot always loads `application.properties` as described above. +By default, if YAML is used, then files with the '`.yaml`' and '`.yml`' extensions are also added to the list. + +TIP: If you want detailed information about the files that are being loaded you can xref:reference:features/logging.adoc#features.logging.log-levels[set the logging level] of `org.springframework.boot.context.config` to `trace`. + + + +[[howto.properties-and-configuration.short-command-line-arguments]] +== Use '`Short`' Command Line Arguments + +Some people like to use (for example) `--port=9000` instead of `--server.port=9000` to set configuration properties on the command line. +You can enable this behavior by using placeholders in `application.properties`, as shown in the following example: + +[configprops,yaml] +---- +server: + port: "${port:8080}" +---- + +TIP: If you inherit from the `spring-boot-starter-parent` POM, the default filter token of the `maven-resources-plugins` has been changed from `+${*}+` to `@` (that is, `@maven.token@` instead of `${maven.token}`) to prevent conflicts with Spring-style placeholders. +If you have enabled Maven filtering for the `application.properties` directly, you may want to also change the default filter token to use https://maven.apache.org/plugins/maven-resources-plugin/resources-mojo.html#delimiters[other delimiters]. + +NOTE: In this specific case, the port binding works in a PaaS environment such as Heroku or Cloud Foundry. +On those two platforms, the `PORT` environment variable is set automatically and Spring can bind to capitalized synonyms for `Environment` properties. + + + +[[howto.properties-and-configuration.yaml]] +== Use YAML for External Properties + +YAML is a superset of JSON and, as such, is a convenient syntax for storing external properties in a hierarchical format, as shown in the following example: + +[source,yaml] +---- +spring: + application: + name: "cruncher" + datasource: + driver-class-name: "com.mysql.jdbc.Driver" + url: "jdbc:mysql://localhost/test" +server: + port: 9000 +---- + +Create a file called `application.yaml` and put it in the root of your classpath. +Then add `snakeyaml` to your dependencies (Maven coordinates `org.yaml:snakeyaml`, already included if you use the `spring-boot-starter`). +A YAML file is parsed to a Java `Map` (like a JSON object), and Spring Boot flattens the map so that it is one level deep and has period-separated keys, as many people are used to with `Properties` files in Java. + +The preceding example YAML corresponds to the following `application.properties` file: + +[source,properties,subs="verbatim",configprops] +---- +spring.application.name=cruncher +spring.datasource.driver-class-name=com.mysql.jdbc.Driver +spring.datasource.url=jdbc:mysql://localhost/test +server.port=9000 +---- + +See xref:reference:features/external-config.adoc#features.external-config.yaml[] in the "`Spring Boot Features'" section for more information about YAML. + + + +[[howto.properties-and-configuration.set-active-spring-profiles]] +== Set the Active Spring Profiles + +The Spring `Environment` has an API for this, but you would normally set a System property (configprop:spring.profiles.active[]) or an OS environment variable (configprop:spring.profiles.active[format=envvar]). +Also, you can launch your application with a `-D` argument (remember to put it before the main class or jar archive), as follows: + +[source,shell] +---- +$ java -jar -Dspring.profiles.active=production demo-0.0.1-SNAPSHOT.jar +---- + +In Spring Boot, you can also set the active profile in `application.properties`, as shown in the following example: + +[configprops,yaml] +---- +spring: + profiles: + active: "production" +---- + +A value set this way is replaced by the System property or environment variable setting but not by the `SpringApplicationBuilder.profiles()` method. +Thus, the latter Java API can be used to augment the profiles without changing the defaults. + +See xref:reference:features/profiles.adoc[] in the "`Spring Boot Features`" section for more information. + + + +[[howto.properties-and-configuration.set-default-spring-profile-name]] +== Set the Default Profile Name + +The default profile is a profile that is enabled if no profile is active. +By default, the name of the default profile is `default`, but it could be changed using a System property (configprop:spring.profiles.default[]) or an OS environment variable (configprop:spring.profiles.default[format=envvar]). + +In Spring Boot, you can also set the default profile name in `application.properties`, as shown in the following example: + +[configprops,yaml] +---- +spring: + profiles: + default: "dev" +---- + +See xref:reference:features/profiles.adoc[] in the "`Spring Boot Features`" section for more information. + + + +[[howto.properties-and-configuration.change-configuration-depending-on-the-environment]] +== Change Configuration Depending on the Environment + +Spring Boot supports multi-document YAML and Properties files (see xref:reference:features/external-config.adoc#features.external-config.files.multi-document[] for details) which can be activated conditionally based on the active profiles. + +If a document contains a `spring.config.activate.on-profile` key, then the profiles value (a comma-separated list of profiles or a profile expression) is fed into the Spring `Environment.acceptsProfiles()` method. +If the profile expression matches, then that document is included in the final merge (otherwise, it is not), as shown in the following example: + +[configprops,yaml] +---- +server: + port: 9000 +--- +spring: + config: + activate: + on-profile: "development" +server: + port: 9001 +--- +spring: + config: + activate: + on-profile: "production" +server: + port: 0 +---- + +In the preceding example, the default port is 9000. +However, if the Spring profile called '`development`' is active, then the port is 9001. +If '`production`' is active, then the port is 0. + +NOTE: The documents are merged in the order in which they are encountered. +Later values override earlier values. + + + +[[howto.properties-and-configuration.discover-build-in-options-for-external-properties]] +== Discover Built-in Options for External Properties + +Spring Boot binds external properties from `application.properties` (or YAML files and other places) into an application at runtime. +There is not (and technically cannot be) an exhaustive list of all supported properties in a single location, because contributions can come from additional jar files on your classpath. + +A running application with the Actuator features has a `configprops` endpoint that shows all the bound and bindable properties available through `@ConfigurationProperties`. + +The appendix includes an xref:appendix:application-properties/index.adoc[`application.properties`] example with a list of the most common properties supported by Spring Boot. +The definitive list comes from searching the source code for `@ConfigurationProperties` and `@Value` annotations as well as the occasional use of `Binder`. +For more about the exact ordering of loading properties, see xref:reference:features/external-config.adoc[]. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/security.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/security.adoc new file mode 100644 index 000000000000..3aa2112aa6fd --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/security.adoc @@ -0,0 +1,49 @@ +[[howto.security]] += Security + +This section addresses questions about security when working with Spring Boot, including questions that arise from using Spring Security with Spring Boot. + +For more about Spring Security, see the {url-spring-security-site}[Spring Security project page]. + + + +[[howto.security.switch-off-spring-boot-configuration]] +== Switch Off the Spring Boot Security Configuration + +If you define a `@Configuration` with a `SecurityFilterChain` bean in your application, this action switches off the default webapp security settings in Spring Boot. + + + +[[howto.security.change-user-details-service-and-add-user-accounts]] +== Change the UserDetailsService and Add User Accounts + +If you provide a `@Bean` of type `AuthenticationManager`, `AuthenticationProvider`, or `UserDetailsService`, the default `@Bean` for `InMemoryUserDetailsManager` is not created. +This means you have the full feature set of Spring Security available (such as {url-spring-security-docs}/servlet/authentication/index.html[various authentication options]). + +The easiest way to add user accounts is by providing your own `UserDetailsService` bean. + + + +[[howto.security.enable-https]] +== Enable HTTPS When Running Behind a Proxy Server + +Ensuring that all your main endpoints are only available over HTTPS is an important chore for any application. +If you use Tomcat as a servlet container, then Spring Boot adds Tomcat's own `RemoteIpValve` automatically if it detects some environment settings, allowing you to rely on the `HttpServletRequest` to report whether it is secure or not (even downstream of a proxy server that handles the real SSL termination). +The standard behavior is determined by the presence or absence of certain request headers (`x-forwarded-for` and `x-forwarded-proto`), whose names are conventional, so it should work with most front-end proxies. +You can switch on the valve by adding some entries to `application.properties`, as shown in the following example: + +[configprops,yaml] +---- +server: + tomcat: + remoteip: + remote-ip-header: "x-forwarded-for" + protocol-header: "x-forwarded-proto" +---- + +(The presence of either of those properties switches on the valve. +Alternatively, you can add the `RemoteIpValve` by customizing the `TomcatServletWebServerFactory` using a `WebServerFactoryCustomizer` bean.) + +To configure Spring Security to require a secure channel for all (or some) requests, consider adding your own `SecurityFilterChain` bean that adds the following `HttpSecurity` configuration: + +include-code::MySecurityConfig[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/spring-mvc.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/spring-mvc.adoc similarity index 83% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/spring-mvc.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/spring-mvc.adoc index 898e15d3f1c5..077fbf97035c 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/spring-mvc.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/spring-mvc.adoc @@ -1,5 +1,6 @@ [[howto.spring-mvc]] -== Spring MVC += Spring MVC + Spring Boot has a number of starters that include Spring MVC. Note that some starters include a dependency on Spring MVC rather than include it directly. This section answers common questions about Spring MVC and Spring Boot. @@ -7,10 +8,11 @@ This section answers common questions about Spring MVC and Spring Boot. [[howto.spring-mvc.write-json-rest-service]] -=== Write a JSON REST Service +== Write a JSON REST Service + Any Spring `@RestController` in a Spring Boot application should render JSON response by default as long as Jackson2 is on the classpath, as shown in the following example: -include::code:MyController[] +include-code::MyController[] As long as `MyThing` can be serialized by Jackson2 (true for a normal POJO or Groovy object), then `http://localhost:8080/thing` serves a JSON representation of it by default. Note that, in a browser, you might sometimes see XML responses, because browsers tend to send accept headers that prefer XML. @@ -18,31 +20,32 @@ Note that, in a browser, you might sometimes see XML responses, because browsers [[howto.spring-mvc.write-xml-rest-service]] -=== Write an XML REST Service +== Write an XML REST Service + If you have the Jackson XML extension (`jackson-dataformat-xml`) on the classpath, you can use it to render XML responses. The previous example that we used for JSON would work. To use the Jackson XML renderer, add the following dependency to your project: -[source,xml,indent=0,subs="verbatim"] +[source,xml] ---- - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ---- If Jackson's XML extension is not available and JAXB is available, XML can be rendered with the additional requirement of having `MyThing` annotated as `@XmlRootElement`, as shown in the following example: -include::code:MyThing[] +include-code::MyThing[] You will need to ensure that the JAXB library is part of your project, for example by adding: -[source,xml,indent=0,subs="verbatim"] +[source,xml] ---- - - org.glassfish.jaxb - jaxb-runtime - + + org.glassfish.jaxb + jaxb-runtime + ---- NOTE: To get the server to render XML instead of JSON, you might have to send an `Accept: text/xml` header (or use a browser). @@ -50,7 +53,8 @@ NOTE: To get the server to render XML instead of JSON, you might have to send an [[howto.spring-mvc.customize-jackson-objectmapper]] -=== Customize the Jackson ObjectMapper +== Customize the Jackson ObjectMapper + Spring MVC (client and server side) uses `HttpMessageConverters` to negotiate content conversion in an HTTP exchange. If Jackson is on the classpath, you already get the default converter(s) provided by `Jackson2ObjectMapperBuilder`, an instance of which is auto-configured for you. @@ -65,11 +69,19 @@ Spring Boot also has some features to make it easier to customize this behavior. You can configure the `ObjectMapper` and `XmlMapper` instances by using the environment. Jackson provides an extensive suite of on/off features that can be used to configure various aspects of its processing. -These features are described in six enums (in Jackson) that map onto properties in the environment: +These features are described in several enums (in Jackson) that map onto properties in the environment: |=== | Enum | Property | Values +| `com.fasterxml.jackson.databind.cfg.EnumFeature` +| `spring.jackson.datatype.enum.` +| `true`, `false` + +| `com.fasterxml.jackson.databind.cfg.JsonNodeFeature` +| `spring.jackson.datatype.json-node.` +| `true`, `false` + | `com.fasterxml.jackson.databind.DeserializationFeature` | `spring.jackson.deserialization.` | `true`, `false` @@ -96,7 +108,7 @@ These features are described in six enums (in Jackson) that map onto properties |=== For example, to enable pretty print, set `spring.jackson.serialization.indent_output=true`. -Note that, thanks to the use of <>, the case of `indent_output` does not have to match the case of the corresponding enum constant, which is `INDENT_OUTPUT`. +Note that, thanks to the use of xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.relaxed-binding[relaxed binding], the case of `indent_output` does not have to match the case of the corresponding enum constant, which is `INDENT_OUTPUT`. This environment-based configuration is applied to the auto-configured `Jackson2ObjectMapperBuilder` bean and applies to any mappers created by using the builder, including the auto-configured `ObjectMapper` bean. @@ -113,12 +125,13 @@ If you provide any `@Beans` of type `MappingJackson2HttpMessageConverter`, they Also, a convenience bean of type `HttpMessageConverters` is provided (and is always available if you use the default MVC configuration). It has some useful methods to access the default and user-enhanced message converters. -See the "`<>`" section and the {spring-boot-autoconfigure-module-code}/web/servlet/WebMvcAutoConfiguration.java[`WebMvcAutoConfiguration`] source code for more details. +See the xref:spring-mvc.adoc#howto.spring-mvc.customize-responsebody-rendering[] section and the {code-spring-boot-autoconfigure-src}/web/servlet/WebMvcAutoConfiguration.java[`WebMvcAutoConfiguration`] source code for more details. [[howto.spring-mvc.customize-responsebody-rendering]] -=== Customize the @ResponseBody Rendering +== Customize the @ResponseBody Rendering + Spring uses `HttpMessageConverters` to render `@ResponseBody` (or responses from `@RestController`). You can contribute additional converters by adding beans of the appropriate type in a Spring Boot context. If a bean you add is of a type that would have been included by default anyway (such as `MappingJackson2HttpMessageConverter` for JSON conversions), it replaces the default value. @@ -127,14 +140,15 @@ It has some useful methods to access the default and user-enhanced message conve As in normal MVC usage, any `WebMvcConfigurer` beans that you provide can also contribute converters by overriding the `configureMessageConverters` method. However, unlike with normal MVC, you can supply only additional converters that you need (because Spring Boot uses the same mechanism to contribute its defaults). -Finally, if you opt out of the Spring Boot default MVC configuration by providing your own `@EnableWebMvc` configuration, you can take control completely and do everything manually by using `getMessageConverters` from `WebMvcConfigurationSupport`. +Finally, if you opt out of the default Spring Boot MVC configuration by providing your own `@EnableWebMvc` configuration, you can take control completely and do everything manually by using `getMessageConverters` from `WebMvcConfigurationSupport`. -See the {spring-boot-autoconfigure-module-code}/web/servlet/WebMvcAutoConfiguration.java[`WebMvcAutoConfiguration`] source code for more details. +See the {code-spring-boot-autoconfigure-src}/web/servlet/WebMvcAutoConfiguration.java[`WebMvcAutoConfiguration`] source code for more details. [[howto.spring-mvc.multipart-file-uploads]] -=== Handling Multipart File Uploads +== Handling Multipart File Uploads + Spring Boot embraces the servlet 5 `jakarta.servlet.http.Part` API to support uploading files. By default, Spring Boot configures Spring MVC with a maximum size of 1MB per file and a maximum of 10MB of file data in a single request. You may override these values, the location to which intermediate data is stored (for example, to the `/tmp` directory), and the threshold past which data is flushed to disk by using the properties exposed in the `MultipartProperties` class. @@ -142,23 +156,24 @@ For example, if you want to specify that files be unlimited, set the configprop: The multipart support is helpful when you want to receive multipart encoded file data as a `@RequestParam`-annotated parameter of type `MultipartFile` in a Spring MVC controller handler method. -See the {spring-boot-autoconfigure-module-code}/web/servlet/MultipartAutoConfiguration.java[`MultipartAutoConfiguration`] source for more details. +See the {code-spring-boot-autoconfigure-src}/web/servlet/MultipartAutoConfiguration.java[`MultipartAutoConfiguration`] source for more details. -NOTE: It is recommended to use the container's built-in support for multipart uploads rather than introducing an additional dependency such as Apache Commons File Upload. +NOTE: It is recommended to use the container's built-in support for multipart uploads rather than introduce an additional dependency such as Apache Commons File Upload. [[howto.spring-mvc.switch-off-dispatcherservlet]] -=== Switch Off the Spring MVC DispatcherServlet +== Switch Off the Spring MVC DispatcherServlet + By default, all content is served from the root of your application (`/`). If you would rather map to a different path, you can configure one as follows: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - mvc: - servlet: - path: "/mypath" +spring: + mvc: + servlet: + path: "/mypath" ---- If you have additional servlets you can declare a `@Bean` of type `Servlet` or `ServletRegistrationBean` for each and Spring Boot will register them transparently to the container. @@ -169,14 +184,16 @@ Configuring the `DispatcherServlet` yourself is unusual but if you really need t [[howto.spring-mvc.switch-off-default-configuration]] -=== Switch off the Default MVC Configuration +== Switch Off the Default MVC Configuration + The easiest way to take complete control over MVC configuration is to provide your own `@Configuration` with the `@EnableWebMvc` annotation. Doing so leaves all MVC configuration in your hands. [[howto.spring-mvc.customize-view-resolvers]] -=== Customize ViewResolvers +== Customize ViewResolvers + A `ViewResolver` is a core component of Spring MVC, translating view names in `@Controller` to actual `View` implementations. Note that `ViewResolvers` are mainly used in UI applications, rather than REST-style services (a `View` is not used to render a `@ResponseBody`). There are many implementations of `ViewResolver` to choose from, and Spring on its own is not opinionated about which ones you should use. @@ -219,7 +236,7 @@ If you add your own, you have to be aware of the order and in which position you For more detail, see the following sections: -* {spring-boot-autoconfigure-module-code}/web/servlet/WebMvcAutoConfiguration.java[`WebMvcAutoConfiguration`] -* {spring-boot-autoconfigure-module-code}/thymeleaf/ThymeleafAutoConfiguration.java[`ThymeleafAutoConfiguration`] -* {spring-boot-autoconfigure-module-code}/freemarker/FreeMarkerAutoConfiguration.java[`FreeMarkerAutoConfiguration`] -* {spring-boot-autoconfigure-module-code}/groovy/template/GroovyTemplateAutoConfiguration.java[`GroovyTemplateAutoConfiguration`] +* {code-spring-boot-autoconfigure-src}/web/servlet/WebMvcAutoConfiguration.java[`WebMvcAutoConfiguration`] +* {code-spring-boot-autoconfigure-src}/thymeleaf/ThymeleafAutoConfiguration.java[`ThymeleafAutoConfiguration`] +* {code-spring-boot-autoconfigure-src}/freemarker/FreeMarkerAutoConfiguration.java[`FreeMarkerAutoConfiguration`] +* {code-spring-boot-autoconfigure-src}/groovy/template/GroovyTemplateAutoConfiguration.java[`GroovyTemplateAutoConfiguration`] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/testing.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/testing.adoc new file mode 100644 index 000000000000..0f8ed0e78731 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/testing.adoc @@ -0,0 +1,45 @@ +[[howto.testing]] += Testing + +Spring Boot includes a number of testing utilities and support classes as well as a dedicated starter that provides common test dependencies. +This section answers common questions about testing. + + + +[[howto.testing.with-spring-security]] +== Testing With Spring Security + +Spring Security provides support for running tests as a specific user. +For example, the test in the snippet below will run with an authenticated user that has the `ADMIN` role. + +include-code::MySecurityTests[] + +Spring Security provides comprehensive integration with Spring MVC Test, and this can also be used when testing controllers using the `@WebMvcTest` slice and `MockMvc`. + +For additional details on Spring Security's testing support, see Spring Security's {url-spring-security-docs}/servlet/test/index.html[reference documentation]. + + + + +[[howto.testing.slice-tests]] +== Structure `@Configuration` Classes for Inclusion in Slice Tests + +Slice tests work by restricting Spring Framework's component scanning to a limited set of components based on their type. +For any beans that are not created through component scanning, for example, beans that are created using the `@Bean` annotation, slice tests will not be able to include/exclude them from the application context. +Consider this example: + +include-code::MyConfiguration[] + +For a `@WebMvcTest` for an application with the above `@Configuration` class, you might expect to have the `SecurityFilterChain` bean in the application context so that you can test if your controller endpoints are secured properly. +However, `MyConfiguration` is not picked up by @WebMvcTest's component scanning filter because it doesn't match any of the types specified by the filter. +You can include the configuration explicitly by annotating the test class with `@Import(MyConfiguration.class)`. +This will load all the beans in `MyConfiguration` including the `BasicDataSource` bean which isn't required when testing the web tier. +Splitting the configuration class into two will enable importing just the security configuration. + +include-code::MySecurityConfiguration[] + +include-code::MyDatasourceConfiguration[] + +Having a single configuration class can be inefficient when beans from a certain domain need to be included in slice tests. +Instead, structuring the application's configuration as multiple granular classes with beans for a specific domain can enable importing them only for specific slice tests. + diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/webserver.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/webserver.adoc new file mode 100644 index 000000000000..969fa927fa90 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/webserver.adoc @@ -0,0 +1,548 @@ +[[howto.webserver]] += Embedded Web Servers + +Each Spring Boot web application includes an embedded web server. +This feature leads to a number of how-to questions, including how to change the embedded server and how to configure the embedded server. +This section answers those questions. + + + +[[howto.webserver.use-another]] +== Use Another Web Server + +Many Spring Boot starters include default embedded containers. + +* For servlet stack applications, the `spring-boot-starter-web` includes Tomcat by including `spring-boot-starter-tomcat`, but you can use `spring-boot-starter-jetty` or `spring-boot-starter-undertow` instead. +* For reactive stack applications, the `spring-boot-starter-webflux` includes Reactor Netty by including `spring-boot-starter-reactor-netty`, but you can use `spring-boot-starter-tomcat`, `spring-boot-starter-jetty`, or `spring-boot-starter-undertow` instead. + +When switching to a different HTTP server, you need to swap the default dependencies for those that you need instead. +To help with this process, Spring Boot provides a separate starter for each of the supported HTTP servers. + +The following Maven example shows how to exclude Tomcat and include Jetty for Spring MVC: + +[source,xml] +---- + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + + org.springframework.boot + spring-boot-starter-jetty + +---- + +The following Gradle example configures the necessary dependencies and a {url-gradle-docs}/resolution_rules.html#sec:module_replacement[module replacement] to use Undertow in place of Reactor Netty for Spring WebFlux: + +[source,gradle] +---- +dependencies { + implementation "org.springframework.boot:spring-boot-starter-undertow" + implementation "org.springframework.boot:spring-boot-starter-webflux" + modules { + module("org.springframework.boot:spring-boot-starter-reactor-netty") { + replacedBy("org.springframework.boot:spring-boot-starter-undertow", "Use Undertow instead of Reactor Netty") + } + } +} +---- + +NOTE: `spring-boot-starter-reactor-netty` is required to use the `WebClient` class, so you may need to keep a dependency on Netty even when you need to include a different HTTP server. + + + +[[howto.webserver.disable]] +== Disabling the Web Server + +If your classpath contains the necessary bits to start a web server, Spring Boot will automatically start it. +To disable this behavior configure the `WebApplicationType` in your `application.properties`, as shown in the following example: + +[configprops,yaml] +---- +spring: + main: + web-application-type: "none" +---- + + + +[[howto.webserver.change-port]] +== Change the HTTP Port + +In a standalone application, the main HTTP port defaults to `8080` but can be set with configprop:server.port[] (for example, in `application.properties` or as a System property). +Thanks to relaxed binding of `Environment` values, you can also use configprop:server.port[format=envvar] (for example, as an OS environment variable). + +To switch off the HTTP endpoints completely but still create a `WebApplicationContext`, use `server.port=-1` (doing so is sometimes useful for testing). + +For more details, see xref:reference:web/servlet.adoc#web.servlet.embedded-container.customizing[Customizing Embedded Servlet Containers] in the '`Spring Boot Features`' section, or the xref:api:java/org/springframework/boot/autoconfigure/web/ServerProperties.html[`ServerProperties`] class. + + + +[[howto.webserver.use-random-port]] +== Use a Random Unassigned HTTP Port + +To scan for a free port (using OS natives to prevent clashes) use `server.port=0`. + + + +[[howto.webserver.discover-port]] +== Discover the HTTP Port at Runtime + +You can access the port the server is running on from log output or from the `WebServerApplicationContext` through its `WebServer`. +The best way to get that and be sure it has been initialized is to add a `@Bean` of type `ApplicationListener` and pull the container out of the event when it is published. + +Tests that use `@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)` can also inject the actual port into a field by using the `@LocalServerPort` annotation, as shown in the following example: + +include-code::MyWebIntegrationTests[] + +[NOTE] +==== +`@LocalServerPort` is a meta-annotation for `@Value("${local.server.port}")`. +Do not try to inject the port in a regular application. +As we just saw, the value is set only after the container has been initialized. +Contrary to a test, application code callbacks are processed early (before the value is actually available). +==== + + + +[[howto.webserver.enable-response-compression]] +== Enable HTTP Response Compression + +HTTP response compression is supported by Jetty, Tomcat, Reactor Netty, and Undertow. +It can be enabled in `application.properties`, as follows: + +[configprops,yaml] +---- +server: + compression: + enabled: true +---- + +By default, responses must be at least 2048 bytes in length for compression to be performed. +You can configure this behavior by setting the configprop:server.compression.min-response-size[] property. + +By default, responses are compressed only if their content type is one of the following: + +* `text/html` +* `text/xml` +* `text/plain` +* `text/css` +* `text/javascript` +* `application/javascript` +* `application/json` +* `application/xml` + +You can configure this behavior by setting the configprop:server.compression.mime-types[] property. + + + +[[howto.webserver.configure-ssl]] +== Configure SSL + +SSL can be configured declaratively by setting the various `+server.ssl.*+` properties, typically in `application.properties` or `application.yaml`. +See xref:api:java/org/springframework/boot/web/server/Ssl.html[`Ssl`] for details of all of the supported properties. + +The following example shows setting SSL properties using a Java KeyStore file: + +[configprops,yaml] +---- +server: + port: 8443 + ssl: + key-store: "classpath:keystore.jks" + key-store-password: "secret" + key-password: "another-secret" +---- + +Using configuration such as the preceding example means the application no longer supports a plain HTTP connector at port 8080. +Spring Boot does not support the configuration of both an HTTP connector and an HTTPS connector through `application.properties`. +If you want to have both, you need to configure one of them programmatically. +We recommend using `application.properties` to configure HTTPS, as the HTTP connector is the easier of the two to configure programmatically. + + + +[[howto.webserver.configure-ssl.pem-files]] +=== Using PEM-encoded files + +You can use PEM-encoded files instead of Java KeyStore files. +You should use PKCS#8 key files wherever possible. +PEM-encoded PKCS#8 key files start with a `-----BEGIN PRIVATE KEY-----` or `-----BEGIN ENCRYPTED PRIVATE KEY-----` header. + +If you have files in other formats, e.g., PKCS#1 (`-----BEGIN RSA PRIVATE KEY-----`) or SEC 1 (`-----BEGIN EC PRIVATE KEY-----`), you can convert them to PKCS#8 using OpenSSL: + +[source,shell,subs="verbatim,attributes"] +---- +openssl pkcs8 -topk8 -nocrypt -in -out +---- + +The following example shows setting SSL properties using PEM-encoded certificate and private key files: + +[configprops,yaml] +---- +server: + port: 8443 + ssl: + certificate: "classpath:my-cert.crt" + certificate-private-key: "classpath:my-cert.key" + trust-certificate: "classpath:ca-cert.crt" +---- + +[[howto.webserver.configure-ssl.bundles]] +=== Using SSL Bundles + +Alternatively, the SSL trust material can be configured in an xref:reference:features/ssl.adoc[SSL bundle] and applied to the web server as shown in this example: + +[configprops,yaml] +---- +server: + port: 8443 + ssl: + bundle: "example" +---- + +NOTE: The `server.ssl.bundle` property can not be combined with the discrete Java KeyStore or PEM property options under `server.ssl`. + +[[howto.webserver.configure-ssl.sni]] +=== Configure Server Name Indication + +Tomcat, Netty, and Undertow can be configured to use unique SSL trust material for individual host names to support Server Name Indication (SNI). +SNI configuration is not supported with Jetty, but Jetty can https://eclipse.dev/jetty/documentation/jetty-12/operations-guide/index.html#og-protocols-ssl-sni[automatically set up SNI] if multiple certificates are provided to it. + +Assuming xref:reference:features/ssl.adoc[SSL bundles] named `web`, `web-alt1`, and `web-alt2` have been configured, the following configuration can be used to assign each bundle to a host name served by the embedded web server: + +[configprops,yaml] +---- +server: + port: 8443 + ssl: + bundle: "web" + server-name-bundles: + - server-name: "alt1.example.com" + bundle: "web-alt1" + - server-name: "alt2.example.com" + bundle: "web-alt2" +---- + +The bundle specified with `server.ssl.bundle` will be used for the default host, and for any client that does support SNI. +This default bundle must be configured if any `server.ssl.server-name-bundles` are configured. + + + +[[howto.webserver.configure-http2]] +== Configure HTTP/2 + +You can enable HTTP/2 support in your Spring Boot application with the configprop:server.http2.enabled[] configuration property. +Both `h2` (HTTP/2 over TLS) and `h2c` (HTTP/2 over TCP) are supported. +To use `h2`, SSL must also be enabled. +When SSL is not enabled, `h2c` will be used. +You may, for example, want to use `h2c` when your application is xref:webserver.adoc#howto.webserver.use-behind-a-proxy-server[running behind a proxy server] that is performing TLS termination. + + + +[[howto.webserver.configure-http2.tomcat]] +=== HTTP/2 With Tomcat + +Spring Boot ships by default with Tomcat 10.1.x which supports `h2c` and `h2` out of the box. +Alternatively, you can use `libtcnative` for `h2` support if the library and its dependencies are installed on the host operating system. + +The library directory must be made available, if not already, to the JVM library path. +You can do so with a JVM argument such as `-Djava.library.path=/usr/local/opt/tomcat-native/lib`. +More on this in the {url-tomcat-docs}/apr.html[official Tomcat documentation]. + + + +[[howto.webserver.configure-http2.jetty]] +=== HTTP/2 With Jetty + +For HTTP/2 support, Jetty requires the additional `org.eclipse.jetty.http2:jetty-http2-server` dependency. +To use `h2c` no other dependencies are required. +To use `h2`, you also need to choose one of the following dependencies, depending on your deployment: + +* `org.eclipse.jetty:jetty-alpn-java-server` to use the JDK built-in support +* `org.eclipse.jetty:jetty-alpn-conscrypt-server` and the https://www.conscrypt.org/[Conscrypt library] + + + +[[howto.webserver.configure-http2.netty]] +=== HTTP/2 With Reactor Netty + +The `spring-boot-webflux-starter` is using by default Reactor Netty as a server. +Reactor Netty supports `h2c` and `h2` out of the box. +For optimal runtime performance, this server also supports `h2` with native libraries. +To enable that, your application needs to have an additional dependency. + +Spring Boot manages the version for the `io.netty:netty-tcnative-boringssl-static` "uber jar", containing native libraries for all platforms. +Developers can choose to import only the required dependencies using a classifier (see https://netty.io/wiki/forked-tomcat-native.html[the Netty official documentation]). + + + +[[howto.webserver.configure-http2.undertow]] +=== HTTP/2 With Undertow + +Undertow supports `h2c` and `h2` out of the box. + + + +[[howto.webserver.configure]] +== Configure the Web Server + +Generally, you should first consider using one of the many available configuration keys and customize your web server by adding new entries in your `application.properties` or `application.yaml` file. +See xref:properties-and-configuration.adoc#howto.properties-and-configuration.discover-build-in-options-for-external-properties[]). +The `server.{asterisk}` namespace is quite useful here, and it includes namespaces like `server.tomcat.{asterisk}`, `server.jetty.{asterisk}` and others, for server-specific features. +See the list of xref:appendix:application-properties/index.adoc[]. + +The previous sections covered already many common use cases, such as compression, SSL or HTTP/2. +However, if a configuration key does not exist for your use case, you should then look at xref:api:java/org/springframework/boot/web/server/WebServerFactoryCustomizer.html[`WebServerFactoryCustomizer`]. +You can declare such a component and get access to the server factory relevant to your choice: you should select the variant for the chosen Server (Tomcat, Jetty, Reactor Netty, Undertow) and the chosen web stack (servlet or reactive). + +The example below is for Tomcat with the `spring-boot-starter-web` (servlet stack): + +include-code::MyTomcatWebServerCustomizer[] + +NOTE: Spring Boot uses that infrastructure internally to auto-configure the server. +Auto-configured `WebServerFactoryCustomizer` beans have an order of `0` and will be processed before any user-defined customizers, unless it has an explicit order that states otherwise. + +Once you have got access to a `WebServerFactory` using the customizer, you can use it to configure specific parts, like connectors, server resources, or the server itself - all using server-specific APIs. + +In addition Spring Boot provides: + +[[howto-configure-webserver-customizers]] +[cols="1,2,2", options="header"] +|=== +| Server | Servlet stack | Reactive stack + +| Tomcat +| `TomcatServletWebServerFactory` +| `TomcatReactiveWebServerFactory` + +| Jetty +| `JettyServletWebServerFactory` +| `JettyReactiveWebServerFactory` + +| Undertow +| `UndertowServletWebServerFactory` +| `UndertowReactiveWebServerFactory` + +| Reactor +| N/A +| `NettyReactiveWebServerFactory` +|=== + +As a last resort, you can also declare your own `WebServerFactory` bean, which will override the one provided by Spring Boot. +When you do so, auto-configured customizers are still applied on your custom factory, so use that option carefully. + + + +[[howto.webserver.add-servlet-filter-listener]] +== Add a Servlet, Filter, or Listener to an Application + +In a servlet stack application, that is with the `spring-boot-starter-web`, there are two ways to add `Servlet`, `Filter`, `ServletContextListener`, and the other listeners supported by the Servlet API to your application: + +* xref:webserver.adoc#howto.webserver.add-servlet-filter-listener.spring-bean[] +* xref:webserver.adoc#howto.webserver.add-servlet-filter-listener.using-scanning[] + + + +[[howto.webserver.add-servlet-filter-listener.spring-bean]] +=== Add a Servlet, Filter, or Listener by Using a Spring Bean + +To add a `Servlet`, `Filter`, or servlet `*Listener` by using a Spring bean, you must provide a `@Bean` definition for it. +Doing so can be very useful when you want to inject configuration or dependencies. +However, you must be very careful that they do not cause eager initialization of too many other beans, because they have to be installed in the container very early in the application lifecycle. +(For example, it is not a good idea to have them depend on your `DataSource` or JPA configuration.) +You can work around such restrictions by initializing the beans lazily when first used instead of on initialization. + +In the case of filters and servlets, you can also add mappings and init parameters by adding a `FilterRegistrationBean` or a `ServletRegistrationBean` instead of or in addition to the underlying component. + +[NOTE] +==== +If no `dispatcherType` is specified on a filter registration, `REQUEST` is used. +This aligns with the servlet specification's default dispatcher type. +==== + +Like any other Spring bean, you can define the order of servlet filter beans; please make sure to check the xref:reference:web/servlet.adoc#web.servlet.embedded-container.servlets-filters-listeners.beans[] section. + + + +[[howto.webserver.add-servlet-filter-listener.spring-bean.disable]] +==== Disable Registration of a Servlet or Filter + +As xref:webserver.adoc#howto.webserver.add-servlet-filter-listener.spring-bean[described earlier], any `Servlet` or `Filter` beans are registered with the servlet container automatically. +To disable registration of a particular `Filter` or `Servlet` bean, create a registration bean for it and mark it as disabled, as shown in the following example: + +include-code::MyFilterConfiguration[] + + + +[[howto.webserver.add-servlet-filter-listener.using-scanning]] +=== Add Servlets, Filters, and Listeners by Using Classpath Scanning + +`@WebServlet`, `@WebFilter`, and `@WebListener` annotated classes can be automatically registered with an embedded servlet container by annotating a `@Configuration` class with `@ServletComponentScan` and specifying the package(s) containing the components that you want to register. +By default, `@ServletComponentScan` scans from the package of the annotated class. + + + +[[howto.webserver.configure-access-logs]] +== Configure Access Logging + +Access logs can be configured for Tomcat, Undertow, and Jetty through their respective namespaces. + +For instance, the following settings log access on Tomcat with a {url-tomcat-docs}/config/valve.html#Access_Logging[custom pattern]. + +[configprops,yaml] +---- +server: + tomcat: + basedir: "my-tomcat" + accesslog: + enabled: true + pattern: "%t %a %r %s (%D microseconds)" +---- + +NOTE: The default location for logs is a `logs` directory relative to the Tomcat base directory. +By default, the `logs` directory is a temporary directory, so you may want to fix Tomcat's base directory or use an absolute path for the logs. +In the preceding example, the logs are available in `my-tomcat/logs` relative to the working directory of the application. + +Access logging for Undertow can be configured in a similar fashion, as shown in the following example: + +[configprops,yaml] +---- +server: + undertow: + accesslog: + enabled: true + pattern: "%t %a %r %s (%D milliseconds)" + options: + server: + record-request-start-time: true +---- + +Note that, in addition to enabling access logging and configuring its pattern, recording request start times has also been enabled. +This is required when including the response time (`%D`) in the access log pattern. +Logs are stored in a `logs` directory relative to the working directory of the application. +You can customize this location by setting the configprop:server.undertow.accesslog.dir[] property. + +Finally, access logging for Jetty can also be configured as follows: + +[configprops,yaml] +---- +server: + jetty: + accesslog: + enabled: true + filename: "/var/log/jetty-access.log" +---- + +By default, logs are redirected to `System.err`. +For more details, see the Jetty documentation. + + + +[[howto.webserver.use-behind-a-proxy-server]] +== Running Behind a Front-end Proxy Server + +If your application is running behind a proxy, a load-balancer or in the cloud, the request information (like the host, port, scheme...) might change along the way. +Your application may be running on `10.10.10.10:8080`, but HTTP clients should only see `example.org`. + +https://tools.ietf.org/html/rfc7239[RFC7239 "Forwarded Headers"] defines the `Forwarded` HTTP header; proxies can use this header to provide information about the original request. +You can configure your application to read those headers and automatically use that information when creating links and sending them to clients in HTTP 302 responses, JSON documents or HTML pages. +There are also non-standard headers, like `X-Forwarded-Host`, `X-Forwarded-Port`, `X-Forwarded-Proto`, `X-Forwarded-Ssl`, and `X-Forwarded-Prefix`. + +If the proxy adds the commonly used `X-Forwarded-For` and `X-Forwarded-Proto` headers, setting `server.forward-headers-strategy` to `NATIVE` is enough to support those. +With this option, the Web servers themselves natively support this feature; you can check their specific documentation to learn about specific behavior. + +If this is not enough, Spring Framework provides a {url-spring-framework-docs}/web/webmvc/filters.html#filters-forwarded-headers[ForwardedHeaderFilter] for the servlet stack and a {url-spring-framework-docs}/web/webflux/reactive-spring.html#webflux-forwarded-headers[ForwardedHeaderTransformer] for the reactive stack. +You can use them in your application by setting configprop:server.forward-headers-strategy[] to `FRAMEWORK`. + +TIP: If you are using Tomcat and terminating SSL at the proxy, configprop:server.tomcat.redirect-context-root[] should be set to `false`. +This allows the `X-Forwarded-Proto` header to be honored before any redirects are performed. + +NOTE: If your application runs xref:api:java/org/springframework/boot/cloud/CloudPlatform.html#enum-constant-summary[in a supported Cloud Platform], the configprop:server.forward-headers-strategy[] property defaults to `NATIVE`. +In all other instances, it defaults to `NONE`. + + + +[[howto.webserver.use-behind-a-proxy-server.tomcat]] +=== Customize Tomcat's Proxy Configuration + +If you use Tomcat, you can additionally configure the names of the headers used to carry "`forwarded`" information, as shown in the following example: + +[configprops,yaml] +---- +server: + tomcat: + remoteip: + remote-ip-header: "x-your-remote-ip-header" + protocol-header: "x-your-protocol-header" +---- + +Tomcat is also configured with a regular expression that matches internal proxies that are to be trusted. +See the xref:appendix:application-properties/index.adoc#application-properties.server.server.tomcat.remoteip.internal-proxies[configprop:server.tomcat.remoteip.internal-proxies[] entry in the appendix] for its default value. +You can customize the valve's configuration by adding an entry to `application.properties`, as shown in the following example: + +[configprops,yaml] +---- +server: + tomcat: + remoteip: + internal-proxies: "192\\.168\\.\\d{1,3}\\.\\d{1,3}" +---- + +NOTE: You can trust all proxies by setting the `internal-proxies` to empty (but do not do so in production). + +You can take complete control of the configuration of Tomcat's `RemoteIpValve` by switching the automatic one off (to do so, set `server.forward-headers-strategy=NONE`) and adding a new valve instance using a `WebServerFactoryCustomizer` bean. + + + +[[howto.webserver.enable-multiple-connectors-in-tomcat]] +== Enable Multiple Connectors with Tomcat + +You can add an `org.apache.catalina.connector.Connector` to the `TomcatServletWebServerFactory`, which can allow multiple connectors, including HTTP and HTTPS connectors, as shown in the following example: + +include-code::MyTomcatConfiguration[] + + + +[[howto.webserver.enable-tomcat-mbean-registry]] +== Enable Tomcat's MBean Registry + +Embedded Tomcat's MBean registry is disabled by default. +This minimizes Tomcat's memory footprint. +If you want to use Tomcat's MBeans, for example so that they can be used by Micrometer to expose metrics, you must use the configprop:server.tomcat.mbeanregistry.enabled[] property to do so, as shown in the following example: + +[configprops,yaml] +---- +server: + tomcat: + mbeanregistry: + enabled: true +---- + + + +[[howto.webserver.enable-multiple-listeners-in-undertow]] +== Enable Multiple Listeners with Undertow + +Add an `UndertowBuilderCustomizer` to the `UndertowServletWebServerFactory` and add a listener to the `Builder`, as shown in the following example: + +include-code::MyUndertowConfiguration[] + + + +[[howto.webserver.create-websocket-endpoints-using-serverendpoint]] +== Create WebSocket Endpoints Using @ServerEndpoint + +If you want to use `@ServerEndpoint` in a Spring Boot application that used an embedded container, you must declare a single `ServerEndpointExporter` `@Bean`, as shown in the following example: + +include-code::MyWebSocketConfiguration[] + +The bean shown in the preceding example registers any `@ServerEndpoint` annotated beans with the underlying WebSocket container. +When deployed to a standalone servlet container, this role is performed by a servlet container initializer, and the `ServerEndpointExporter` bean is not required. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/partials/nav-how-to.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/partials/nav-how-to.adoc new file mode 100644 index 000000000000..d1dabaeb7316 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/partials/nav-how-to.adoc @@ -0,0 +1,29 @@ +* xref:how-to:index.adoc[] + +** xref:how-to:application.adoc[] +** xref:how-to:properties-and-configuration.adoc[] +** xref:how-to:webserver.adoc[] +** xref:how-to:spring-mvc.adoc[] +** xref:how-to:jersey.adoc[] +** xref:how-to:http-clients.adoc[] +** xref:how-to:logging.adoc[] +** xref:how-to:data-access.adoc[] +** xref:how-to:data-initialization.adoc[] +** xref:how-to:nosql.adoc[] +** xref:how-to:messaging.adoc[] +** xref:how-to:batch.adoc[] +** xref:how-to:actuator.adoc[] +** xref:how-to:security.adoc[] +** xref:how-to:hotswapping.adoc[] +** xref:how-to:testing.adoc[] +** xref:how-to:build.adoc[] +** xref:how-to:aot.adoc[] +** xref:how-to:native-image/index.adoc[] +*** xref:how-to:native-image/developing-your-first-application.adoc[] +*** xref:how-to:native-image/testing-native-applications.adoc[] +** xref:how-to:class-data-sharing.adoc[] +** xref:how-to:deployment/index.adoc[] +*** xref:how-to:deployment/traditional-deployment.adoc[] +*** xref:how-to:deployment/cloud.adoc[] +*** xref:how-to:deployment/installing.adoc[] +** xref:how-to:docker-compose.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/auditing.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/auditing.adoc similarity index 97% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/auditing.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/auditing.adoc index bac2395df008..7c366cb29bf7 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/auditing.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/auditing.adoc @@ -1,5 +1,6 @@ [[actuator.auditing]] -== Auditing += Auditing + Once Spring Security is in play, Spring Boot Actuator has a flexible audit framework that publishes events (by default, "`authentication success`", "`failure`" and "`access denied`" exceptions). This feature can be very useful for reporting and for implementing a lock-out policy based on authentication failures. @@ -11,7 +12,8 @@ For production environments, consider creating your own alternative `AuditEventR [[actuator.auditing.custom]] -=== Custom Auditing +== Custom Auditing + To customize published security events, you can provide your own implementations of `AbstractAuthenticationAuditListener` and `AbstractAuthorizationAuditListener`. You can also use the audit services for your own business events. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/cloud-foundry.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/cloud-foundry.adoc similarity index 80% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/cloud-foundry.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/cloud-foundry.adoc index 97e81386fd68..a5e940dfe7a5 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/cloud-foundry.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/cloud-foundry.adoc @@ -1,5 +1,6 @@ [[actuator.cloud-foundry]] -== Cloud Foundry Support += Cloud Foundry Support + Spring Boot's actuator module includes additional support that is activated when you deploy to a compatible Cloud Foundry instance. The `/cloudfoundryapplication` path provides an alternative secured route to all `@Endpoint` beans. @@ -12,34 +13,37 @@ To use the endpoint, you must pass a valid UAA token with the request. [[actuator.cloud-foundry.disable]] -=== Disabling Extended Cloud Foundry Actuator Support +== Disabling Extended Cloud Foundry Actuator Support + If you want to fully disable the `/cloudfoundryapplication` endpoints, you can add the following setting to your `application.properties` file: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - management: - cloudfoundry: - enabled: false +management: + cloudfoundry: + enabled: false ---- [[actuator.cloud-foundry.ssl]] -=== Cloud Foundry Self-signed Certificates +== Cloud Foundry Self-signed Certificates + By default, the security verification for `/cloudfoundryapplication` endpoints makes SSL calls to various Cloud Foundry services. If your Cloud Foundry UAA or Cloud Controller services use self-signed certificates, you need to set the following property: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - management: - cloudfoundry: - skip-ssl-validation: true +management: + cloudfoundry: + skip-ssl-validation: true ---- [[actuator.cloud-foundry.custom-context-path]] -=== Custom Context Path +== Custom Context Path + If the server's context-path has been configured to anything other than `/`, the Cloud Foundry endpoints are not available at the root of the application. For example, if `server.servlet.context-path=/app`, Cloud Foundry endpoints are available at `/app/cloudfoundryapplication/*`. @@ -47,8 +51,8 @@ If you expect the Cloud Foundry endpoints to always be available at `/cloudfound The configuration differs, depending on the web server in use. For Tomcat, you can add the following configuration: -include::code:MyCloudFoundryConfiguration[] +include-code::MyCloudFoundryConfiguration[] If you're using a Webflux based application, you can use the following configuration: -include::code:MyReactiveCloudFoundryConfiguration[] +include-code::MyReactiveCloudFoundryConfiguration[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/enabling.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/enabling.adoc new file mode 100644 index 000000000000..d817a326efbc --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/enabling.adoc @@ -0,0 +1,32 @@ +[[actuator.enabling]] += Enabling Production-ready Features + +The {code-spring-boot}/spring-boot-project/spring-boot-actuator[`spring-boot-actuator`] module provides all of Spring Boot's production-ready features. +The recommended way to enable the features is to add a dependency on the `spring-boot-starter-actuator` starter. + +.Definition of Actuator +**** +An actuator is a manufacturing term that refers to a mechanical device for moving or controlling something. +Actuators can generate a large amount of motion from a small change. +**** + +To add the actuator to a Maven-based project, add the following starter dependency: + +[source,xml] +---- + + + org.springframework.boot + spring-boot-starter-actuator + + +---- + +For Gradle, use the following declaration: + +[source,gradle] +---- +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-actuator' +} +---- diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/endpoints.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/endpoints.adoc new file mode 100644 index 000000000000..4ab350b0cff4 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/endpoints.adoc @@ -0,0 +1,1315 @@ +[[actuator.endpoints]] += Endpoints + +Actuator endpoints let you monitor and interact with your application. +Spring Boot includes a number of built-in endpoints and lets you add your own. +For example, the `health` endpoint provides basic application health information. + +You can xref:actuator/endpoints.adoc#actuator.endpoints.enabling[enable or disable] each individual endpoint and xref:actuator/endpoints.adoc#actuator.endpoints.exposing[expose them (make them remotely accessible) over HTTP or JMX]. +An endpoint is considered to be available when it is both enabled and exposed. +The built-in endpoints are auto-configured only when they are available. +Most applications choose exposure over HTTP, where the ID of the endpoint and a prefix of `/actuator` is mapped to a URL. +For example, by default, the `health` endpoint is mapped to `/actuator/health`. + +TIP: To learn more about the Actuator's endpoints and their request and response formats, see the xref:api:rest/actuator/index.adoc[API documentation]. + +The following technology-agnostic endpoints are available: + +[cols="2,5"] +|=== +| ID | Description + +| `auditevents` +| Exposes audit events information for the current application. + Requires an `AuditEventRepository` bean. + +| `beans` +| Displays a complete list of all the Spring beans in your application. + +| `caches` +| Exposes available caches. + +| `conditions` +| Shows the conditions that were evaluated on configuration and auto-configuration classes and the reasons why they did or did not match. + +| `configprops` +| Displays a collated list of all `@ConfigurationProperties`. +Subject to xref:actuator/endpoints.adoc#actuator.endpoints.sanitization[sanitization]. + +| `env` +| Exposes properties from Spring's `ConfigurableEnvironment`. +Subject to xref:actuator/endpoints.adoc#actuator.endpoints.sanitization[sanitization]. + +| `flyway` +| Shows any Flyway database migrations that have been applied. + Requires one or more `Flyway` beans. + +| `health` +| Shows application health information. + +| `httpexchanges` +| Displays HTTP exchange information (by default, the last 100 HTTP request-response exchanges). + Requires an `HttpExchangeRepository` bean. + +| `info` +| Displays arbitrary application info. + +| `integrationgraph` +| Shows the Spring Integration graph. + Requires a dependency on `spring-integration-core`. + +| `loggers` +| Shows and modifies the configuration of loggers in the application. + +| `liquibase` +| Shows any Liquibase database migrations that have been applied. + Requires one or more `Liquibase` beans. + +| `metrics` +| Shows "`metrics`" information for the current application. + +| `mappings` +| Displays a collated list of all `@RequestMapping` paths. + +|`quartz` +|Shows information about Quartz Scheduler jobs. +Subject to xref:actuator/endpoints.adoc#actuator.endpoints.sanitization[sanitization]. + +| `scheduledtasks` +| Displays the scheduled tasks in your application. + +| `sessions` +| Allows retrieval and deletion of user sessions from a Spring Session-backed session store. + Requires a servlet-based web application that uses Spring Session. + +| `shutdown` +| Lets the application be gracefully shutdown. + Only works when using jar packaging. + Disabled by default. + +| `startup` +| Shows the xref:features/spring-application.adoc#features.spring-application.startup-tracking[startup steps data] collected by the `ApplicationStartup`. + Requires the `SpringApplication` to be configured with a `BufferingApplicationStartup`. + +| `threaddump` +| Performs a thread dump. +|=== + +If your application is a web application (Spring MVC, Spring WebFlux, or Jersey), you can use the following additional endpoints: + +[cols="2,5"] +|=== +| ID | Description + +| `heapdump` +| Returns a heap dump file. + On a HotSpot JVM, an `HPROF`-format file is returned. + On an OpenJ9 JVM, a `PHD`-format file is returned. + +| `logfile` +| Returns the contents of the logfile (if the `logging.file.name` or the `logging.file.path` property has been set). + Supports the use of the HTTP `Range` header to retrieve part of the log file's content. + +| `prometheus` +| Exposes metrics in a format that can be scraped by a Prometheus server. + Requires a dependency on `micrometer-registry-prometheus`. +|=== + + + +[[actuator.endpoints.enabling]] +== Enabling Endpoints + +By default, all endpoints except for `shutdown` are enabled. +To configure the enablement of an endpoint, use its `management.endpoint..enabled` property. +The following example enables the `shutdown` endpoint: + +[configprops,yaml] +---- +management: + endpoint: + shutdown: + enabled: true +---- + +If you prefer endpoint enablement to be opt-in rather than opt-out, set the configprop:management.endpoints.enabled-by-default[] property to `false` and use individual endpoint `enabled` properties to opt back in. +The following example enables the `info` endpoint and disables all other endpoints: + +[configprops,yaml] +---- +management: + endpoints: + enabled-by-default: false + endpoint: + info: + enabled: true +---- + +NOTE: Disabled endpoints are removed entirely from the application context. +If you want to change only the technologies over which an endpoint is exposed, use the xref:actuator/endpoints.adoc#actuator.endpoints.exposing[`include` and `exclude` properties] instead. + + + +[[actuator.endpoints.exposing]] +== Exposing Endpoints + +By default, only the health endpoint is exposed over HTTP and JMX. +Since Endpoints may contain sensitive information, you should carefully consider when to expose them. + +To change which endpoints are exposed, use the following technology-specific `include` and `exclude` properties: + +[cols="3,1"] +|=== +| Property | Default + +| configprop:management.endpoints.jmx.exposure.exclude[] +| + +| configprop:management.endpoints.jmx.exposure.include[] +| `health` + +| configprop:management.endpoints.web.exposure.exclude[] +| + +| configprop:management.endpoints.web.exposure.include[] +| `health` +|=== + +The `include` property lists the IDs of the endpoints that are exposed. +The `exclude` property lists the IDs of the endpoints that should not be exposed. +The `exclude` property takes precedence over the `include` property. +You can configure both the `include` and the `exclude` properties with a list of endpoint IDs. + +For example, to only expose the `health` and `info` endpoints over JMX, use the following property: + +[configprops,yaml] +---- +management: + endpoints: + jmx: + exposure: + include: "health,info" +---- + +`*` can be used to select all endpoints. +For example, to expose everything over HTTP except the `env` and `beans` endpoints, use the following properties: + +[configprops,yaml] +---- +management: + endpoints: + web: + exposure: + include: "*" + exclude: "env,beans" +---- + +NOTE: `*` has a special meaning in YAML, so be sure to add quotation marks if you want to include (or exclude) all endpoints. + +NOTE: If your application is exposed publicly, we strongly recommend that you also xref:actuator/endpoints.adoc#actuator.endpoints.security[secure your endpoints]. + +TIP: If you want to implement your own strategy for when endpoints are exposed, you can register an `EndpointFilter` bean. + + + +[[actuator.endpoints.security]] +== Security + +For security purposes, only the `/health` endpoint is exposed over HTTP by default. +You can use the configprop:management.endpoints.web.exposure.include[] property to configure the endpoints that are exposed. + +NOTE: Before setting the `management.endpoints.web.exposure.include`, ensure that the exposed actuators do not contain sensitive information, are secured by placing them behind a firewall, or are secured by something like Spring Security. + +If Spring Security is on the classpath and no other `SecurityFilterChain` bean is present, all actuators other than `/health` are secured by Spring Boot auto-configuration. +If you define a custom `SecurityFilterChain` bean, Spring Boot auto-configuration backs off and lets you fully control the actuator access rules. + +If you wish to configure custom security for HTTP endpoints (for example, to allow only users with a certain role to access them), Spring Boot provides some convenient `RequestMatcher` objects that you can use in combination with Spring Security. + +A typical Spring Security configuration might look something like the following example: + +include-code::typical/MySecurityConfiguration[] + +The preceding example uses `EndpointRequest.toAnyEndpoint()` to match a request to any endpoint and then ensures that all have the `ENDPOINT_ADMIN` role. +Several other matcher methods are also available on `EndpointRequest`. +See the xref:api:rest/actuator/index.adoc[API documentation] for details. + +If you deploy applications behind a firewall, you may prefer that all your actuator endpoints can be accessed without requiring authentication. +You can do so by changing the configprop:management.endpoints.web.exposure.include[] property, as follows: + +[configprops,yaml] +---- +management: + endpoints: + web: + exposure: + include: "*" +---- + +Additionally, if Spring Security is present, you would need to add custom security configuration that allows unauthenticated access to the endpoints, as the following example shows: + +include-code::exposeall/MySecurityConfiguration[] + +NOTE: In both of the preceding examples, the configuration applies only to the actuator endpoints. +Since Spring Boot's security configuration backs off completely in the presence of any `SecurityFilterChain` bean, you need to configure an additional `SecurityFilterChain` bean with rules that apply to the rest of the application. + + + +[[actuator.endpoints.security.csrf]] +=== Cross Site Request Forgery Protection + +Since Spring Boot relies on Spring Security's defaults, CSRF protection is turned on by default. +This means that the actuator endpoints that require a `POST` (shutdown and loggers endpoints), a `PUT`, or a `DELETE` get a 403 (forbidden) error when the default security configuration is in use. + +NOTE: We recommend disabling CSRF protection completely only if you are creating a service that is used by non-browser clients. + +You can find additional information about CSRF protection in the {url-spring-security-docs}/features/exploits/csrf.html[Spring Security Reference Guide]. + + + +[[actuator.endpoints.caching]] +== Configuring Endpoints + +Endpoints automatically cache responses to read operations that do not take any parameters. +To configure the amount of time for which an endpoint caches a response, use its `cache.time-to-live` property. +The following example sets the time-to-live of the `beans` endpoint's cache to 10 seconds: + +[configprops,yaml] +---- +management: + endpoint: + beans: + cache: + time-to-live: "10s" +---- + +NOTE: The `management.endpoint.` prefix uniquely identifies the endpoint that is being configured. + + + +[[actuator.endpoints.sanitization]] +== Sanitize Sensitive Values + +Information returned by the `/env`, `/configprops` and `/quartz` endpoints can be sensitive, so by default values are always fully sanitized (replaced by `+******+`). + +Values can only be viewed in an unsanitized form when: + +- The `show-values` property has been set to something other than `NEVER` +- No custom xref:how-to:actuator.adoc#howto.actuator.customizing-sanitization[`SanitizingFunction`] beans apply + +The `show-values` property can be configured for sanitizable endpoints to one of the following values: + +- `NEVER` - values are always fully sanitized (replaced by `+******+`) +- `ALWAYS` - values are shown to all users (as long as no `SanitizingFunction` bean applies) +- `WHEN_AUTHORIZED` - values are shown only to authorized users (as long as no `SanitizingFunction` bean applies) + +For HTTP endpoints, a user is considered to be authorized if they have authenticated and have the roles configured by the endpoint's roles property. +By default, any authenticated user is authorized. + +For JMX endpoints, all users are always authorized. + +The following example allows all users with the `admin` role to view values from the `/env` endpoint in their original form. +Unauthorized users, or users without the `admin` role, will see only sanitized values. + +[configprops,yaml] +---- +management: + endpoint: + env: + show-values: WHEN_AUTHORIZED + roles: "admin" +---- + +NOTE: This example assumes that no xref:how-to:actuator.adoc#howto.actuator.customizing-sanitization[`SanitizingFunction`] beans have been defined. + + + +[[actuator.endpoints.hypermedia]] +== Hypermedia for Actuator Web Endpoints + +A "`discovery page`" is added with links to all the endpoints. +The "`discovery page`" is available on `/actuator` by default. + +To disable the "`discovery page`", add the following property to your application properties: + +[configprops,yaml] +---- +management: + endpoints: + web: + discovery: + enabled: false +---- + +When a custom management context path is configured, the "`discovery page`" automatically moves from `/actuator` to the root of the management context. +For example, if the management context path is `/management`, the discovery page is available from `/management`. +When the management context path is set to `/`, the discovery page is disabled to prevent the possibility of a clash with other mappings. + + + +[[actuator.endpoints.cors]] +== CORS Support + +https://en.wikipedia.org/wiki/Cross-origin_resource_sharing[Cross-origin resource sharing] (CORS) is a https://www.w3.org/TR/cors/[W3C specification] that lets you specify in a flexible way what kind of cross-domain requests are authorized. +If you use Spring MVC or Spring WebFlux, you can configure Actuator's web endpoints to support such scenarios. + +CORS support is disabled by default and is only enabled once you have set the configprop:management.endpoints.web.cors.allowed-origins[] property. +The following configuration permits `GET` and `POST` calls from the `example.com` domain: + +[configprops,yaml] +---- +management: + endpoints: + web: + cors: + allowed-origins: "https://example.com" + allowed-methods: "GET,POST" +---- + +TIP: See xref:api:java/org/springframework/boot/actuate/autoconfigure/endpoint/web/CorsEndpointProperties.html[`CorsEndpointProperties`] for a complete list of options. + + + +[[actuator.endpoints.implementing-custom]] +== Implementing Custom Endpoints + +If you add a `@Bean` annotated with `@Endpoint`, any methods annotated with `@ReadOperation`, `@WriteOperation`, or `@DeleteOperation` are automatically exposed over JMX and, in a web application, over HTTP as well. +Endpoints can be exposed over HTTP by using Jersey, Spring MVC, or Spring WebFlux. +If both Jersey and Spring MVC are available, Spring MVC is used. + +The following example exposes a read operation that returns a custom object: + +include-code::MyEndpoint[tag=read] + +You can also write technology-specific endpoints by using `@JmxEndpoint` or `@WebEndpoint`. +These endpoints are restricted to their respective technologies. +For example, `@WebEndpoint` is exposed only over HTTP and not over JMX. + +You can write technology-specific extensions by using `@EndpointWebExtension` and `@EndpointJmxExtension`. +These annotations let you provide technology-specific operations to augment an existing endpoint. + +Finally, if you need access to web-framework-specific functionality, you can implement servlet or Spring `@Controller` and `@RestController` endpoints at the cost of them not being available over JMX or when using a different web framework. + + + +[[actuator.endpoints.implementing-custom.input]] +=== Receiving Input + +Operations on an endpoint receive input through their parameters. +When exposed over the web, the values for these parameters are taken from the URL's query parameters and from the JSON request body. +When exposed over JMX, the parameters are mapped to the parameters of the MBean's operations. +Parameters are required by default. +They can be made optional by annotating them with either `@javax.annotation.Nullable` or `@org.springframework.lang.Nullable`. + +You can map each root property in the JSON request body to a parameter of the endpoint. +Consider the following JSON request body: + +[source,json] +---- +{ + "name": "test", + "counter": 42 +} +---- + +You can use this to invoke a write operation that takes `String name` and `int counter` parameters, as the following example shows: + +include-code::../MyEndpoint[tag=write] + +TIP: Because endpoints are technology agnostic, only simple types can be specified in the method signature. +In particular, declaring a single parameter with a `CustomData` type that defines a `name` and `counter` properties is not supported. + +NOTE: To let the input be mapped to the operation method's parameters, Java code that implements an endpoint should be compiled with `-parameters`, and Kotlin code that implements an endpoint should be compiled with `-java-parameters`. +This will happen automatically if you use Spring Boot's Gradle plugin or if you use Maven and `spring-boot-starter-parent`. + + + +[[actuator.endpoints.implementing-custom.input.conversion]] +==== Input Type Conversion + +The parameters passed to endpoint operation methods are, if necessary, automatically converted to the required type. +Before calling an operation method, the input received over JMX or HTTP is converted to the required types by using an instance of `ApplicationConversionService` as well as any `Converter` or `GenericConverter` beans qualified with `@EndpointConverter`. + + + +[[actuator.endpoints.implementing-custom.web]] +=== Custom Web Endpoints + +Operations on an `@Endpoint`, `@WebEndpoint`, or `@EndpointWebExtension` are automatically exposed over HTTP using Jersey, Spring MVC, or Spring WebFlux. +If both Jersey and Spring MVC are available, Spring MVC is used. + + + +[[actuator.endpoints.implementing-custom.web.request-predicates]] +==== Web Endpoint Request Predicates + +A request predicate is automatically generated for each operation on a web-exposed endpoint. + + + +[[actuator.endpoints.implementing-custom.web.path-predicates]] +==== Path + +The path of the predicate is determined by the ID of the endpoint and the base path of the web-exposed endpoints. +The default base path is `/actuator`. +For example, an endpoint with an ID of `sessions` uses `/actuator/sessions` as its path in the predicate. + +You can further customize the path by annotating one or more parameters of the operation method with `@Selector`. +Such a parameter is added to the path predicate as a path variable. +The variable's value is passed into the operation method when the endpoint operation is invoked. +If you want to capture all remaining path elements, you can add `@Selector(Match=ALL_REMAINING)` to the last parameter and make it a type that is conversion-compatible with a `String[]`. + + + +[[actuator.endpoints.implementing-custom.web.method-predicates]] +==== HTTP method + +The HTTP method of the predicate is determined by the operation type, as shown in the following table: + +[cols="3, 1"] +|=== +| Operation | HTTP method + +| `@ReadOperation` +| `GET` + +| `@WriteOperation` +| `POST` + +| `@DeleteOperation` +| `DELETE` +|=== + + + +[[actuator.endpoints.implementing-custom.web.consumes-predicates]] +==== Consumes + +For a `@WriteOperation` (HTTP `POST`) that uses the request body, the `consumes` clause of the predicate is `application/vnd.spring-boot.actuator.v2+json, application/json`. +For all other operations, the `consumes` clause is empty. + + + +[[actuator.endpoints.implementing-custom.web.produces-predicates]] +==== Produces + +The `produces` clause of the predicate can be determined by the `produces` attribute of the `@DeleteOperation`, `@ReadOperation`, and `@WriteOperation` annotations. +The attribute is optional. +If it is not used, the `produces` clause is determined automatically. + +If the operation method returns `void` or `Void`, the `produces` clause is empty. +If the operation method returns a `org.springframework.core.io.Resource`, the `produces` clause is `application/octet-stream`. +For all other operations, the `produces` clause is `application/vnd.spring-boot.actuator.v2+json, application/json`. + + + +[[actuator.endpoints.implementing-custom.web.response-status]] +==== Web Endpoint Response Status + +The default response status for an endpoint operation depends on the operation type (read, write, or delete) and what, if anything, the operation returns. + +If a `@ReadOperation` returns a value, the response status will be 200 (OK). +If it does not return a value, the response status will be 404 (Not Found). + +If a `@WriteOperation` or `@DeleteOperation` returns a value, the response status will be 200 (OK). +If it does not return a value, the response status will be 204 (No Content). + +If an operation is invoked without a required parameter or with a parameter that cannot be converted to the required type, the operation method is not called, and the response status will be 400 (Bad Request). + + + +[[actuator.endpoints.implementing-custom.web.range-requests]] +==== Web Endpoint Range Requests + +You can use an HTTP range request to request part of an HTTP resource. +When using Spring MVC or Spring Web Flux, operations that return a `org.springframework.core.io.Resource` automatically support range requests. + +NOTE: Range requests are not supported when using Jersey. + + + +[[actuator.endpoints.implementing-custom.web.security]] +==== Web Endpoint Security + +An operation on a web endpoint or a web-specific endpoint extension can receive the current `java.security.Principal` or `org.springframework.boot.actuate.endpoint.SecurityContext` as a method parameter. +The former is typically used in conjunction with `@Nullable` to provide different behavior for authenticated and unauthenticated users. +The latter is typically used to perform authorization checks by using its `isUserInRole(String)` method. + + + +[[actuator.endpoints.health]] +== Health Information + +You can use health information to check the status of your running application. +It is often used by monitoring software to alert someone when a production system goes down. +The information exposed by the `health` endpoint depends on the configprop:management.endpoint.health.show-details[] and configprop:management.endpoint.health.show-components[] properties, which can be configured with one of the following values: + +[cols="1, 3"] +|=== +| Name | Description + +| `never` +| Details are never shown. + +| `when-authorized` +| Details are shown only to authorized users. + Authorized roles can be configured by using `management.endpoint.health.roles`. + +| `always` +| Details are shown to all users. +|=== + +The default value is `never`. +A user is considered to be authorized when they are in one or more of the endpoint's roles. +If the endpoint has no configured roles (the default), all authenticated users are considered to be authorized. +You can configure the roles by using the configprop:management.endpoint.health.roles[] property. + +NOTE: If you have secured your application and wish to use `always`, your security configuration must permit access to the health endpoint for both authenticated and unauthenticated users. + +Health information is collected from the content of a xref:api:java/org/springframework/boot/actuate/health/HealthContributorRegistry.html[`HealthContributorRegistry`] (by default, all xref:api:java/org/springframework/boot/actuate/health/HealthContributor.html[`HealthContributor`] instances defined in your `ApplicationContext`). +Spring Boot includes a number of auto-configured `HealthContributors`, and you can also write your own. + +A `HealthContributor` can be either a `HealthIndicator` or a `CompositeHealthContributor`. +A `HealthIndicator` provides actual health information, including a `Status`. +A `CompositeHealthContributor` provides a composite of other `HealthContributors`. +Taken together, contributors form a tree structure to represent the overall system health. + +By default, the final system health is derived by a `StatusAggregator`, which sorts the statuses from each `HealthIndicator` based on an ordered list of statuses. +The first status in the sorted list is used as the overall health status. +If no `HealthIndicator` returns a status that is known to the `StatusAggregator`, an `UNKNOWN` status is used. + +TIP: You can use the `HealthContributorRegistry` to register and unregister health indicators at runtime. + + + +[[actuator.endpoints.health.auto-configured-health-indicators]] +=== Auto-configured HealthIndicators + +When appropriate, Spring Boot auto-configures the `HealthIndicators` listed in the following table. +You can also enable or disable selected indicators by configuring `management.health.key.enabled`, +with the `key` listed in the following table: + +[cols="2,4,6"] +|=== +| Key | Name | Description + +| `cassandra` +| xref:api:java/org/springframework/boot/actuate/cassandra/CassandraDriverHealthIndicator.html[`CassandraDriverHealthIndicator`] +| Checks that a Cassandra database is up. + +| `couchbase` +| xref:api:java/org/springframework/boot/actuate/couchbase/CouchbaseHealthIndicator.html[`CouchbaseHealthIndicator`] +| Checks that a Couchbase cluster is up. + +| `db` +| xref:api:java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicator.html[`DataSourceHealthIndicator`] +| Checks that a connection to `DataSource` can be obtained. + +| `diskspace` +| xref:api:java/org/springframework/boot/actuate/system/DiskSpaceHealthIndicator.html[`DiskSpaceHealthIndicator`] +| Checks for low disk space. + +| `elasticsearch` +| xref:api:java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestClientHealthIndicator.html[`ElasticsearchRestClientHealthIndicator`] +| Checks that an Elasticsearch cluster is up. + +| `hazelcast` +| xref:api:java/org/springframework/boot/actuate/hazelcast/HazelcastHealthIndicator.html[`HazelcastHealthIndicator`] +| Checks that a Hazelcast server is up. + +| `jms` +| xref:api:java/org/springframework/boot/actuate/jms/JmsHealthIndicator.html[`JmsHealthIndicator`] +| Checks that a JMS broker is up. + +| `ldap` +| xref:api:java/org/springframework/boot/actuate/ldap/LdapHealthIndicator.html[`LdapHealthIndicator`] +| Checks that an LDAP server is up. + +| `mail` +| xref:api:java/org/springframework/boot/actuate/mail/MailHealthIndicator.html[`MailHealthIndicator`] +| Checks that a mail server is up. + +| `mongo` +| xref:api:java/org/springframework/boot/actuate/data/mongo/MongoHealthIndicator.html[`MongoHealthIndicator`] +| Checks that a Mongo database is up. + +| `neo4j` +| xref:api:java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicator.html[`Neo4jHealthIndicator`] +| Checks that a Neo4j database is up. + +| `ping` +| xref:api:java/org/springframework/boot/actuate/health/PingHealthIndicator.html[`PingHealthIndicator`] +| Always responds with `UP`. + +| `rabbit` +| xref:api:java/org/springframework/boot/actuate/amqp/RabbitHealthIndicator.html[`RabbitHealthIndicator`] +| Checks that a Rabbit server is up. + +| `redis` +| xref:api:java/org/springframework/boot/actuate/data/redis/RedisHealthIndicator.html[`RedisHealthIndicator`] +| Checks that a Redis server is up. +|=== + +TIP: You can disable them all by setting the configprop:management.health.defaults.enabled[] property. + +Additional `HealthIndicators` are available but are not enabled by default: + +[cols="3,4,6"] +|=== +| Key | Name | Description + +| `livenessstate` +| xref:api:java/org/springframework/boot/actuate/availability/LivenessStateHealthIndicator.html[`LivenessStateHealthIndicator`] +| Exposes the "`Liveness`" application availability state. + +| `readinessstate` +| xref:api:java/org/springframework/boot/actuate/availability/ReadinessStateHealthIndicator.html[`ReadinessStateHealthIndicator`] +| Exposes the "`Readiness`" application availability state. +|=== + + + +[[actuator.endpoints.health.writing-custom-health-indicators]] +=== Writing Custom HealthIndicators + +To provide custom health information, you can register Spring beans that implement the xref:api:java/org/springframework/boot/actuate/health/HealthIndicator.html[`HealthIndicator`] interface. +You need to provide an implementation of the `health()` method and return a `Health` response. +The `Health` response should include a status and can optionally include additional details to be displayed. +The following code shows a sample `HealthIndicator` implementation: + +include-code::MyHealthIndicator[] + +NOTE: The identifier for a given `HealthIndicator` is the name of the bean without the `HealthIndicator` suffix, if it exists. +In the preceding example, the health information is available in an entry named `my`. + +TIP: Health indicators are usually called over HTTP and need to respond before any connection timeouts. +Spring Boot will log a warning message for any health indicator that takes longer than 10 seconds to respond. +If you want to configure this threshold, you can use the configprop:management.endpoint.health.logging.slow-indicator-threshold[] property. + +In addition to Spring Boot's predefined xref:api:java/org/springframework/boot/actuate/health/Status.html[`Status`] types, `Health` can return a custom `Status` that represents a new system state. +In such cases, you also need to provide a custom implementation of the xref:api:java/org/springframework/boot/actuate/health/StatusAggregator.html[`StatusAggregator`] interface, or you must configure the default implementation by using the configprop:management.endpoint.health.status.order[] configuration property. + +For example, assume a new `Status` with a code of `FATAL` is being used in one of your `HealthIndicator` implementations. +To configure the severity order, add the following property to your application properties: + +[configprops,yaml] +---- +management: + endpoint: + health: + status: + order: "fatal,down,out-of-service,unknown,up" +---- + +The HTTP status code in the response reflects the overall health status. +By default, `OUT_OF_SERVICE` and `DOWN` map to 503. +Any unmapped health statuses, including `UP`, map to 200. +You might also want to register custom status mappings if you access the health endpoint over HTTP. +Configuring a custom mapping disables the defaults mappings for `DOWN` and `OUT_OF_SERVICE`. +If you want to retain the default mappings, you must explicitly configure them, alongside any custom mappings. +For example, the following property maps `FATAL` to 503 (service unavailable) and retains the default mappings for `DOWN` and `OUT_OF_SERVICE`: + +[configprops,yaml] +---- +management: + endpoint: + health: + status: + http-mapping: + down: 503 + fatal: 503 + out-of-service: 503 +---- + +TIP: If you need more control, you can define your own `HttpCodeStatusMapper` bean. + +The following table shows the default status mappings for the built-in statuses: + +[cols="1,3"] +|=== +| Status | Mapping + +| `DOWN` +| `SERVICE_UNAVAILABLE` (`503`) + +| `OUT_OF_SERVICE` +| `SERVICE_UNAVAILABLE` (`503`) + +| `UP` +| No mapping by default, so HTTP status is `200` + +| `UNKNOWN` +| No mapping by default, so HTTP status is `200` +|=== + + + +[[actuator.endpoints.health.reactive-health-indicators]] +=== Reactive Health Indicators + +For reactive applications, such as those that use Spring WebFlux, `ReactiveHealthContributor` provides a non-blocking contract for getting application health. +Similar to a traditional `HealthContributor`, health information is collected from the content of a xref:api:java/org/springframework/boot/actuate/health/ReactiveHealthContributorRegistry.html[`ReactiveHealthContributorRegistry`] (by default, all xref:api:java/org/springframework/boot/actuate/health/HealthContributor.html[`HealthContributor`] and xref:api:java/org/springframework/boot/actuate/health/ReactiveHealthContributor.html[`ReactiveHealthContributor`] instances defined in your `ApplicationContext`). +Regular `HealthContributors` that do not check against a reactive API are executed on the elastic scheduler. + +TIP: In a reactive application, you should use the `ReactiveHealthContributorRegistry` to register and unregister health indicators at runtime. +If you need to register a regular `HealthContributor`, you should wrap it with `ReactiveHealthContributor#adapt`. + +To provide custom health information from a reactive API, you can register Spring beans that implement the xref:api:java/org/springframework/boot/actuate/health/ReactiveHealthIndicator.html[`ReactiveHealthIndicator`] interface. +The following code shows a sample `ReactiveHealthIndicator` implementation: + +include-code::MyReactiveHealthIndicator[] + +TIP: To handle the error automatically, consider extending from `AbstractReactiveHealthIndicator`. + + + +[[actuator.endpoints.health.auto-configured-reactive-health-indicators]] +=== Auto-configured ReactiveHealthIndicators + +When appropriate, Spring Boot auto-configures the following `ReactiveHealthIndicators`: + +[cols="2,4,6"] +|=== +| Key | Name | Description + +| `cassandra` +| xref:api:java/org/springframework/boot/actuate/cassandra/CassandraDriverReactiveHealthIndicator.html[`CassandraDriverReactiveHealthIndicator`] +| Checks that a Cassandra database is up. + +| `couchbase` +| xref:api:java/org/springframework/boot/actuate/couchbase/CouchbaseReactiveHealthIndicator.html[`CouchbaseReactiveHealthIndicator`] +| Checks that a Couchbase cluster is up. + +| `elasticsearch` +| xref:api:java/org/springframework/boot/actuate/data/elasticsearch/ElasticsearchReactiveHealthIndicator.html[`ElasticsearchReactiveHealthIndicator`] +| Checks that an Elasticsearch cluster is up. + +| `mongo` +| xref:api:java/org/springframework/boot/actuate/data/mongo/MongoReactiveHealthIndicator.html[`MongoReactiveHealthIndicator`] +| Checks that a Mongo database is up. + +| `neo4j` +| xref:api:java/org/springframework/boot/actuate/neo4j/Neo4jReactiveHealthIndicator.html[`Neo4jReactiveHealthIndicator`] +| Checks that a Neo4j database is up. + +| `redis` +| xref:api:java/org/springframework/boot/actuate/data/redis/RedisReactiveHealthIndicator.html[`RedisReactiveHealthIndicator`] +| Checks that a Redis server is up. +|=== + +TIP: If necessary, reactive indicators replace the regular ones. +Also, any `HealthIndicator` that is not handled explicitly is wrapped automatically. + + + +[[actuator.endpoints.health.groups]] +=== Health Groups + +It is sometimes useful to organize health indicators into groups that you can use for different purposes. + +To create a health indicator group, you can use the `management.endpoint.health.group.` property and specify a list of health indicator IDs to `include` or `exclude`. +For example, to create a group that includes only database indicators you can define the following: + +[configprops,yaml] +---- +management: + endpoint: + health: + group: + custom: + include: "db" +---- + +You can then check the result by hitting `http://localhost:8080/actuator/health/custom`. + +Similarly, to create a group that excludes the database indicators from the group and includes all the other indicators, you can define the following: + +[configprops,yaml] +---- +management: + endpoint: + health: + group: + custom: + exclude: "db" +---- + +By default, startup will fail if a health group includes or excludes a health indicator that does not exist. +To disable this behavior set configprop:management.endpoint.health.validate-group-membership[] to `false`. + +By default, groups inherit the same `StatusAggregator` and `HttpCodeStatusMapper` settings as the system health. +However, you can also define these on a per-group basis. +You can also override the `show-details` and `roles` properties if required: + +[configprops,yaml] +---- +management: + endpoint: + health: + group: + custom: + show-details: "when-authorized" + roles: "admin" + status: + order: "fatal,up" + http-mapping: + fatal: 500 + out-of-service: 500 +---- + +TIP: You can use `@Qualifier("groupname")` if you need to register custom `StatusAggregator` or `HttpCodeStatusMapper` beans for use with the group. + +A health group can also include/exclude a `CompositeHealthContributor`. +You can also include/exclude only a certain component of a `CompositeHealthContributor`. +This can be done using the fully qualified name of the component as follows: + +[source,properties] +---- +management.endpoint.health.group.custom.include="test/primary" +management.endpoint.health.group.custom.exclude="test/primary/b" +---- + +In the example above, the `custom` group will include the `HealthContributor` with the name `primary` which is a component of the composite `test`. +Here, `primary` itself is a composite and the `HealthContributor` with the name `b` will be excluded from the `custom` group. + + +Health groups can be made available at an additional path on either the main or management port. +This is useful in cloud environments such as Kubernetes, where it is quite common to use a separate management port for the actuator endpoints for security purposes. +Having a separate port could lead to unreliable health checks because the main application might not work properly even if the health check is successful. +The health group can be configured with an additional path as follows: + +[source,properties] +---- +management.endpoint.health.group.live.additional-path="server:/healthz" +---- + +This would make the `live` health group available on the main server port at `/healthz`. +The prefix is mandatory and must be either `server:` (represents the main server port) or `management:` (represents the management port, if configured.) +The path must be a single path segment. + + + +[[actuator.endpoints.health.datasource]] +=== DataSource Health + +The `DataSource` health indicator shows the health of both standard data sources and routing data source beans. +The health of a routing data source includes the health of each of its target data sources. +In the health endpoint's response, each of a routing data source's targets is named by using its routing key. +If you prefer not to include routing data sources in the indicator's output, set configprop:management.health.db.ignore-routing-data-sources[] to `true`. + + + +[[actuator.endpoints.kubernetes-probes]] +== Kubernetes Probes + +Applications deployed on Kubernetes can provide information about their internal state with https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes[Container Probes]. +Depending on https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/[your Kubernetes configuration], the kubelet calls those probes and reacts to the result. + +By default, Spring Boot manages your xref:features/spring-application.adoc#features.spring-application.application-availability[Application Availability] state. +If deployed in a Kubernetes environment, actuator gathers the "`Liveness`" and "`Readiness`" information from the `ApplicationAvailability` interface and uses that information in dedicated xref:actuator/endpoints.adoc#actuator.endpoints.health.auto-configured-health-indicators[health indicators]: `LivenessStateHealthIndicator` and `ReadinessStateHealthIndicator`. +These indicators are shown on the global health endpoint (`"/actuator/health"`). +They are also exposed as separate HTTP Probes by using xref:actuator/endpoints.adoc#actuator.endpoints.health.groups[health groups]: `"/actuator/health/liveness"` and `"/actuator/health/readiness"`. + +You can then configure your Kubernetes infrastructure with the following endpoint information: + +[source,yaml] +---- +livenessProbe: + httpGet: + path: "/actuator/health/liveness" + port: + failureThreshold: ... + periodSeconds: ... + +readinessProbe: + httpGet: + path: "/actuator/health/readiness" + port: + failureThreshold: ... + periodSeconds: ... +---- + +NOTE: `` should be set to the port that the actuator endpoints are available on. +It could be the main web server port or a separate management port if the `"management.server.port"` property has been set. + +These health groups are automatically enabled only if the application xref:how-to:deployment/cloud.adoc#howto.deployment.cloud.kubernetes[runs in a Kubernetes environment]. +You can enable them in any environment by using the configprop:management.endpoint.health.probes.enabled[] configuration property. + +NOTE: If an application takes longer to start than the configured liveness period, Kubernetes mentions the `"startupProbe"` as a possible solution. +Generally speaking, the `"startupProbe"` is not necessarily needed here, as the `"readinessProbe"` fails until all startup tasks are done. +This means your application will not receive traffic until it is ready. +However, if your application takes a long time to start, consider using a `"startupProbe"` to make sure that Kubernetes won't kill your application while it is in the process of starting. +See the section that describes xref:actuator/endpoints.adoc#actuator.endpoints.kubernetes-probes.lifecycle[how probes behave during the application lifecycle]. + +If your Actuator endpoints are deployed on a separate management context, the endpoints do not use the same web infrastructure (port, connection pools, framework components) as the main application. +In this case, a probe check could be successful even if the main application does not work properly (for example, it cannot accept new connections). +For this reason, it is a good idea to make the `liveness` and `readiness` health groups available on the main server port. +This can be done by setting the following property: + +[source,properties] +---- +management.endpoint.health.probes.add-additional-paths=true +---- + +This would make the `liveness` group available at `/livez` and the `readiness` group available at `/readyz` on the main server port. +Paths can be customized using the `additional-path` property on each group, see xref:actuator/endpoints.adoc#actuator.endpoints.health.groups[health groups] for details. + + + +[[actuator.endpoints.kubernetes-probes.external-state]] +=== Checking External State With Kubernetes Probes + +Actuator configures the "`liveness`" and "`readiness`" probes as Health Groups. +This means that all the xref:actuator/endpoints.adoc#actuator.endpoints.health.groups[health groups features] are available for them. +You can, for example, configure additional Health Indicators: + +[configprops,yaml] +---- +management: + endpoint: + health: + group: + readiness: + include: "readinessState,customCheck" +---- + +By default, Spring Boot does not add other health indicators to these groups. + +The "`liveness`" probe should not depend on health checks for external systems. +If the xref:features/spring-application.adoc#features.spring-application.application-availability.liveness[liveness state of an application] is broken, Kubernetes tries to solve that problem by restarting the application instance. +This means that if an external system (such as a database, a Web API, or an external cache) fails, Kubernetes might restart all application instances and create cascading failures. + +As for the "`readiness`" probe, the choice of checking external systems must be made carefully by the application developers. +For this reason, Spring Boot does not include any additional health checks in the readiness probe. +If the xref:features/spring-application.adoc#features.spring-application.application-availability.readiness[readiness state of an application instance] is unready, Kubernetes does not route traffic to that instance. +Some external systems might not be shared by application instances, in which case they could be included in a readiness probe. +Other external systems might not be essential to the application (the application could have circuit breakers and fallbacks), in which case they definitely should not be included. +Unfortunately, an external system that is shared by all application instances is common, and you have to make a judgement call: Include it in the readiness probe and expect that the application is taken out of service when the external service is down or leave it out and deal with failures higher up the stack, perhaps by using a circuit breaker in the caller. + +NOTE: If all instances of an application are unready, a Kubernetes Service with `type=ClusterIP` or `NodePort` does not accept any incoming connections. +There is no HTTP error response (503 and so on), since there is no connection. +A service with `type=LoadBalancer` might or might not accept connections, depending on the provider. +A service that has an explicit https://kubernetes.io/docs/concepts/services-networking/ingress/[ingress] also responds in a way that depends on the implementation -- the ingress service itself has to decide how to handle the "`connection refused`" from downstream. +HTTP 503 is quite likely in the case of both load balancer and ingress. + +Also, if an application uses Kubernetes https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/[autoscaling], it may react differently to applications being taken out of the load-balancer, depending on its autoscaler configuration. + + + +[[actuator.endpoints.kubernetes-probes.lifecycle]] +=== Application Lifecycle and Probe States + +An important aspect of the Kubernetes Probes support is its consistency with the application lifecycle. +There is a significant difference between the `AvailabilityState` (which is the in-memory, internal state of the application) +and the actual probe (which exposes that state). +Depending on the phase of application lifecycle, the probe might not be available. + +Spring Boot publishes xref:features/spring-application.adoc#features.spring-application.application-events-and-listeners[application events during startup and shutdown], +and probes can listen to such events and expose the `AvailabilityState` information. + +The following tables show the `AvailabilityState` and the state of HTTP connectors at different stages. + +When a Spring Boot application starts: + +[cols="2,2,2,3,5"] +|=== +|Startup phase |LivenessState |ReadinessState |HTTP server |Notes + +|Starting +|`BROKEN` +|`REFUSING_TRAFFIC` +|Not started +|Kubernetes checks the "liveness" Probe and restarts the application if it takes too long. + +|Started +|`CORRECT` +|`REFUSING_TRAFFIC` +|Refuses requests +|The application context is refreshed. The application performs startup tasks and does not receive traffic yet. + +|Ready +|`CORRECT` +|`ACCEPTING_TRAFFIC` +|Accepts requests +|Startup tasks are finished. The application is receiving traffic. +|=== + +When a Spring Boot application shuts down: + +[cols="2,2,2,3,5"] +|=== +|Shutdown phase |Liveness State |Readiness State |HTTP server |Notes + +|Running +|`CORRECT` +|`ACCEPTING_TRAFFIC` +|Accepts requests +|Shutdown has been requested. + +|Graceful shutdown +|`CORRECT` +|`REFUSING_TRAFFIC` +|New requests are rejected +|If enabled, xref:web/graceful-shutdown.adoc[graceful shutdown processes in-flight requests]. + +|Shutdown complete +|N/A +|N/A +|Server is shut down +|The application context is closed and the application is shut down. +|=== + +TIP: See xref:how-to:deployment/cloud.adoc#howto.deployment.cloud.kubernetes.container-lifecycle[] for more information about Kubernetes deployment. + + + +[[actuator.endpoints.info]] +== Application Information + +Application information exposes various information collected from all xref:api:java/org/springframework/boot/actuate/info/InfoContributor.html[`InfoContributor`] beans defined in your `ApplicationContext`. +Spring Boot includes a number of auto-configured `InfoContributor` beans, and you can write your own. + + + +[[actuator.endpoints.info.auto-configured-info-contributors]] +=== Auto-configured InfoContributors + +When appropriate, Spring auto-configures the following `InfoContributor` beans: + +[cols="1,4,8,4"] +|=== +| ID | Name | Description | Prerequisites + +| `build` +| xref:api:java/org/springframework/boot/actuate/info/BuildInfoContributor.html[`BuildInfoContributor`] +| Exposes build information. +| A `META-INF/build-info.properties` resource. + +| `env` +| xref:api:java/org/springframework/boot/actuate/info/EnvironmentInfoContributor.html[`EnvironmentInfoContributor`] +| Exposes any property from the `Environment` whose name starts with `info.`. +| None. + +| `git` +| xref:api:java/org/springframework/boot/actuate/info/GitInfoContributor.html[`GitInfoContributor`] +| Exposes git information. +| A `git.properties` resource. + +| `java` +| xref:api:java/org/springframework/boot/actuate/info/JavaInfoContributor.html[`JavaInfoContributor`] +| Exposes Java runtime information. +| None. + +| `os` +| xref:api:java/org/springframework/boot/actuate/info/OsInfoContributor.html[`OsInfoContributor`] +| Exposes Operating System information. +| None. + +| `process` +| xref:api:java/org/springframework/boot/actuate/info/ProcessInfoContributor.html[`ProcessInfoContributor`] +| Exposes process information. +| None. + +|=== + +Whether an individual contributor is enabled is controlled by its `management.info..enabled` property. +Different contributors have different defaults for this property, depending on their prerequisites and the nature of the information that they expose. + +With no prerequisites to indicate that they should be enabled, the `env`, `java`, `os`, and `process` contributors are disabled by default. +Each can be enabled by setting its `management.info..enabled` property to `true`. + +The `build` and `git` info contributors are enabled by default. +Each can be disabled by setting its `management.info..enabled` property to `false`. +Alternatively, to disable every contributor that is usually enabled by default, set the configprop:management.info.defaults.enabled[] property to `false`. + + + +[[actuator.endpoints.info.custom-application-information]] +=== Custom Application Information + +When the `env` contributor is enabled, you can customize the data exposed by the `info` endpoint by setting `+info.*+` Spring properties. +All `Environment` properties under the `info` key are automatically exposed. +For example, you could add the following settings to your `application.properties` file: + +[configprops,yaml] +---- +info: + app: + encoding: "UTF-8" + java: + source: "17" + target: "17" +---- + +[TIP] +==== +Rather than hardcoding those values, you could also xref:how-to:properties-and-configuration.adoc#howto.properties-and-configuration.expand-properties[expand info properties at build time]. + +Assuming you use Maven, you could rewrite the preceding example as follows: + +[configprops,yaml] +---- +info: + app: + encoding: "@project.build.sourceEncoding@" + java: + source: "@java.version@" + target: "@java.version@" +---- +==== + + + +[[actuator.endpoints.info.git-commit-information]] +=== Git Commit Information + +Another useful feature of the `info` endpoint is its ability to publish information about the state of your `git` source code repository when the project was built. +If a `GitProperties` bean is available, you can use the `info` endpoint to expose these properties. + +TIP: A `GitProperties` bean is auto-configured if a `git.properties` file is available at the root of the classpath. +See xref:how-to:build.adoc#howto.build.generate-git-info[] for more detail. + +By default, the endpoint exposes `git.branch`, `git.commit.id`, and `git.commit.time` properties, if present. +If you do not want any of these properties in the endpoint response, they need to be excluded from the `git.properties` file. +If you want to display the full git information (that is, the full content of `git.properties`), use the configprop:management.info.git.mode[] property, as follows: + +[configprops,yaml] +---- +management: + info: + git: + mode: "full" +---- + +To disable the git commit information from the `info` endpoint completely, set the configprop:management.info.git.enabled[] property to `false`, as follows: + +[configprops,yaml] +---- +management: + info: + git: + enabled: false +---- + + + +[[actuator.endpoints.info.build-information]] +=== Build Information + +If a `BuildProperties` bean is available, the `info` endpoint can also publish information about your build. +This happens if a `META-INF/build-info.properties` file is available in the classpath. + +TIP: The Maven and Gradle plugins can both generate that file. +See xref:how-to:build.adoc#howto.build.generate-info[] for more details. + + + +[[actuator.endpoints.info.java-information]] +=== Java Information + +The `info` endpoint publishes information about your Java runtime environment, see xref:api:java/org/springframework/boot/info/JavaInfo.html[`JavaInfo`] for more details. + + + +[[actuator.endpoints.info.os-information]] +=== OS Information + +The `info` endpoint publishes information about your Operating System, see xref:api:java/org/springframework/boot/info/OsInfo.html[`OsInfo`] for more details. + + + +[[actuator.endpoints.info.process-information]] +=== Process Information + +The `info` endpoint publishes information about your process, see xref:api:java/org/springframework/boot/info/ProcessInfo.html[`ProcessInfo`] for more details. + + + +[[actuator.endpoints.info.writing-custom-info-contributors]] +=== Writing Custom InfoContributors + +To provide custom application information, you can register Spring beans that implement the xref:api:java/org/springframework/boot/actuate/info/InfoContributor.html[`InfoContributor`] interface. + +The following example contributes an `example` entry with a single value: + +include-code::MyInfoContributor[] + +If you reach the `info` endpoint, you should see a response that contains the following additional entry: + +[source,json] +---- +{ + "example": { + "key" : "value" + } +} +---- + + + +[[actuator.endpoints.sbom]] +== Software Bill of Materials (SBOM) + +The `sbom` endpoint exposes the https://en.wikipedia.org/wiki/Software_supply_chain[Software Bill of Materials]. +CycloneDX SBOMs can be auto-detected, but other formats can be manually configured, too. + +The `spring-boot-starter-parent` Maven parent and the Spring Boot Gradle plugin configure the https://github.com/CycloneDX/cyclonedx-maven-plugin[CycloneDX Maven plugin] and the https://github.com/CycloneDX/cyclonedx-gradle-plugin[CycloneDX Gradle plugin] respectively. + +To get a CycloneDX SBOM, you'll need to add this to your Maven build: + +[source,xml] +---- + + + + org.cyclonedx + cyclonedx-maven-plugin + + + +---- + +For Gradle, you'll need to apply the CycloneDX Gradle plugin: + +[source,groovy] +---- +plugins { + id 'org.cyclonedx.bom' version '1.8.2' +} +---- + +The `sbom` actuator endpoint will then expose an SBOM called "application", which describes the contents of your application. + + + +[[actuator.endpoints.sbom.other-formats]] +=== Other SBOM formats + +If you want to publish an SBOM in a different format, there are some configuration properties which you can use. + +The configuration property configprop:management.endpoint.sbom.application.location[] sets the location for the application SBOM. +For example, setting this to `classpath:sbom.json` will use the contents of the `/sbom.json` resource on the classpath. + +The media type for SBOMs in CycloneDX, SPDX and Syft format is detected automatically. +To override the auto-detected media type, use the configuration property configprop:management.endpoint.sbom.application.media-type[]. + + + +[[actuator.endpoints.sbom.additional]] +=== Additional SBOMs + +The actuator endpoint can handle multiple SBOMs. +To add SBOMs, use the configuration property configprop:management.endpoint.sbom.additional[], as shown in this example: + +[configprops,yaml] +---- +management: + endpoint: + sbom: + additional: + system: + location: "optional:file:/system.spdx.json" + media-type: "application/spdx+json" +---- + +This will add an SBOM called "system", which is stored in `/system.spdx.json`. +The `optional:` prefix can be used to prevent a startup failure if the file doesn't exist. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/http-exchanges.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/http-exchanges.adoc similarity index 85% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/http-exchanges.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/http-exchanges.adoc index faf3c3f6b6f4..06b12f1e96c9 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/http-exchanges.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/http-exchanges.adoc @@ -1,5 +1,6 @@ [[actuator.http-exchanges]] -== Recording HTTP Exchanges += Recording HTTP Exchanges + You can enable recording of HTTP exchanges by providing a bean of type `HttpExchangeRepository` in your application's configuration. For convenience, Spring Boot offers `InMemoryHttpExchangeRepository`, which, by default, stores the last 100 request-response exchanges. `InMemoryHttpExchangeRepository` is limited compared to tracing solutions, and we recommend using it only for development environments. @@ -11,7 +12,8 @@ You can use the `httpexchanges` endpoint to obtain information about the request [[actuator.http-exchanges.custom]] -=== Custom HTTP Exchange Recording +== Custom HTTP Exchange Recording + To customize the items that are included in each recorded exchange, use the configprop:management.httpexchanges.recording.include[] configuration property. -To disable recording entirely, set configprop:management.httpexchanges.recording.enabled[] to `false`. +To disable recoding entirely, set configprop:management.httpexchanges.recording.enabled[] to `false`. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/index.adoc new file mode 100644 index 000000000000..3c05673e8739 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/index.adoc @@ -0,0 +1,8 @@ + +[[actuator]] += Production-ready Features + +Spring Boot includes a number of additional features to help you monitor and manage your application when you push it to production. +You can choose to manage and monitor your application by using HTTP endpoints or with JMX. +Auditing, health, and metrics gathering can also be automatically applied to your application. + diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/jmx.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/jmx.adoc similarity index 76% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/jmx.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/jmx.adoc index ad3a52b2a439..37f197ee7381 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/jmx.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/jmx.adoc @@ -1,5 +1,6 @@ [[actuator.jmx]] -== Monitoring and Management over JMX += Monitoring and Management over JMX + Java Management Extensions (JMX) provide a standard mechanism to monitor and manage applications. By default, this feature is not enabled. You can turn it on by setting the configprop:spring.jmx.enabled[] configuration property to `true`. @@ -9,7 +10,7 @@ Any of your beans that are annotated with Spring JMX annotations (`@ManagedResou If your platform provides a standard `MBeanServer`, Spring Boot uses that and defaults to the VM `MBeanServer`, if necessary. If all that fails, a new `MBeanServer` is created. -See the {spring-boot-autoconfigure-module-code}/jmx/JmxAutoConfiguration.java[`JmxAutoConfiguration`] class for more details. +See the {code-spring-boot-autoconfigure-src}/jmx/JmxAutoConfiguration.java[`JmxAutoConfiguration`] class for more details. By default, Spring Boot also exposes management endpoints as JMX MBeans under the `org.springframework.boot` domain. To take full control over endpoint registration in the JMX domain, consider registering your own `EndpointObjectNameFactory` implementation. @@ -17,7 +18,8 @@ To take full control over endpoint registration in the JMX domain, consider regi [[actuator.jmx.custom-mbean-names]] -=== Customizing MBean Names +== Customizing MBean Names + The name of the MBean is usually generated from the `id` of the endpoint. For example, the `health` endpoint is exposed as `org.springframework.boot:type=Endpoint,name=Health`. @@ -27,28 +29,29 @@ To solve this problem, you can set the configprop:spring.jmx.unique-names[] prop You can also customize the JMX domain under which endpoints are exposed. The following settings show an example of doing so in `application.properties`: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - jmx: - unique-names: true - management: - endpoints: - jmx: - domain: "com.example.myapp" +spring: + jmx: + unique-names: true +management: + endpoints: + jmx: + domain: "com.example.myapp" ---- [[actuator.jmx.disable-jmx-endpoints]] -=== Disabling JMX Endpoints +== Disabling JMX Endpoints + If you do not want to expose endpoints over JMX, you can set the configprop:management.endpoints.jmx.exposure.exclude[] property to `*`, as the following example shows: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - management: - endpoints: - jmx: - exposure: - exclude: "*" +management: + endpoints: + jmx: + exposure: + exclude: "*" ---- diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/loggers.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/loggers.adoc new file mode 100644 index 000000000000..3c6e0534acd9 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/loggers.adoc @@ -0,0 +1,33 @@ +[[actuator.loggers]] += Loggers + +Spring Boot Actuator includes the ability to view and configure the log levels of your application at runtime. +You can view either the entire list or an individual logger's configuration, which is made up of both the explicitly configured logging level as well as the effective logging level given to it by the logging framework. +These levels can be one of: + +* `TRACE` +* `DEBUG` +* `INFO` +* `WARN` +* `ERROR` +* `FATAL` +* `OFF` +* `null` + +`null` indicates that there is no explicit configuration. + + + +[[actuator.loggers.configure]] +== Configure a Logger + +To configure a given logger, `POST` a partial entity to the resource's URI, as the following example shows: + +[source,json] +---- +{ + "configuredLevel": "DEBUG" +} +---- + +TIP: To "`reset`" the specific level of the logger (and use the default configuration instead), you can pass a value of `null` as the `configuredLevel`. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc new file mode 100644 index 000000000000..62b12611022e --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc @@ -0,0 +1,1273 @@ +[[actuator.metrics]] += Metrics + +Spring Boot Actuator provides dependency management and auto-configuration for {url-micrometer-site}[Micrometer], an application metrics facade that supports {url-micrometer-docs}[numerous monitoring systems], including: + +- xref:actuator/metrics.adoc#actuator.metrics.export.appoptics[] +- xref:actuator/metrics.adoc#actuator.metrics.export.atlas[] +- xref:actuator/metrics.adoc#actuator.metrics.export.datadog[] +- xref:actuator/metrics.adoc#actuator.metrics.export.dynatrace[] +- xref:actuator/metrics.adoc#actuator.metrics.export.elastic[] +- xref:actuator/metrics.adoc#actuator.metrics.export.ganglia[] +- xref:actuator/metrics.adoc#actuator.metrics.export.graphite[] +- xref:actuator/metrics.adoc#actuator.metrics.export.humio[] +- xref:actuator/metrics.adoc#actuator.metrics.export.influx[] +- xref:actuator/metrics.adoc#actuator.metrics.export.jmx[] +- xref:actuator/metrics.adoc#actuator.metrics.export.kairos[] +- xref:actuator/metrics.adoc#actuator.metrics.export.newrelic[] +- xref:actuator/metrics.adoc#actuator.metrics.export.otlp[] +- xref:actuator/metrics.adoc#actuator.metrics.export.prometheus[] +- xref:actuator/metrics.adoc#actuator.metrics.export.signalfx[] +- xref:actuator/metrics.adoc#actuator.metrics.export.simple[] (in-memory) +- xref:actuator/metrics.adoc#actuator.metrics.export.stackdriver[] +- xref:actuator/metrics.adoc#actuator.metrics.export.statsd[D] +- xref:actuator/metrics.adoc#actuator.metrics.export.wavefront[] + +TIP: To learn more about Micrometer's capabilities, see its {url-micrometer-docs}[reference documentation], in particular the {url-micrometer-docs-concepts}[concepts section]. + + + +[[actuator.metrics.getting-started]] +== Getting Started + +Spring Boot auto-configures a composite `MeterRegistry` and adds a registry to the composite for each of the supported implementations that it finds on the classpath. +Having a dependency on `micrometer-registry-\{system}` in your runtime classpath is enough for Spring Boot to configure the registry. + +Most registries share common features. +For instance, you can disable a particular registry even if the Micrometer registry implementation is on the classpath. +The following example disables Datadog: + +[configprops,yaml] +---- +management: + datadog: + metrics: + export: + enabled: false +---- + +You can also disable all registries unless stated otherwise by the registry-specific property, as the following example shows: + +[configprops,yaml] +---- +management: + defaults: + metrics: + export: + enabled: false +---- + +Spring Boot also adds any auto-configured registries to the global static composite registry on the `Metrics` class, unless you explicitly tell it not to: + +[configprops,yaml] +---- +management: + metrics: + use-global-registry: false +---- + +You can register any number of `MeterRegistryCustomizer` beans to further configure the registry, such as applying common tags, before any meters are registered with the registry: + +include-code::commontags/MyMeterRegistryConfiguration[] + +You can apply customizations to particular registry implementations by being more specific about the generic type: + +include-code::specifictype/MyMeterRegistryConfiguration[] + +Spring Boot also xref:actuator/metrics.adoc#actuator.metrics.supported[configures built-in instrumentation] that you can control through configuration or dedicated annotation markers. + + + +[[actuator.metrics.export]] +== Supported Monitoring Systems + +This section briefly describes each of the supported monitoring systems. + + + +[[actuator.metrics.export.appoptics]] +=== AppOptics + +By default, the AppOptics registry periodically pushes metrics to `https://api.appoptics.com/v1/measurements`. +To export metrics to SaaS {url-micrometer-docs-implementations}/appOptics[AppOptics], your API token must be provided: + +[configprops,yaml] +---- +management: + appoptics: + metrics: + export: + api-token: "YOUR_TOKEN" +---- + + + +[[actuator.metrics.export.atlas]] +=== Atlas + +By default, metrics are exported to {url-micrometer-docs-implementations}/atlas[Atlas] running on your local machine. +You can provide the location of the https://github.com/Netflix/atlas[Atlas server]: + +[configprops,yaml] +---- +management: + atlas: + metrics: + export: + uri: "https://atlas.example.com:7101/api/v1/publish" +---- + + + +[[actuator.metrics.export.datadog]] +=== Datadog + +A Datadog registry periodically pushes metrics to https://www.datadoghq.com[datadoghq]. +To export metrics to {url-micrometer-docs-implementations}/datadog[Datadog], you must provide your API key: + +[configprops,yaml] +---- +management: + datadog: + metrics: + export: + api-key: "YOUR_KEY" +---- + +If you additionally provide an application key (optional), then metadata such as meter descriptions, types, and base units will also be exported: + +[configprops,yaml] +---- +management: + datadog: + metrics: + export: + api-key: "YOUR_API_KEY" + application-key: "YOUR_APPLICATION_KEY" +---- + +By default, metrics are sent to the Datadog US https://docs.datadoghq.com/getting_started/site[site] (`https://api.datadoghq.com`). +If your Datadog project is hosted on one of the other sites, or you need to send metrics through a proxy, configure the URI accordingly: + +[configprops,yaml] +---- +management: + datadog: + metrics: + export: + uri: "https://api.datadoghq.eu" +---- + +You can also change the interval at which metrics are sent to Datadog: + +[configprops,yaml] +---- +management: + datadog: + metrics: + export: + step: "30s" +---- + + + +[[actuator.metrics.export.dynatrace]] +=== Dynatrace + +Dynatrace offers two metrics ingest APIs, both of which are implemented for {url-micrometer-docs-implementations}/dynatrace[Micrometer]. +You can find the Dynatrace documentation on Micrometer metrics ingest {url-dynatrace-docs-shortlink}/micrometer-metrics-ingest[here]. +Configuration properties in the `v1` namespace apply only when exporting to the {url-dynatrace-docs-shortlink}/api-metrics[Timeseries v1 API]. +Configuration properties in the `v2` namespace apply only when exporting to the {url-dynatrace-docs-shortlink}/api-metrics-v2-post-datapoints[Metrics v2 API]. +Note that this integration can export only to either the `v1` or `v2` version of the API at a time, with `v2` being preferred. +If the `device-id` (required for v1 but not used in v2) is set in the `v1` namespace, metrics are exported to the `v1` endpoint. +Otherwise, `v2` is assumed. + + + +[[actuator.metrics.export.dynatrace.v2-api]] +==== v2 API + +You can use the v2 API in two ways. + + + +[[actuator.metrics.export.dynatrace.v2-api.auto-config]] +===== Auto-configuration + +Dynatrace auto-configuration is available for hosts that are monitored by the OneAgent or by the Dynatrace Operator for Kubernetes. + +**Local OneAgent:** If a OneAgent is running on the host, metrics are automatically exported to the {url-dynatrace-docs-shortlink}/local-api[local OneAgent ingest endpoint]. +The ingest endpoint forwards the metrics to the Dynatrace backend. + +**Dynatrace Kubernetes Operator:** When running in Kubernetes with the Dynatrace Operator installed, the registry will automatically pick up your endpoint URI and API token from the operator instead. + +This is the default behavior and requires no special setup beyond a dependency on `io.micrometer:micrometer-registry-dynatrace`. + + + +[[actuator.metrics.export.dynatrace.v2-api.manual-config]] +===== Manual Configuration + +If no auto-configuration is available, the endpoint of the {url-dynatrace-docs-shortlink}/api-metrics-v2-post-datapoints[Metrics v2 API] and an API token are required. +The {url-dynatrace-docs-shortlink}/api-authentication[API token] must have the "`Ingest metrics`" (`metrics.ingest`) permission set. +We recommend limiting the scope of the token to this one permission. +You must ensure that the endpoint URI contains the path (for example, `/api/v2/metrics/ingest`): + +The URL of the Metrics API v2 ingest endpoint is different according to your deployment option: + +* SaaS: `+https://{your-environment-id}.live.dynatrace.com/api/v2/metrics/ingest+` +* Managed deployments: `+https://{your-domain}/e/{your-environment-id}/api/v2/metrics/ingest+` + +The example below configures metrics export using the `example` environment id: + +[configprops,yaml] +---- +management: + dynatrace: + metrics: + export: + uri: "https://example.live.dynatrace.com/api/v2/metrics/ingest" + api-token: "YOUR_TOKEN" +---- + +When using the Dynatrace v2 API, the following optional features are available (more details can be found in the {url-dynatrace-docs-shortlink}/micrometer-metrics-ingest#dt-configuration-properties[Dynatrace documentation]): + +* Metric key prefix: Sets a prefix that is prepended to all exported metric keys. +* Enrich with Dynatrace metadata: If a OneAgent or Dynatrace operator is running, enrich metrics with additional metadata (for example, about the host, process, or pod). +* Default dimensions: Specify key-value pairs that are added to all exported metrics. +If tags with the same key are specified with Micrometer, they overwrite the default dimensions. +* Use Dynatrace Summary instruments: In some cases the Micrometer Dynatrace registry created metrics that were rejected. +In Micrometer 1.9.x, this was fixed by introducing Dynatrace-specific summary instruments. +Setting this toggle to `false` forces Micrometer to fall back to the behavior that was the default before 1.9.x. +It should only be used when encountering problems while migrating from Micrometer 1.8.x to 1.9.x. +* Export meter metadata: Starting from Micrometer 1.12.0, the Dynatrace exporter will also export meter metadata, such as unit and description by default. +Use the `export-meter-metadata` toggle to turn this feature off. + +It is possible to not specify a URI and API token, as shown in the following example. +In this scenario, the automatically configured endpoint is used: + +[configprops,yaml] +---- +management: + dynatrace: + metrics: + export: + # Specify uri and api-token here if not using the local OneAgent endpoint. + v2: + metric-key-prefix: "your.key.prefix" + enrich-with-dynatrace-metadata: true + default-dimensions: + key1: "value1" + key2: "value2" + use-dynatrace-summary-instruments: true # (default: true) + export-meter-metadata: true # (default: true) +---- + + + +[[actuator.metrics.export.dynatrace.v1-api]] +==== v1 API (Legacy) + +The Dynatrace v1 API metrics registry pushes metrics to the configured URI periodically by using the {url-dynatrace-docs-shortlink}/api-metrics[Timeseries v1 API]. +For backwards-compatibility with existing setups, when `device-id` is set (required for v1, but not used in v2), metrics are exported to the Timeseries v1 endpoint. +To export metrics to {url-micrometer-docs-implementations}/dynatrace[Dynatrace], your API token, device ID, and URI must be provided: + +[configprops,yaml] +---- +management: + dynatrace: + metrics: + export: + uri: "https://{your-environment-id}.live.dynatrace.com" + api-token: "YOUR_TOKEN" + v1: + device-id: "YOUR_DEVICE_ID" +---- + +For the v1 API, you must specify the base environment URI without a path, as the v1 endpoint path is added automatically. + + + +[[actuator.metrics.export.dynatrace.version-independent-settings]] +==== Version-independent Settings + +In addition to the API endpoint and token, you can also change the interval at which metrics are sent to Dynatrace. +The default export interval is `60s`. +The following example sets the export interval to 30 seconds: + +[configprops,yaml] +---- +management: + dynatrace: + metrics: + export: + step: "30s" +---- + +You can find more information on how to set up the Dynatrace exporter for Micrometer in the {url-micrometer-docs-implementations}/dynatrace[Micrometer documentation] and the {url-dynatrace-docs-shortlink}/micrometer-metrics-ingest[Dynatrace documentation]. + + + +[[actuator.metrics.export.elastic]] +=== Elastic + +By default, metrics are exported to {url-micrometer-docs-implementations}/elastic[Elastic] running on your local machine. +You can provide the location of the Elastic server to use by using the following property: + +[configprops,yaml] +---- +management: + elastic: + metrics: + export: + host: "https://elastic.example.com:8086" +---- + + + +[[actuator.metrics.export.ganglia]] +=== Ganglia + +By default, metrics are exported to {url-micrometer-docs-implementations}/ganglia[Ganglia] running on your local machine. +You can provide the http://ganglia.sourceforge.net[Ganglia server] host and port, as the following example shows: + +[configprops,yaml] +---- +management: + ganglia: + metrics: + export: + host: "ganglia.example.com" + port: 9649 +---- + + + +[[actuator.metrics.export.graphite]] +=== Graphite + +By default, metrics are exported to {url-micrometer-docs-implementations}/graphite[Graphite] running on your local machine. +You can provide the https://graphiteapp.org[Graphite server] host and port, as the following example shows: + +[configprops,yaml] +---- +management: + graphite: + metrics: + export: + host: "graphite.example.com" + port: 9004 +---- + +Micrometer provides a default `HierarchicalNameMapper` that governs how a dimensional meter ID is {url-micrometer-docs-implementations}/graphite#_hierarchical_name_mapping[mapped to flat hierarchical names]. + +[TIP] +==== +To take control over this behavior, define your `GraphiteMeterRegistry` and supply your own `HierarchicalNameMapper`. +An auto-configured `GraphiteConfig` and `Clock` beans are provided unless you define your own: + +include-code::MyGraphiteConfiguration[] +==== + + + +[[actuator.metrics.export.humio]] +=== Humio + +By default, the Humio registry periodically pushes metrics to https://cloud.humio.com. +To export metrics to SaaS {url-micrometer-docs-implementations}/humio[Humio], you must provide your API token: + +[configprops,yaml] +---- +management: + humio: + metrics: + export: + api-token: "YOUR_TOKEN" +---- + +You should also configure one or more tags to identify the data source to which metrics are pushed: + +[configprops,yaml] +---- +management: + humio: + metrics: + export: + tags: + alpha: "a" + bravo: "b" +---- + + + +[[actuator.metrics.export.influx]] +=== Influx + +By default, metrics are exported to an {url-micrometer-docs-implementations}/influx[Influx] v1 instance running on your local machine with the default configuration. +To export metrics to InfluxDB v2, configure the `org`, `bucket`, and authentication `token` for writing metrics. +You can provide the location of the https://www.influxdata.com[Influx server] to use by using: + +[configprops,yaml] +---- +management: + influx: + metrics: + export: + uri: "https://influx.example.com:8086" +---- + + + +[[actuator.metrics.export.jmx]] +=== JMX + +Micrometer provides a hierarchical mapping to {url-micrometer-docs-implementations}/jmx[JMX], primarily as a cheap and portable way to view metrics locally. +By default, metrics are exported to the `metrics` JMX domain. +You can provide the domain to use by using: + +[configprops,yaml] +---- +management: + jmx: + metrics: + export: + domain: "com.example.app.metrics" +---- + +Micrometer provides a default `HierarchicalNameMapper` that governs how a dimensional meter ID is {url-micrometer-docs-implementations}/jmx#_hierarchical_name_mapping[mapped to flat hierarchical names]. + +[TIP] +==== +To take control over this behavior, define your `JmxMeterRegistry` and supply your own `HierarchicalNameMapper`. +An auto-configured `JmxConfig` and `Clock` beans are provided unless you define your own: + +include-code::MyJmxConfiguration[] +==== + + + +[[actuator.metrics.export.kairos]] +=== KairosDB + +By default, metrics are exported to {url-micrometer-docs-implementations}/kairos[KairosDB] running on your local machine. +You can provide the location of the https://kairosdb.github.io/[KairosDB server] to use by using: + +[configprops,yaml] +---- +management: + kairos: + metrics: + export: + uri: "https://kairosdb.example.com:8080/api/v1/datapoints" +---- + + + +[[actuator.metrics.export.newrelic]] +=== New Relic + +A New Relic registry periodically pushes metrics to {url-micrometer-docs-implementations}/new-relic[New Relic]. +To export metrics to https://newrelic.com[New Relic], you must provide your API key and account ID: + +[configprops,yaml] +---- +management: + newrelic: + metrics: + export: + api-key: "YOUR_KEY" + account-id: "YOUR_ACCOUNT_ID" +---- + +You can also change the interval at which metrics are sent to New Relic: + +[configprops,yaml] +---- +management: + newrelic: + metrics: + export: + step: "30s" +---- + +By default, metrics are published through REST calls, but you can also use the Java Agent API if you have it on the classpath: + +[configprops,yaml] +---- +management: + newrelic: + metrics: + export: + client-provider-type: "insights-agent" +---- + +Finally, you can take full control by defining your own `NewRelicClientProvider` bean. + + + +[[actuator.metrics.export.otlp]] +=== OpenTelemetry + +By default, metrics are exported to {url-micrometer-docs-implementations}/otlp[OpenTelemetry] running on your local machine. +You can provide the location of the https://opentelemetry.io/[OpenTelemetry metric endpoint] to use by using: + +[configprops,yaml] +---- +management: + otlp: + metrics: + export: + url: "https://otlp.example.com:4318/v1/metrics" +---- + + + +[[actuator.metrics.export.prometheus]] +=== Prometheus + +{url-micrometer-docs-implementations}/prometheus[Prometheus] expects to scrape or poll individual application instances for metrics. +Spring Boot provides an actuator endpoint at `/actuator/prometheus` to present a https://prometheus.io[Prometheus scrape] with the appropriate format. + +TIP: By default, the endpoint is not available and must be exposed. See xref:actuator/endpoints.adoc#actuator.endpoints.exposing[exposing endpoints] for more details. + +The following example `scrape_config` adds to `prometheus.yml`: + +[source,yaml] +---- +scrape_configs: +- job_name: "spring" + metrics_path: "/actuator/prometheus" + static_configs: + - targets: ["HOST:PORT"] +---- + +https://prometheus.io/docs/prometheus/latest/feature_flags/#exemplars-storage[Prometheus Exemplars] are also supported. +To enable this feature, a `SpanContext` bean should be present. +If you're using the deprecated Prometheus simpleclient support and want to enable that feature, a `SpanContextSupplier` bean should be present. +If you use {url-micrometer-tracing-docs}[Micrometer Tracing], this will be auto-configured for you, but you can always create your own if you want. +Please check the https://prometheus.io/docs/prometheus/latest/feature_flags/#exemplars-storage[Prometheus Docs], since this feature needs to be explicitly enabled on Prometheus' side, and it is only supported using the https://github.com/OpenObservability/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#exemplars[OpenMetrics] format. + +For ephemeral or batch jobs that may not exist long enough to be scraped, you can use https://github.com/prometheus/pushgateway[Prometheus Pushgateway] support to expose the metrics to Prometheus. + +NOTE: The Prometheus Pushgateway only works with the deprecated Prometheus simpleclient for now, until the Prometheus 1.x client adds support for it. +To switch to the simpleclient, remove `io.micrometer:micrometer-registry-prometheus` from your project and add `io.micrometer:micrometer-registry-prometheus-simpleclient` instead. + +To enable Prometheus Pushgateway support, add the following dependency to your project: + +[source,xml] +---- + + io.prometheus + simpleclient_pushgateway + +---- + +When the Prometheus Pushgateway dependency is present on the classpath and the configprop:management.prometheus.metrics.export.pushgateway.enabled[] property is set to `true`, a `PrometheusPushGatewayManager` bean is auto-configured. +This manages the pushing of metrics to a Prometheus Pushgateway. + +You can tune the `PrometheusPushGatewayManager` by using properties under `management.prometheus.metrics.export.pushgateway`. +For advanced configuration, you can also provide your own `PrometheusPushGatewayManager` bean. + + + +[[actuator.metrics.export.signalfx]] +=== SignalFx + +SignalFx registry periodically pushes metrics to {url-micrometer-docs-implementations}/signalFx[SignalFx]. +To export metrics to https://www.signalfx.com[SignalFx], you must provide your access token: + +[configprops,yaml] +---- +management: + signalfx: + metrics: + export: + access-token: "YOUR_ACCESS_TOKEN" +---- + +You can also change the interval at which metrics are sent to SignalFx: + +[configprops,yaml] +---- +management: + signalfx: + metrics: + export: + step: "30s" +---- + + + +[[actuator.metrics.export.simple]] +=== Simple + +Micrometer ships with a simple, in-memory backend that is automatically used as a fallback if no other registry is configured. +This lets you see what metrics are collected in the xref:actuator/metrics.adoc#actuator.metrics.endpoint[metrics endpoint]. + +The in-memory backend disables itself as soon as you use any other available backend. +You can also disable it explicitly: + +[configprops,yaml] +---- +management: + simple: + metrics: + export: + enabled: false +---- + + + +[[actuator.metrics.export.stackdriver]] +=== Stackdriver + +The Stackdriver registry periodically pushes metrics to https://cloud.google.com/stackdriver/[Stackdriver]. +To export metrics to SaaS {url-micrometer-docs-implementations}/stackdriver[Stackdriver], you must provide your Google Cloud project ID: + +[configprops,yaml] +---- +management: + stackdriver: + metrics: + export: + project-id: "my-project" +---- + +You can also change the interval at which metrics are sent to Stackdriver: + +[configprops,yaml] +---- +management: + stackdriver: + metrics: + export: + step: "30s" +---- + + + +[[actuator.metrics.export.statsd]] +=== StatsD + +The StatsD registry eagerly pushes metrics over UDP to a StatsD agent. +By default, metrics are exported to a {url-micrometer-docs-implementations}/statsD[StatsD] agent running on your local machine. +You can provide the StatsD agent host, port, and protocol to use by using: + +[configprops,yaml] +---- +management: + statsd: + metrics: + export: + host: "statsd.example.com" + port: 9125 + protocol: "udp" +---- + +You can also change the StatsD line protocol to use (it defaults to Datadog): + +[configprops,yaml] +---- +management: + statsd: + metrics: + export: + flavor: "etsy" +---- + + + +[[actuator.metrics.export.wavefront]] +=== Wavefront + +The Wavefront registry periodically pushes metrics to {url-micrometer-docs-implementations}/wavefront[Wavefront]. +If you are exporting metrics to https://www.wavefront.com/[Wavefront] directly, you must provide your API token: + +[configprops,yaml] +---- +management: + wavefront: + api-token: "YOUR_API_TOKEN" +---- + +Alternatively, you can use a Wavefront sidecar or an internal proxy in your environment to forward metrics data to the Wavefront API host: + +[configprops,yaml] +---- +management: + wavefront: + uri: "proxy://localhost:2878" +---- + +NOTE: If you publish metrics to a Wavefront proxy (as described in https://docs.wavefront.com/proxies_installing.html[the Wavefront documentation]), the host must be in the `proxy://HOST:PORT` format. + +You can also change the interval at which metrics are sent to Wavefront: + +[configprops,yaml] +---- +management: + wavefront: + metrics: + export: + step: "30s" +---- + + + +[[actuator.metrics.supported]] +== Supported Metrics and Meters + +Spring Boot provides automatic meter registration for a wide variety of technologies. +In most situations, the defaults provide sensible metrics that can be published to any of the supported monitoring systems. + + + +[[actuator.metrics.supported.jvm]] +=== JVM Metrics + +Auto-configuration enables JVM Metrics by using core Micrometer classes. +JVM metrics are published under the `jvm.` meter name. + +The following JVM metrics are provided: + +* Various memory and buffer pool details +* Statistics related to garbage collection +* Thread utilization +* The number of classes loaded and unloaded +* JVM version information +* JIT compilation time + + + +[[actuator.metrics.supported.system]] +=== System Metrics + +Auto-configuration enables system metrics by using core Micrometer classes. +System metrics are published under the `system.`, `process.`, and `disk.` meter names. + +The following system metrics are provided: + +* CPU metrics +* File descriptor metrics +* Uptime metrics (both the amount of time the application has been running and a fixed gauge of the absolute start time) +* Disk space available + + + +[[actuator.metrics.supported.application-startup]] +=== Application Startup Metrics + +Auto-configuration exposes application startup time metrics: + +* `application.started.time`: time taken to start the application. +* `application.ready.time`: time taken for the application to be ready to service requests. + +Metrics are tagged by the fully qualified name of the application class. + + + +[[actuator.metrics.supported.logger]] +=== Logger Metrics + +Auto-configuration enables the event metrics for both Logback and Log4J2. +The details are published under the `log4j2.events.` or `logback.events.` meter names. + + + +[[actuator.metrics.supported.tasks]] +=== Task Execution and Scheduling Metrics + +Auto-configuration enables the instrumentation of all available `ThreadPoolTaskExecutor` and `ThreadPoolTaskScheduler` beans, as long as the underling `ThreadPoolExecutor` is available. +Metrics are tagged by the name of the executor, which is derived from the bean name. + + + +[[actuator.metrics.supported.jms]] +=== JMS Metrics + +Auto-configuration enables the instrumentation of all available `JmsTemplate` beans and `@JmsListener` annotated methods. +This will produce `"jms.message.publish"` and `"jms.message.process"` metrics respectively. +See the {url-spring-framework-docs}/integration/observability.html#observability.jms[Spring Framework reference documentation for more information on produced observations]. + + + +[[actuator.metrics.supported.spring-mvc]] +=== Spring MVC Metrics + +Auto-configuration enables the instrumentation of all requests handled by Spring MVC controllers and functional handlers. +By default, metrics are generated with the name, `http.server.requests`. +You can customize the name by setting the configprop:management.observations.http.server.requests.name[] property. + +See the {url-spring-framework-docs}/integration/observability.html#observability.http-server.servlet[Spring Framework reference documentation for more information on produced observations]. + +To add to the default tags, provide a `@Bean` that extends `DefaultServerRequestObservationConvention` from the `org.springframework.http.server.observation` package. +To replace the default tags, provide a `@Bean` that implements `ServerRequestObservationConvention`. + + +TIP: In some cases, exceptions handled in web controllers are not recorded as request metrics tags. +Applications can opt in and record exceptions by xref:web/servlet.adoc#web.servlet.spring-mvc.error-handling[setting handled exceptions as request attributes]. + +By default, all requests are handled. +To customize the filter, provide a `@Bean` that implements `FilterRegistrationBean`. + + + +[[actuator.metrics.supported.spring-webflux]] +=== Spring WebFlux Metrics + +Auto-configuration enables the instrumentation of all requests handled by Spring WebFlux controllers and functional handlers. +By default, metrics are generated with the name, `http.server.requests`. +You can customize the name by setting the configprop:management.observations.http.server.requests.name[] property. + +See the {url-spring-framework-docs}/integration/observability.html#observability.http-server.reactive[Spring Framework reference documentation for more information on produced observations]. + +To add to the default tags, provide a `@Bean` that extends `DefaultServerRequestObservationConvention` from the `org.springframework.http.server.reactive.observation` package. +To replace the default tags, provide a `@Bean` that implements `ServerRequestObservationConvention`. + +TIP: In some cases, exceptions handled in controllers and handler functions are not recorded as request metrics tags. +Applications can opt in and record exceptions by xref:web/reactive.adoc#web.reactive.webflux.error-handling[setting handled exceptions as request attributes]. + + + +[[actuator.metrics.supported.jersey]] +=== Jersey Server Metrics + +Auto-configuration enables the instrumentation of all requests handled by the Jersey JAX-RS implementation. +By default, metrics are generated with the name, `http.server.requests`. +You can customize the name by setting the configprop:management.observations.http.server.requests.name[] property. + +By default, Jersey server metrics are tagged with the following information: + +|=== +| Tag | Description + +| `exception` +| The simple class name of any exception that was thrown while handling the request. + +| `method` +| The request's method (for example, `GET` or `POST`) + +| `outcome` +| The request's outcome, based on the status code of the response. + 1xx is `INFORMATIONAL`, 2xx is `SUCCESS`, 3xx is `REDIRECTION`, 4xx is `CLIENT_ERROR`, and 5xx is `SERVER_ERROR` + +| `status` +| The response's HTTP status code (for example, `200` or `500`) + +| `uri` +| The request's URI template prior to variable substitution, if possible (for example, `/api/person/\{id}`) +|=== + +To customize the tags, provide a `@Bean` that implements `JerseyObservationConvention`. + + + +[[actuator.metrics.supported.http-clients]] +=== HTTP Client Metrics + +Spring Boot Actuator manages the instrumentation of `RestTemplate`, `WebClient` and `RestClient`. +For that, you have to inject the auto-configured builder and use it to create instances: + +* `RestTemplateBuilder` for `RestTemplate` +* `WebClient.Builder` for `WebClient` +* `RestClient.Builder` for `RestClient` + +You can also manually apply the customizers responsible for this instrumentation, namely `ObservationRestTemplateCustomizer`, `ObservationWebClientCustomizer` and `ObservationRestClientCustomizer`. + +By default, metrics are generated with the name, `http.client.requests`. +You can customize the name by setting the configprop:management.observations.http.client.requests.name[] property. + +See the {url-spring-framework-docs}/integration/observability.html#observability.http-client[Spring Framework reference documentation for more information on produced observations]. + +To customize the tags when using `RestTemplate` or `RestClient`, provide a `@Bean` that implements `ClientRequestObservationConvention` from the `org.springframework.http.client.observation` package. +To customize the tags when using `WebClient`, provide a `@Bean` that implements `ClientRequestObservationConvention` from the `org.springframework.web.reactive.function.client` package. + + + +[[actuator.metrics.supported.tomcat]] +=== Tomcat Metrics + +Auto-configuration enables the instrumentation of Tomcat only when an `MBeanRegistry` is enabled. +By default, the `MBeanRegistry` is disabled, but you can enable it by setting configprop:server.tomcat.mbeanregistry.enabled[] to `true`. + +Tomcat metrics are published under the `tomcat.` meter name. + + + +[[actuator.metrics.supported.cache]] +=== Cache Metrics + +Auto-configuration enables the instrumentation of all available `Cache` instances on startup, with metrics prefixed with `cache`. +Cache instrumentation is standardized for a basic set of metrics. +Additional, cache-specific metrics are also available. + +The following cache libraries are supported: + +* Cache2k +* Caffeine +* Hazelcast +* Any compliant JCache (JSR-107) implementation +* Redis + +Metrics are tagged by the name of the cache and by the name of the `CacheManager`, which is derived from the bean name. + +NOTE: Only caches that are configured on startup are bound to the registry. +For caches not defined in the cache’s configuration, such as caches created on the fly or programmatically after the startup phase, an explicit registration is required. +A `CacheMetricsRegistrar` bean is made available to make that process easier. + + + +[[actuator.metrics.supported.spring-batch]] +=== Spring Batch Metrics + +See the {url-spring-batch-docs}/monitoring-and-metrics.html[Spring Batch reference documentation]. + + + +[[actuator.metrics.supported.spring-graphql]] +=== Spring GraphQL Metrics + +See the {url-spring-graphql-docs}/observability.html[Spring GraphQL reference documentation]. + + + +[[actuator.metrics.supported.jdbc]] +=== DataSource Metrics + +Auto-configuration enables the instrumentation of all available `DataSource` objects with metrics prefixed with `jdbc.connections`. +Data source instrumentation results in gauges that represent the currently active, idle, maximum allowed, and minimum allowed connections in the pool. + +Metrics are also tagged by the name of the `DataSource` computed based on the bean name. + +TIP: By default, Spring Boot provides metadata for all supported data sources. +You can add additional `DataSourcePoolMetadataProvider` beans if your favorite data source is not supported. +See `DataSourcePoolMetadataProvidersConfiguration` for examples. + +Also, Hikari-specific metrics are exposed with a `hikaricp` prefix. +Each metric is tagged by the name of the pool (you can control it with `spring.datasource.name`). + + + +[[actuator.metrics.supported.hibernate]] +=== Hibernate Metrics + +If `org.hibernate.orm:hibernate-micrometer` is on the classpath, all available Hibernate `EntityManagerFactory` instances that have statistics enabled are instrumented with a metric named `hibernate`. + +Metrics are also tagged by the name of the `EntityManagerFactory`, which is derived from the bean name. + +To enable statistics, the standard JPA property `hibernate.generate_statistics` must be set to `true`. +You can enable that on the auto-configured `EntityManagerFactory`: + +[configprops,yaml] +---- +spring: + jpa: + properties: + "[hibernate.generate_statistics]": true +---- + + + +[[actuator.metrics.supported.spring-data-repository]] +=== Spring Data Repository Metrics + +Auto-configuration enables the instrumentation of all Spring Data `Repository` method invocations. +By default, metrics are generated with the name, `spring.data.repository.invocations`. +You can customize the name by setting the configprop:management.metrics.data.repository.metric-name[] property. + +The `@Timed` annotation from the `io.micrometer.core.annotation` package is supported on `Repository` interfaces and methods. +If you do not want to record metrics for all `Repository` invocations, you can set configprop:management.metrics.data.repository.autotime.enabled[] to `false` and exclusively use `@Timed` annotations instead. + +NOTE: A `@Timed` annotation with `longTask = true` enables a long task timer for the method. +Long task timers require a separate metric name and can be stacked with a short task timer. + +By default, repository invocation related metrics are tagged with the following information: + +|=== +| Tag | Description + +| `repository` +| The simple class name of the source `Repository`. + +| `method` +| The name of the `Repository` method that was invoked. + +| `state` +| The result state (`SUCCESS`, `ERROR`, `CANCELED`, or `RUNNING`). + +| `exception` +| The simple class name of any exception that was thrown from the invocation. +|=== + +To replace the default tags, provide a `@Bean` that implements `RepositoryTagsProvider`. + + + +[[actuator.metrics.supported.rabbitmq]] +=== RabbitMQ Metrics + +Auto-configuration enables the instrumentation of all available RabbitMQ connection factories with a metric named `rabbitmq`. + + + +[[actuator.metrics.supported.spring-integration]] +=== Spring Integration Metrics + +Spring Integration automatically provides {url-spring-integration-docs}/metrics.html#micrometer-integration[Micrometer support] whenever a `MeterRegistry` bean is available. +Metrics are published under the `spring.integration.` meter name. + + + +[[actuator.metrics.supported.kafka]] +=== Kafka Metrics + +Auto-configuration registers a `MicrometerConsumerListener` and `MicrometerProducerListener` for the auto-configured consumer factory and producer factory, respectively. +It also registers a `KafkaStreamsMicrometerListener` for `StreamsBuilderFactoryBean`. +For more detail, see the {url-spring-kafka-docs}/kafka/micrometer.html#micrometer-native[Micrometer Native Metrics] section of the Spring Kafka documentation. + + + +[[actuator.metrics.supported.mongodb]] +=== MongoDB Metrics + +This section briefly describes the available metrics for MongoDB. + + + +[[actuator.metrics.supported.mongodb.command]] +==== MongoDB Command Metrics + +Auto-configuration registers a `MongoMetricsCommandListener` with the auto-configured `MongoClient`. + +A timer metric named `mongodb.driver.commands` is created for each command issued to the underlying MongoDB driver. +Each metric is tagged with the following information by default: +|=== +| Tag | Description + +| `command` +| The name of the command issued. + +| `cluster.id` +| The identifier of the cluster to which the command was sent. + +| `server.address` +| The address of the server to which the command was sent. + +| `status` +| The outcome of the command (`SUCCESS` or `FAILED`). +|=== + +To replace the default metric tags, define a `MongoCommandTagsProvider` bean, as the following example shows: + +include-code::MyCommandTagsProviderConfiguration[] + +To disable the auto-configured command metrics, set the following property: + +[configprops,yaml] +---- +management: + metrics: + mongo: + command: + enabled: false +---- + + + +[[actuator.metrics.supported.mongodb.connection-pool]] +==== MongoDB Connection Pool Metrics + +Auto-configuration registers a `MongoMetricsConnectionPoolListener` with the auto-configured `MongoClient`. + +The following gauge metrics are created for the connection pool: + +* `mongodb.driver.pool.size` reports the current size of the connection pool, including idle and and in-use members. +* `mongodb.driver.pool.checkedout` reports the count of connections that are currently in use. +* `mongodb.driver.pool.waitqueuesize` reports the current size of the wait queue for a connection from the pool. + +Each metric is tagged with the following information by default: +|=== +| Tag | Description + +| `cluster.id` +| The identifier of the cluster to which the connection pool corresponds. + +| `server.address` +| The address of the server to which the connection pool corresponds. +|=== + +To replace the default metric tags, define a `MongoConnectionPoolTagsProvider` bean: + +include-code::MyConnectionPoolTagsProviderConfiguration[] + +To disable the auto-configured connection pool metrics, set the following property: + +[configprops,yaml] +---- +management: + metrics: + mongo: + connectionpool: + enabled: false +---- + + + +[[actuator.metrics.supported.jetty]] +=== Jetty Metrics + +Auto-configuration binds metrics for Jetty's `ThreadPool` by using Micrometer's `JettyServerThreadPoolMetrics`. +Metrics for Jetty's `Connector` instances are bound by using Micrometer's `JettyConnectionMetrics` and, when configprop:server.ssl.enabled[] is set to `true`, Micrometer's `JettySslHandshakeMetrics`. + + + +[[actuator.metrics.supported.timed-annotation]] +=== @Timed Annotation Support + +To enable scanning of `@Timed` annotations, you will need to set the configprop:management.observations.annotations.enabled[] property to `true`. +Please refer to the {url-micrometer-docs-concepts}#_the_timed_annotation[Micrometer documentation]. + + + +[[actuator.metrics.supported.redis]] +=== Redis Metrics + +Auto-configuration registers a `MicrometerCommandLatencyRecorder` for the auto-configured `LettuceConnectionFactory`. +For more detail, see the {url-lettuce-docs}#command.latency.metrics.micrometer[Micrometer Metrics section] of the Lettuce documentation. + + + +[[actuator.metrics.registering-custom]] +== Registering Custom Metrics + +To register custom metrics, inject `MeterRegistry` into your component: + +include-code::MyBean[] + +If your metrics depend on other beans, we recommend that you use a `MeterBinder` to register them: + +include-code::MyMeterBinderConfiguration[] + +Using a `MeterBinder` ensures that the correct dependency relationships are set up and that the bean is available when the metric's value is retrieved. +A `MeterBinder` implementation can also be useful if you find that you repeatedly instrument a suite of metrics across components or applications. + +NOTE: By default, metrics from all `MeterBinder` beans are automatically bound to the Spring-managed `MeterRegistry`. + + + +[[actuator.metrics.customizing]] +== Customizing Individual Metrics + +If you need to apply customizations to specific `Meter` instances, you can use the `io.micrometer.core.instrument.config.MeterFilter` interface. + +For example, if you want to rename the `mytag.region` tag to `mytag.area` for all meter IDs beginning with `com.example`, you can do the following: + +include-code::MyMetricsFilterConfiguration[] + +NOTE: By default, all `MeterFilter` beans are automatically bound to the Spring-managed `MeterRegistry`. +Make sure to register your metrics by using the Spring-managed `MeterRegistry` and not any of the static methods on `Metrics`. +These use the global registry that is not Spring-managed. + + + +[[actuator.metrics.customizing.common-tags]] +=== Common Tags + +Common tags are generally used for dimensional drill-down on the operating environment, such as host, instance, region, stack, and others. +Commons tags are applied to all meters and can be configured, as the following example shows: + +[configprops,yaml] +---- +management: + metrics: + tags: + region: "us-east-1" + stack: "prod" +---- + +The preceding example adds `region` and `stack` tags to all meters with a value of `us-east-1` and `prod`, respectively. + +NOTE: The order of common tags is important if you use Graphite. +As the order of common tags cannot be guaranteed by using this approach, Graphite users are advised to define a custom `MeterFilter` instead. + + + +[[actuator.metrics.customizing.per-meter-properties]] +=== Per-meter Properties + +In addition to `MeterFilter` beans, you can apply a limited set of customization on a per-meter basis using properties. +Per-meter customizations are applied, using Spring Boot's `PropertiesMeterFilter`, to any meter IDs that start with the given name. +The following example filters out any meters that have an ID starting with `example.remote`. + +[configprops,yaml] +---- +management: + metrics: + enable: + example: + remote: false +---- + +The following properties allow per-meter customization: + +.Per-meter customizations +|=== +| Property | Description + +| configprop:management.metrics.enable[] +| Whether to accept meters with certain IDs. + Meters that are not accepted are filtered from the `MeterRegistry`. + +| configprop:management.metrics.distribution.percentiles-histogram[] +| Whether to publish a histogram suitable for computing aggregable (across dimension) percentile approximations. + +| configprop:management.metrics.distribution.minimum-expected-value[], configprop:management.metrics.distribution.maximum-expected-value[] +| Publish fewer histogram buckets by clamping the range of expected values. + +| configprop:management.metrics.distribution.percentiles[] +| Publish percentile values computed in your application + +| configprop:management.metrics.distribution.expiry[], configprop:management.metrics.distribution.buffer-length[] +| Give greater weight to recent samples by accumulating them in ring buffers which rotate after a configurable expiry, with a +configurable buffer length. + +| configprop:management.metrics.distribution.slo[] +| Publish a cumulative histogram with buckets defined by your service-level objectives. +|=== + +For more details on the concepts behind `percentiles-histogram`, `percentiles`, and `slo`, see the {url-micrometer-docs-concepts}#_histograms_and_percentiles[Histograms and percentiles] section of the Micrometer documentation. + + + +[[actuator.metrics.endpoint]] +== Metrics Endpoint + +Spring Boot provides a `metrics` endpoint that you can use diagnostically to examine the metrics collected by an application. +The endpoint is not available by default and must be exposed. +See xref:actuator/endpoints.adoc#actuator.endpoints.exposing[exposing endpoints] for more details. + +Navigating to `/actuator/metrics` displays a list of available meter names. +You can drill down to view information about a particular meter by providing its name as a selector -- for example, `/actuator/metrics/jvm.memory.max`. + +[TIP] +==== +The name you use here should match the name used in the code, not the name after it has been naming-convention normalized for a monitoring system to which it is shipped. +In other words, if `jvm.memory.max` appears as `jvm_memory_max` in Prometheus because of its snake case naming convention, you should still use `jvm.memory.max` as the selector when inspecting the meter in the `metrics` endpoint. +==== + +You can also add any number of `tag=KEY:VALUE` query parameters to the end of the URL to dimensionally drill down on a meter -- for example, `/actuator/metrics/jvm.memory.max?tag=area:nonheap`. + +[TIP] +==== +The reported measurements are the _sum_ of the statistics of all meters that match the meter name and any tags that have been applied. +In the preceding example, the returned `Value` statistic is the sum of the maximum memory footprints of the "`Code Cache`", "`Compressed Class Space`", and "`Metaspace`" areas of the heap. +If you wanted to see only the maximum size for the "`Metaspace`", you could add an additional `tag=id:Metaspace` -- that is, `/actuator/metrics/jvm.memory.max?tag=area:nonheap&tag=id:Metaspace`. +==== + + + +[[actuator.metrics.micrometer-observation]] +== Integration with Micrometer Observation + +A `DefaultMeterObservationHandler` is automatically registered on the `ObservationRegistry`, which creates metrics for every completed observation. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/monitoring.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/monitoring.adoc new file mode 100644 index 000000000000..faec2eafd4aa --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/monitoring.adoc @@ -0,0 +1,154 @@ +[[actuator.monitoring]] += Monitoring and Management Over HTTP + +If you are developing a web application, Spring Boot Actuator auto-configures all enabled endpoints to be exposed over HTTP. +The default convention is to use the `id` of the endpoint with a prefix of `/actuator` as the URL path. +For example, `health` is exposed as `/actuator/health`. + +TIP: Actuator is supported natively with Spring MVC, Spring WebFlux, and Jersey. +If both Jersey and Spring MVC are available, Spring MVC is used. + +NOTE: Jackson is a required dependency in order to get the correct JSON responses as documented in the xref:api:rest/actuator/index.adoc[API documentation]. + + + +[[actuator.monitoring.customizing-management-server-context-path]] +== Customizing the Management Endpoint Paths + +Sometimes, it is useful to customize the prefix for the management endpoints. +For example, your application might already use `/actuator` for another purpose. +You can use the configprop:management.endpoints.web.base-path[] property to change the prefix for your management endpoint, as the following example shows: + +[configprops,yaml] +---- +management: + endpoints: + web: + base-path: "/manage" +---- + +The preceding `application.properties` example changes the endpoint from `/actuator/\{id}` to `/manage/\{id}` (for example, `/manage/info`). + +NOTE: Unless the management port has been configured to xref:actuator/monitoring.adoc#actuator.monitoring.customizing-management-server-port[expose endpoints by using a different HTTP port], `management.endpoints.web.base-path` is relative to `server.servlet.context-path` (for servlet web applications) or `spring.webflux.base-path` (for reactive web applications). +If `management.server.port` is configured, `management.endpoints.web.base-path` is relative to `management.server.base-path`. + +If you want to map endpoints to a different path, you can use the configprop:management.endpoints.web.path-mapping[] property. + +The following example remaps `/actuator/health` to `/healthcheck`: + +[configprops,yaml] +---- +management: + endpoints: + web: + base-path: "/" + path-mapping: + health: "healthcheck" +---- + + + +[[actuator.monitoring.customizing-management-server-port]] +== Customizing the Management Server Port + +Exposing management endpoints by using the default HTTP port is a sensible choice for cloud-based deployments. +If, however, your application runs inside your own data center, you may prefer to expose endpoints by using a different HTTP port. + +You can set the configprop:management.server.port[] property to change the HTTP port, as the following example shows: + +[configprops,yaml] +---- +management: + server: + port: 8081 +---- + +NOTE: On Cloud Foundry, by default, applications receive requests only on port 8080 for both HTTP and TCP routing. +If you want to use a custom management port on Cloud Foundry, you need to explicitly set up the application's routes to forward traffic to the custom port. + + + +[[actuator.monitoring.management-specific-ssl]] +== Configuring Management-specific SSL + +When configured to use a custom port, you can also configure the management server with its own SSL by using the various `management.server.ssl.*` properties. +For example, doing so lets a management server be available over HTTP while the main application uses HTTPS, as the following property settings show: + +[configprops,yaml] +---- +server: + port: 8443 + ssl: + enabled: true + key-store: "classpath:store.jks" + key-password: "secret" +management: + server: + port: 8080 + ssl: + enabled: false +---- + +Alternatively, both the main server and the management server can use SSL but with different key stores, as follows: + +[configprops,yaml] +---- +server: + port: 8443 + ssl: + enabled: true + key-store: "classpath:main.jks" + key-password: "secret" +management: + server: + port: 8080 + ssl: + enabled: true + key-store: "classpath:management.jks" + key-password: "secret" +---- + + + +[[actuator.monitoring.customizing-management-server-address]] +== Customizing the Management Server Address + +You can customize the address on which the management endpoints are available by setting the configprop:management.server.address[] property. +Doing so can be useful if you want to listen only on an internal or ops-facing network or to listen only for connections from `localhost`. + +NOTE: You can listen on a different address only when the port differs from the main server port. + +The following example `application.properties` does not allow remote management connections: + +[configprops,yaml] +---- +management: + server: + port: 8081 + address: "127.0.0.1" +---- + + + +[[actuator.monitoring.disabling-http-endpoints]] +== Disabling HTTP Endpoints + +If you do not want to expose endpoints over HTTP, you can set the management port to `-1`, as the following example shows: + +[configprops,yaml] +---- +management: + server: + port: -1 +---- + +You can also achieve this by using the configprop:management.endpoints.web.exposure.exclude[] property, as the following example shows: + +[configprops,yaml] +---- +management: + endpoints: + web: + exposure: + exclude: "*" +---- diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/observability.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/observability.adoc new file mode 100644 index 000000000000..e225e1db1a19 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/observability.adoc @@ -0,0 +1,102 @@ +[[actuator.observability]] += Observability + +Observability is the ability to observe the internal state of a running system from the outside. +It consists of the three pillars: logging, metrics and traces. + +For metrics and traces, Spring Boot uses {url-micrometer-docs}/observation[Micrometer Observation]. +To create your own observations (which will lead to metrics and traces), you can inject an `ObservationRegistry`. + +include-code::MyCustomObservation[] + +NOTE: Low cardinality tags will be added to metrics and traces, while high cardinality tags will only be added to traces. + +Beans of type `ObservationPredicate`, `GlobalObservationConvention`, `ObservationFilter` and `ObservationHandler` will be automatically registered on the `ObservationRegistry`. +You can additionally register any number of `ObservationRegistryCustomizer` beans to further configure the registry. + +Observability support relies on the https://github.com/micrometer-metrics/context-propagation[Context Propagation library] for forwarding the current observation across threads and reactive pipelines. +By default, `ThreadLocal` values are not automatically reinstated in reactive operators. +This behavior is controlled with the configprop:spring.reactor.context-propagation[] property, which can be set to `auto` to enable automatic propagation. + +For more details about observations please see the {url-micrometer-docs}/observation[Micrometer Observation documentation]. + +TIP: Observability for JDBC can be configured using a separate project. +The https://github.com/jdbc-observations/datasource-micrometer[Datasource Micrometer project] provides a Spring Boot starter which automatically creates observations when JDBC operations are invoked. +Read more about it https://jdbc-observations.github.io/datasource-micrometer/docs/current/docs/html/[in the reference documentation]. + +TIP: Observability for R2DBC is built into Spring Boot. +To enable it, add the `io.r2dbc:r2dbc-proxy` dependency to your project. + + + +[[actuator.observability.common-tags]] +== Common Tags + +Common tags are generally used for dimensional drill-down on the operating environment, such as host, instance, region, stack, and others. +Common tags are applied to all observations as low cardinality tags and can be configured, as the following example shows: + +[configprops,yaml] +---- +management: + observations: + key-values: + region: "us-east-1" + stack: "prod" +---- + +The preceding example adds `region` and `stack` tags to all observations with a value of `us-east-1` and `prod`, respectively. + + + +[[actuator.observability.preventing-observations]] +== Preventing Observations + +If you'd like to prevent some observations from being reported, you can use the configprop:management.observations.enable[] properties: + +[configprops,yaml] +---- +management: + observations: + enable: + denied: + prefix: false + another: + denied: + prefix: false +---- + +The preceding example will prevent all observations with a name starting with `denied.prefix` or `another.denied.prefix`. + +TIP: If you want to prevent Spring Security from reporting observations, set the property configprop:management.observations.enable.spring.security[] to `false`. + +If you need greater control over the prevention of observations, you can register beans of type `ObservationPredicate`. +Observations are only reported if all the `ObservationPredicate` beans return `true` for that observation. + +include-code::MyObservationPredicate[] + +The preceding example will prevent all observations whose name contains "denied". + + + +[[actuator.observability.opentelemetry]] +== OpenTelemetry Support + +Spring Boot's actuator module includes basic support for https://opentelemetry.io/[OpenTelemetry]. + +It provides a bean of type `OpenTelemetry`, and if there are beans of type `SdkTracerProvider`, `ContextPropagators`, `SdkLoggerProvider` or `SdkMeterProvider` in the application context, they automatically get registered. +Additionally, it provides a `Resource` bean. +The attributes of the auto-configured `Resource` can be configured via the configprop:management.opentelemetry.resource-attributes[] configuration property. +If you have defined your own `Resource` bean, this will no longer be the case. + +NOTE: Spring Boot does not provide auto-configuration for OpenTelemetry metrics or logging. +OpenTelemetry tracing is only auto-configured when used together with xref:actuator/tracing.adoc[Micrometer Tracing]. + +The next sections will provide more details about logging, metrics and traces. + + + +[[actuator.observability.annotations]] +== Micrometer Observation Annotations support + +To enable scanning of metrics and tracing annotations like `@Timed`, `@Counted`, `@MeterTag` and `@NewSpan` annotations, you will need to set the configprop:management.observations.annotations.enabled[] property to `true`. +This feature is supported Micrometer directly. Please refer to the {url-micrometer-docs-concepts}#_the_timed_annotation[Micrometer] and {url-micrometer-tracing-docs}/api.html#_aspect_oriented_programming[Micrometer Tracing] reference docs. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/process-monitoring.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/process-monitoring.adoc new file mode 100644 index 000000000000..58e37cf0413b --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/process-monitoring.adoc @@ -0,0 +1,34 @@ +[[actuator.process-monitoring]] += Process Monitoring + +In the `spring-boot` module, you can find two classes to create files that are often useful for process monitoring: + +* `ApplicationPidFileWriter` creates a file that contains the application PID (by default, in the application directory with a file name of `application.pid`). +* `WebServerPortFileWriter` creates a file (or files) that contain the ports of the running web server (by default, in the application directory with a file name of `application.port`). + +By default, these writers are not activated, but you can enable them: + +* xref:actuator/process-monitoring.adoc#actuator.process-monitoring.configuration[] +* xref:actuator/process-monitoring.adoc#actuator.process-monitoring.programmatically[] + + + +[[actuator.process-monitoring.configuration]] +== Extending Configuration + +In the `META-INF/spring.factories` file, you can activate the listener (or listeners) that writes a PID file: + +[source] +---- +org.springframework.context.ApplicationListener=\ +org.springframework.boot.context.ApplicationPidFileWriter,\ +org.springframework.boot.web.context.WebServerPortFileWriter +---- + + + +[[actuator.process-monitoring.programmatically]] +== Programmatically Enabling Process Monitoring + +You can also activate a listener by invoking the `SpringApplication.addListeners(...)` method and passing the appropriate `Writer` object. +This method also lets you customize the file name and path in the `Writer` constructor. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/tracing.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/tracing.adoc new file mode 100644 index 000000000000..fcddb23e4227 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/tracing.adoc @@ -0,0 +1,219 @@ +[[actuator.micrometer-tracing]] += Tracing + +Spring Boot Actuator provides dependency management and auto-configuration for {url-micrometer-tracing-docs}[Micrometer Tracing], a facade for popular tracer libraries. + +TIP: To learn more about Micrometer Tracing capabilities, see its {url-micrometer-tracing-docs}[reference documentation]. + + + +[[actuator.micrometer-tracing.tracers]] +== Supported Tracers + +Spring Boot ships auto-configuration for the following tracers: + +* https://opentelemetry.io/[OpenTelemetry] with https://zipkin.io/[Zipkin], https://docs.wavefront.com/[Wavefront], or https://opentelemetry.io/docs/reference/specification/protocol/[OTLP] +* https://github.com/openzipkin/brave[OpenZipkin Brave] with https://zipkin.io/[Zipkin] or https://docs.wavefront.com/[Wavefront] + + + +[[actuator.micrometer-tracing.getting-started]] +== Getting Started + +We need an example application that we can use to get started with tracing. +For our purposes, the simple "`Hello World!`" web application that's covered in the xref:tutorial:first-application/index.adoc[] section will suffice. +We're going to use the OpenTelemetry tracer with Zipkin as trace backend. + +To recap, our main application code looks like this: + +include-code::MyApplication[] + +NOTE: There's an added logger statement in the `home()` method, which will be important later. + +Now we have to add the following dependencies: + +* `org.springframework.boot:spring-boot-starter-actuator` +* `io.micrometer:micrometer-tracing-bridge-otel` - bridges the Micrometer Observation API to OpenTelemetry. +* `io.opentelemetry:opentelemetry-exporter-zipkin` - reports {url-micrometer-tracing-docs}/glossary[traces] to Zipkin. + +Add the following application properties: + +[configprops,yaml] +---- +management: + tracing: + sampling: + probability: 1.0 +---- + +By default, Spring Boot samples only 10% of requests to prevent overwhelming the trace backend. +This property switches it to 100% so that every request is sent to the trace backend. + +To collect and visualize the traces, we need a running trace backend. +We use Zipkin as our trace backend here. +The https://zipkin.io/pages/quickstart[Zipkin Quickstart guide] provides instructions how to start Zipkin locally. + +After Zipkin is running, you can start your application. + +If you open a web browser to `http://localhost:8080`, you should see the following output: + +[source] +---- +Hello World! +---- + +Behind the scenes, an observation has been created for the HTTP request, which in turn gets bridged to OpenTelemetry, which reports a new trace to Zipkin. + +Now open the Zipkin UI at `http://localhost:9411` and press the "Run Query" button to list all collected traces. +You should see one trace. +Press the "Show" button to see the details of that trace. + + + +[[actuator.micrometer-tracing.logging]] +== Logging Correlation IDs + +Correlation IDs provide a helpful way to link lines in your log files to spans/traces. +If you are using Micrometer Tracing, Spring Boot will include correlation IDs in your logs by default. + +The default correlation ID is built from `traceId` and `spanId` https://logback.qos.ch/manual/mdc.html[MDC] values. +For example, if Micrometer Tracing has added an MDC `traceId` of `803B448A0489F84084905D3093480352` and an MDC `spanId` of `3425F23BB2432450` the log output will include the correlation ID `[803B448A0489F84084905D3093480352-3425F23BB2432450]`. + +If you prefer to use a different format for your correlation ID, you can use the configprop:logging.pattern.correlation[] property to define one. +For example, the following will provide a correlation ID for Logback in format previously used by Spring Cloud Sleuth: + +[configprops,yaml] +---- +logging: + pattern: + correlation: "[${spring.application.name:},%X{traceId:-},%X{spanId:-}] " + include-application-name: false +---- + +NOTE: In the example above, configprop:logging.include-application-name[] is set to `false` to avoid the application name being duplicated in the log messages (configprop:logging.pattern.correlation[] already contains it). +It's also worth mentioning that configprop:logging.pattern.correlation[] contains a trailing space so that it is separated from the logger name that comes right after it by default. + + + +[[actuator.micrometer-tracing.propagating-traces]] +== Propagating Traces + +To automatically propagate traces over the network, use the auto-configured xref:io/rest-client.adoc#io.rest-client.resttemplate[`RestTemplateBuilder`], xref:io/rest-client.adoc#io.rest-client.restclient[`RestClient.Builder`] or xref:io/rest-client.adoc#io.rest-client.webclient[`WebClient.Builder`] to construct the client. + +WARNING: If you create the `RestTemplate`, the `RestClient` or the `WebClient` without using the auto-configured builders, automatic trace propagation won't work! + + + +[[actuator.micrometer-tracing.tracer-implementations]] +== Tracer Implementations + +As Micrometer Tracer supports multiple tracer implementations, there are multiple dependency combinations possible with Spring Boot. + +All tracer implementations need the `org.springframework.boot:spring-boot-starter-actuator` dependency. + + + +[[actuator.micrometer-tracing.tracer-implementations.otel-zipkin]] +=== OpenTelemetry With Zipkin + +Tracing with OpenTelemetry and reporting to Zipkin requires the following dependencies: + +* `io.micrometer:micrometer-tracing-bridge-otel` - bridges the Micrometer Observation API to OpenTelemetry. +* `io.opentelemetry:opentelemetry-exporter-zipkin` - reports traces to Zipkin. + +Use the `management.zipkin.tracing.*` configuration properties to configure reporting to Zipkin. + + + +[[actuator.micrometer-tracing.tracer-implementations.otel-wavefront]] +=== OpenTelemetry With Wavefront + +Tracing with OpenTelemetry and reporting to Wavefront requires the following dependencies: + +* `io.micrometer:micrometer-tracing-bridge-otel` - bridges the Micrometer Observation API to OpenTelemetry. +* `io.micrometer:micrometer-tracing-reporter-wavefront` - reports traces to Wavefront. + +Use the `management.wavefront.*` configuration properties to configure reporting to Wavefront. + + + +[[actuator.micrometer-tracing.tracer-implementations.otel-otlp]] +=== OpenTelemetry With OTLP + +Tracing with OpenTelemetry and reporting using OTLP requires the following dependencies: + +* `io.micrometer:micrometer-tracing-bridge-otel` - bridges the Micrometer Observation API to OpenTelemetry. +* `io.opentelemetry:opentelemetry-exporter-otlp` - reports traces to a collector that can accept OTLP. + +Use the `management.otlp.tracing.*` configuration properties to configure reporting using OTLP. + + + +[[actuator.micrometer-tracing.tracer-implementations.brave-zipkin]] +=== OpenZipkin Brave With Zipkin + +Tracing with OpenZipkin Brave and reporting to Zipkin requires the following dependencies: + +* `io.micrometer:micrometer-tracing-bridge-brave` - bridges the Micrometer Observation API to Brave. +* `io.zipkin.reporter2:zipkin-reporter-brave` - reports traces to Zipkin. + +Use the `management.zipkin.tracing.*` configuration properties to configure reporting to Zipkin. + + + +[[actuator.micrometer-tracing.tracer-implementations.brave-wavefront]] +=== OpenZipkin Brave With Wavefront + +Tracing with OpenZipkin Brave and reporting to Wavefront requires the following dependencies: + +* `io.micrometer:micrometer-tracing-bridge-brave` - bridges the Micrometer Observation API to Brave. +* `io.micrometer:micrometer-tracing-reporter-wavefront` - reports traces to Wavefront. + +Use the `management.wavefront.*` configuration properties to configure reporting to Wavefront. + + + +[[actuator.micrometer-tracing.micrometer-observation]] +== Integration with Micrometer Observation + +A `TracingAwareMeterObservationHandler` is automatically registered on the `ObservationRegistry`, which creates spans for every completed observation. + + + +[[actuator.micrometer-tracing.creating-spans]] +== Creating Custom Spans + +You can create your own spans by starting an observation. +For this, inject `ObservationRegistry` into your component: + +include-code::CustomObservation[] + +This will create an observation named "some-operation" with the tag "some-tag=some-value". + +TIP: If you want to create a span without creating a metric, you need to use the {url-micrometer-tracing-docs}/api[lower-level `Tracer` API] from Micrometer. + + + +[[actuator.micrometer-tracing.baggage]] +== Baggage + +You can create baggage with the `Tracer` API: + +include-code::CreatingBaggage[] + +This example creates baggage named `baggage1` with the value `value1`. +The baggage is automatically propagated over the network if you're using W3C propagation. +If you're using B3 propagation, baggage is not automatically propagated. +To manually propagate baggage over the network, use the configprop:management.tracing.baggage.remote-fields[] configuration property (this works for W3C, too). +For the example above, setting this property to `baggage1` results in an HTTP header `baggage1: value1`. + +If you want to propagate the baggage to the MDC, use the configprop:management.tracing.baggage.correlation.fields[] configuration property. +For the example above, setting this property to `baggage1` results in an MDC entry named `baggage1`. + + + +[[actuator.micrometer-tracing.tests]] +== Tests + +Tracing components which are reporting data are not auto-configured when using `@SpringBootTest`. +See xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.tracing[] for more details. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/data/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/data/index.adoc new file mode 100644 index 000000000000..b6e87cc5fd50 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/data/index.adoc @@ -0,0 +1,4 @@ +[[data]] += Data + +Spring Boot integrates with a number of data technologies, both SQL and NoSQL. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/data/nosql.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/data/nosql.adoc new file mode 100644 index 000000000000..b55017e77082 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/data/nosql.adoc @@ -0,0 +1,712 @@ +[[data.nosql]] += Working with NoSQL Technologies + +Spring Data provides additional projects that help you access a variety of NoSQL technologies, including: + +* {url-spring-data-cassandra-site}[Cassandra] +* {url-spring-data-couchbase-site}[Couchbase] +* {url-spring-data-elasticsearch-site}[Elasticsearch] +* {url-spring-data-gemfire-site}[GemFire] or {url-spring-data-geode-site}[Geode] +* {url-spring-data-ldap-site}[LDAP] +* {url-spring-data-mongodb-site}[MongoDB] +* {url-spring-data-neo4j-site}[Neo4J] +* {url-spring-data-redis-site}[Redis] + +Of these, Spring Boot provides auto-configuration for Cassandra, Couchbase, Elasticsearch, LDAP, MongoDB, Neo4J and Redis. +Additionally, {url-spring-boot-for-apache-geode-site}[Spring Boot for Apache Geode] provides {url-spring-boot-for-apache-geode-docs}#geode-repositories[auto-configuration for Apache Geode]. +You can make use of the other projects, but you must configure them yourself. +See the appropriate reference documentation at {url-spring-data-site}. + +Spring Boot also provides auto-configuration for the InfluxDB client but it is deprecated in favor of https://github.com/influxdata/influxdb-client-java[the new InfluxDB Java client] that provides its own Spring Boot integration. + + + +[[data.nosql.redis]] +== Redis + +https://redis.io/[Redis] is a cache, message broker, and richly-featured key-value store. +Spring Boot offers basic auto-configuration for the https://github.com/lettuce-io/lettuce-core/[Lettuce] and https://github.com/xetorthio/jedis/[Jedis] client libraries and the abstractions on top of them provided by https://github.com/spring-projects/spring-data-redis[Spring Data Redis]. + +There is a `spring-boot-starter-data-redis` starter for collecting the dependencies in a convenient way. +By default, it uses https://github.com/lettuce-io/lettuce-core/[Lettuce]. +That starter handles both traditional and reactive applications. + +TIP: We also provide a `spring-boot-starter-data-redis-reactive` starter for consistency with the other stores with reactive support. + + + +[[data.nosql.redis.connecting]] +=== Connecting to Redis + +You can inject an auto-configured `RedisConnectionFactory`, `StringRedisTemplate`, or vanilla `RedisTemplate` instance as you would any other Spring Bean. +The following listing shows an example of such a bean: + +include-code::MyBean[] + +By default, the instance tries to connect to a Redis server at `localhost:6379`. +You can specify custom connection details using `spring.data.redis.*` properties, as shown in the following example: + +[configprops,yaml] +---- +spring: + data: + redis: + host: "localhost" + port: 6379 + database: 0 + username: "user" + password: "secret" +---- + +TIP: You can also register an arbitrary number of beans that implement `LettuceClientConfigurationBuilderCustomizer` for more advanced customizations. +`ClientResources` can also be customized using `ClientResourcesBuilderCustomizer`. +If you use Jedis, `JedisClientConfigurationBuilderCustomizer` is also available. +Alternatively, you can register a bean of type `RedisStandaloneConfiguration`, `RedisSentinelConfiguration`, or `RedisClusterConfiguration` to take full control over the configuration. + +If you add your own `@Bean` of any of the auto-configured types, it replaces the default (except in the case of `RedisTemplate`, when the exclusion is based on the bean name, `redisTemplate`, not its type). + +By default, a pooled connection factory is auto-configured if `commons-pool2` is on the classpath. + +The auto-configured `RedisConnectionFactory` can be configured to use SSL for communication with the server by setting the properties as shown in this example: + +[configprops,yaml] +---- +spring: + data: + redis: + ssl: + enabled: true +---- + +Custom SSL trust material can be configured in an xref:features/ssl.adoc[SSL bundle] and applied to the `RedisConnectionFactory` as shown in this example: + +[configprops,yaml] +---- +spring: + data: + redis: + ssl: + bundle: "example" +---- + + + +[[data.nosql.mongodb]] +== MongoDB + +https://www.mongodb.com/[MongoDB] is an open-source NoSQL document database that uses a JSON-like schema instead of traditional table-based relational data. +Spring Boot offers several conveniences for working with MongoDB, including the `spring-boot-starter-data-mongodb` and `spring-boot-starter-data-mongodb-reactive` starters. + + + +[[data.nosql.mongodb.connecting]] +=== Connecting to a MongoDB Database + +To access MongoDB databases, you can inject an auto-configured `org.springframework.data.mongodb.MongoDatabaseFactory`. +By default, the instance tries to connect to a MongoDB server at `mongodb://localhost/test`. +The following example shows how to connect to a MongoDB database: + +include-code::MyBean[] + +If you have defined your own `MongoClient`, it will be used to auto-configure a suitable `MongoDatabaseFactory`. + +The auto-configured `MongoClient` is created using a `MongoClientSettings` bean. +If you have defined your own `MongoClientSettings`, it will be used without modification and the `spring.data.mongodb` properties will be ignored. +Otherwise a `MongoClientSettings` will be auto-configured and will have the `spring.data.mongodb` properties applied to it. +In either case, you can declare one or more `MongoClientSettingsBuilderCustomizer` beans to fine-tune the `MongoClientSettings` configuration. +Each will be called in order with the `MongoClientSettings.Builder` that is used to build the `MongoClientSettings`. + +You can set the configprop:spring.data.mongodb.uri[] property to change the URL and configure additional settings such as the _replica set_, as shown in the following example: + +[configprops,yaml] +---- +spring: + data: + mongodb: + uri: "mongodb://user:secret@mongoserver1.example.com:27017,mongoserver2.example.com:23456/test" +---- + +Alternatively, you can specify connection details using discrete properties. +For example, you might declare the following settings in your `application.properties`: + +[configprops,yaml] +---- +spring: + data: + mongodb: + host: "mongoserver1.example.com" + port: 27017 + additional-hosts: + - "mongoserver2.example.com:23456" + database: "test" + username: "user" + password: "secret" +---- + +The auto-configured `MongoClient` can be configured to use SSL for communication with the server by setting the properties as shown in this example: + +[configprops,yaml] +---- +spring: + data: + mongodb: + uri: "mongodb://user:secret@mongoserver1.example.com:27017,mongoserver2.example.com:23456/test" + ssl: + enabled: true +---- + +Custom SSL trust material can be configured in an xref:features/ssl.adoc[SSL bundle] and applied to the `MongoClient` as shown in this example: + +[configprops,yaml] +---- +spring: + data: + mongodb: + uri: "mongodb://user:secret@mongoserver1.example.com:27017,mongoserver2.example.com:23456/test" + ssl: + bundle: "example" +---- + + +[TIP] +==== +If `spring.data.mongodb.port` is not specified, the default of `27017` is used. +You could delete this line from the example shown earlier. + +You can also specify the port as part of the host address by using the `host:port` syntax. +This format should be used if you need to change the port of an `additional-hosts` entry. +==== + +TIP: If you do not use Spring Data MongoDB, you can inject a `MongoClient` bean instead of using `MongoDatabaseFactory`. +If you want to take complete control of establishing the MongoDB connection, you can also declare your own `MongoDatabaseFactory` or `MongoClient` bean. + +NOTE: If you are using the reactive driver, Netty is required for SSL. +The auto-configuration configures this factory automatically if Netty is available and the factory to use has not been customized already. + + + +[[data.nosql.mongodb.template]] +=== MongoTemplate + +{url-spring-data-mongodb-site}[Spring Data MongoDB] provides a {url-spring-data-mongodb-javadoc}/org/springframework/data/mongodb/core/MongoTemplate.html[`MongoTemplate`] class that is very similar in its design to Spring's `JdbcTemplate`. +As with `JdbcTemplate`, Spring Boot auto-configures a bean for you to inject the template, as follows: + +include-code::MyBean[] + +See the {url-spring-data-mongodb-javadoc}/org/springframework/data/mongodb/core/MongoOperations.html[`MongoOperations`] API documentation for complete details. + + + +[[data.nosql.mongodb.repositories]] +=== Spring Data MongoDB Repositories + +Spring Data includes repository support for MongoDB. +As with the JPA repositories discussed earlier, the basic principle is that queries are constructed automatically, based on method names. + +In fact, both Spring Data JPA and Spring Data MongoDB share the same common infrastructure. +You could take the JPA example from earlier and, assuming that `City` is now a MongoDB data class rather than a JPA `@Entity`, it works in the same way, as shown in the following example: + +include-code::CityRepository[] + +Repositories and documents are found through scanning. +By default, the xref:using/auto-configuration.adoc#using.auto-configuration.packages[auto-configuration packages] are scanned. +You can customize the locations to look for repositories and documents by using `@EnableMongoRepositories` and `@EntityScan` respectively. + +TIP: For complete details of Spring Data MongoDB, including its rich object mapping technologies, see its {url-spring-data-mongodb-docs}[reference documentation]. + + + +[[data.nosql.neo4j]] +== Neo4j + +https://neo4j.com/[Neo4j] is an open-source NoSQL graph database that uses a rich data model of nodes connected by first class relationships, which is better suited for connected big data than traditional RDBMS approaches. +Spring Boot offers several conveniences for working with Neo4j, including the `spring-boot-starter-data-neo4j` starter. + + + +[[data.nosql.neo4j.connecting]] +=== Connecting to a Neo4j Database + +To access a Neo4j server, you can inject an auto-configured `org.neo4j.driver.Driver`. +By default, the instance tries to connect to a Neo4j server at `localhost:7687` using the Bolt protocol. +The following example shows how to inject a Neo4j `Driver` that gives you access, amongst other things, to a `Session`: + +include-code::MyBean[] + +You can configure various aspects of the driver using `spring.neo4j.*` properties. +The following example shows how to configure the uri and credentials to use: + +[configprops,yaml] +---- +spring: + neo4j: + uri: "bolt://my-server:7687" + authentication: + username: "neo4j" + password: "secret" +---- + +The auto-configured `Driver` is created using `ConfigBuilder`. +To fine-tune its configuration, declare one or more `ConfigBuilderCustomizer` beans. +Each will be called in order with the `ConfigBuilder` that is used to build the `Driver`. + + + +[[data.nosql.neo4j.repositories]] +=== Spring Data Neo4j Repositories + +Spring Data includes repository support for Neo4j. +For complete details of Spring Data Neo4j, see the {url-spring-data-neo4j-docs}[reference documentation]. + +Spring Data Neo4j shares the common infrastructure with Spring Data JPA as many other Spring Data modules do. +You could take the JPA example from earlier and define `City` as Spring Data Neo4j `@Node` rather than JPA `@Entity` and the repository abstraction works in the same way, as shown in the following example: + +include-code::CityRepository[] + +The `spring-boot-starter-data-neo4j` starter enables the repository support as well as transaction management. +Spring Boot supports both classic and reactive Neo4j repositories, using the `Neo4jTemplate` or `ReactiveNeo4jTemplate` beans. +When Project Reactor is available on the classpath, the reactive style is also auto-configured. + +Repositories and entities are found through scanning. +By default, the xref:using/auto-configuration.adoc#using.auto-configuration.packages[auto-configuration packages] are scanned. +You can customize the locations to look for repositories and entities by using `@EnableNeo4jRepositories` and `@EntityScan` respectively. + +[NOTE] +==== +In an application using the reactive style, a `ReactiveTransactionManager` is not auto-configured. +To enable transaction management, the following bean must be defined in your configuration: + +include-code::MyNeo4jConfiguration[] +==== + + + +[[data.nosql.elasticsearch]] +== Elasticsearch + +https://www.elastic.co/products/elasticsearch[Elasticsearch] is an open source, distributed, RESTful search and analytics engine. +Spring Boot offers basic auto-configuration for Elasticsearch clients. + +Spring Boot supports several clients: + +* The official low-level REST client +* The official Java API client +* The `ReactiveElasticsearchClient` provided by Spring Data Elasticsearch + +Spring Boot provides a dedicated starter, `spring-boot-starter-data-elasticsearch`. + + + +[[data.nosql.elasticsearch.connecting-using-rest]] +=== Connecting to Elasticsearch Using REST clients + +Elasticsearch ships two different REST clients that you can use to query a cluster: the https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/java-rest-low.html[low-level client] from the `org.elasticsearch.client:elasticsearch-rest-client` module and the https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html[Java API client] from the `co.elastic.clients:elasticsearch-java` module. +Additionally, Spring Boot provides support for a reactive client from the `org.springframework.data:spring-data-elasticsearch` module. +By default, the clients will target `http://localhost:9200`. +You can use `spring.elasticsearch.*` properties to further tune how the clients are configured, as shown in the following example: + +[configprops,yaml] +---- +spring: + elasticsearch: + uris: "https://search.example.com:9200" + socket-timeout: "10s" + username: "user" + password: "secret" +---- + + + +[[data.nosql.elasticsearch.connecting-using-rest.restclient]] +==== Connecting to Elasticsearch Using RestClient + +If you have `elasticsearch-rest-client` on the classpath, Spring Boot will auto-configure and register a `RestClient` bean. +In addition to the properties described previously, to fine-tune the `RestClient` you can register an arbitrary number of beans that implement `RestClientBuilderCustomizer` for more advanced customizations. +To take full control over the clients' configuration, define a `RestClientBuilder` bean. + + + +Additionally, if `elasticsearch-rest-client-sniffer` is on the classpath, a `Sniffer` is auto-configured to automatically discover nodes from a running Elasticsearch cluster and set them on the `RestClient` bean. +You can further tune how `Sniffer` is configured, as shown in the following example: + +[configprops,yaml] +---- +spring: + elasticsearch: + restclient: + sniffer: + interval: "10m" + delay-after-failure: "30s" +---- + + + +[[data.nosql.elasticsearch.connecting-using-rest.javaapiclient]] +==== Connecting to Elasticsearch Using ElasticsearchClient + +If you have `co.elastic.clients:elasticsearch-java` on the classpath, Spring Boot will auto-configure and register an `ElasticsearchClient` bean. + +The `ElasticsearchClient` uses a transport that depends upon the previously described `RestClient`. +Therefore, the properties described previously can be used to configure the `ElasticsearchClient`. +Furthermore, you can define a `RestClientOptions` bean to take further control of the behavior of the transport. + + + +[[data.nosql.elasticsearch.connecting-using-rest.reactiveclient]] +==== Connecting to Elasticsearch using ReactiveElasticsearchClient + +{url-spring-data-elasticsearch-site}[Spring Data Elasticsearch] ships `ReactiveElasticsearchClient` for querying Elasticsearch instances in a reactive fashion. +If you have Spring Data Elasticsearch and Reactor on the classpath, Spring Boot will auto-configure and register a `ReactiveElasticsearchClient`. + +The `ReactiveElasticsearchclient` uses a transport that depends upon the previously described `RestClient`. +Therefore, the properties described previously can be used to configure the `ReactiveElasticsearchClient`. +Furthermore, you can define a `RestClientOptions` bean to take further control of the behavior of the transport. + + + +[[data.nosql.elasticsearch.connecting-using-spring-data]] +=== Connecting to Elasticsearch by Using Spring Data + +To connect to Elasticsearch, an `ElasticsearchClient` bean must be defined, +auto-configured by Spring Boot or manually provided by the application (see previous sections). +With this configuration in place, an +`ElasticsearchTemplate` can be injected like any other Spring bean, +as shown in the following example: + +include-code::MyBean[] + +In the presence of `spring-data-elasticsearch` and Reactor, Spring Boot can also auto-configure a xref:data/nosql.adoc#data.nosql.elasticsearch.connecting-using-rest.reactiveclient[`ReactiveElasticsearchClient`] and a `ReactiveElasticsearchTemplate` as beans. +They are the reactive equivalent of the other REST clients. + + + +[[data.nosql.elasticsearch.repositories]] +=== Spring Data Elasticsearch Repositories + +Spring Data includes repository support for Elasticsearch. +As with the JPA repositories discussed earlier, the basic principle is that queries are constructed for you automatically based on method names. + +In fact, both Spring Data JPA and Spring Data Elasticsearch share the same common infrastructure. +You could take the JPA example from earlier and, assuming that `City` is now an Elasticsearch `@Document` class rather than a JPA `@Entity`, it works in the same way. + +Repositories and documents are found through scanning. +By default, the xref:using/auto-configuration.adoc#using.auto-configuration.packages[auto-configuration packages] are scanned. +You can customize the locations to look for repositories and documents by using `@EnableElasticsearchRepositories` and `@EntityScan` respectively. + +TIP: For complete details of Spring Data Elasticsearch, see the {url-spring-data-elasticsearch-docs}[reference documentation]. + +Spring Boot supports both classic and reactive Elasticsearch repositories, using the `ElasticsearchRestTemplate` or `ReactiveElasticsearchTemplate` beans. +Most likely those beans are auto-configured by Spring Boot given the required dependencies are present. + +If you wish to use your own template for backing the Elasticsearch repositories, you can add your own `ElasticsearchRestTemplate` or `ElasticsearchOperations` `@Bean`, as long as it is named `"elasticsearchTemplate"`. +Same applies to `ReactiveElasticsearchTemplate` and `ReactiveElasticsearchOperations`, with the bean name `"reactiveElasticsearchTemplate"`. + +You can choose to disable the repositories support with the following property: + +[configprops,yaml] +---- + spring: + data: + elasticsearch: + repositories: + enabled: false +---- + + + +[[data.nosql.cassandra]] +== Cassandra + +https://cassandra.apache.org/[Cassandra] is an open source, distributed database management system designed to handle large amounts of data across many commodity servers. +Spring Boot offers auto-configuration for Cassandra and the abstractions on top of it provided by {url-spring-data-cassandra-site}[Spring Data Cassandra]. +There is a `spring-boot-starter-data-cassandra` starter for collecting the dependencies in a convenient way. + + + +[[data.nosql.cassandra.connecting]] +=== Connecting to Cassandra + +You can inject an auto-configured `CassandraTemplate` or a Cassandra `CqlSession` instance as you would with any other Spring Bean. +The `spring.cassandra.*` properties can be used to customize the connection. +Generally, you provide `keyspace-name` and `contact-points` as well the local datacenter name, as shown in the following example: + +[configprops,yaml] +---- +spring: + cassandra: + keyspace-name: "mykeyspace" + contact-points: "cassandrahost1:9042,cassandrahost2:9042" + local-datacenter: "datacenter1" +---- + +If the port is the same for all your contact points you can use a shortcut and only specify the host names, as shown in the following example: + +[configprops,yaml] +---- +spring: + cassandra: + keyspace-name: "mykeyspace" + contact-points: "cassandrahost1,cassandrahost2" + local-datacenter: "datacenter1" +---- + +TIP: Those two examples are identical as the port default to `9042`. +If you need to configure the port, use `spring.cassandra.port`. + +The auto-configured `CqlSession` can be configured to use SSL for communication with the server by setting the properties as shown in this example: + +[configprops,yaml] +---- +spring: + cassandra: + keyspace-name: "mykeyspace" + contact-points: "cassandrahost1,cassandrahost2" + local-datacenter: "datacenter1" + ssl: + enabled: true +---- + +Custom SSL trust material can be configured in an xref:features/ssl.adoc[SSL bundle] and applied to the `CqlSession` as shown in this example: + +[configprops,yaml] +---- +spring: + cassandra: + keyspace-name: "mykeyspace" + contact-points: "cassandrahost1,cassandrahost2" + local-datacenter: "datacenter1" + ssl: + bundle: "example" +---- + + +[NOTE] +==== +The Cassandra driver has its own configuration infrastructure that loads an `application.conf` at the root of the classpath. + +Spring Boot does not look for such a file by default but can load one using `spring.cassandra.config`. +If a property is both present in `+spring.cassandra.*+` and the configuration file, the value in `+spring.cassandra.*+` takes precedence. + +For more advanced driver customizations, you can register an arbitrary number of beans that implement `DriverConfigLoaderBuilderCustomizer`. +The `CqlSession` can be customized with a bean of type `CqlSessionBuilderCustomizer`. +==== + +NOTE: If you use `CqlSessionBuilder` to create multiple `CqlSession` beans, keep in mind the builder is mutable so make sure to inject a fresh copy for each session. + +The following code listing shows how to inject a Cassandra bean: + +include-code::MyBean[] + +If you add your own `@Bean` of type `CassandraTemplate`, it replaces the default. + + + +[[data.nosql.cassandra.repositories]] +=== Spring Data Cassandra Repositories + +Spring Data includes basic repository support for Cassandra. +Currently, this is more limited than the JPA repositories discussed earlier and needs `@Query` annotated finder methods. + +Repositories and entities are found through scanning. +By default, the xref:using/auto-configuration.adoc#using.auto-configuration.packages[auto-configuration packages] are scanned. +You can customize the locations to look for repositories and entities by using `@EnableCassandraRepositories` and `@EntityScan` respectively. + +TIP: For complete details of Spring Data Cassandra, see the https://docs.spring.io/spring-data/cassandra/docs/[reference documentation]. + + + +[[data.nosql.couchbase]] +== Couchbase + +https://www.couchbase.com/[Couchbase] is an open-source, distributed, multi-model NoSQL document-oriented database that is optimized for interactive applications. +Spring Boot offers auto-configuration for Couchbase and the abstractions on top of it provided by https://github.com/spring-projects/spring-data-couchbase[Spring Data Couchbase]. +There are `spring-boot-starter-data-couchbase` and `spring-boot-starter-data-couchbase-reactive` starters for collecting the dependencies in a convenient way. + + + +[[data.nosql.couchbase.connecting]] +=== Connecting to Couchbase + +You can get a `Cluster` by adding the Couchbase SDK and some configuration. +The `spring.couchbase.*` properties can be used to customize the connection. +Generally, you provide the https://github.com/couchbaselabs/sdk-rfcs/blob/master/rfc/0011-connection-string.md[connection string], username, and password, as shown in the following example: + +[configprops,yaml] +---- +spring: + couchbase: + connection-string: "couchbase://192.168.1.123" + username: "user" + password: "secret" +---- + +It is also possible to customize some of the `ClusterEnvironment` settings. +For instance, the following configuration changes the timeout to open a new `Bucket` and enables SSL support with a reference to a configured xref:features/ssl.adoc[SSL bundle]: + +[configprops,yaml] +---- +spring: + couchbase: + env: + timeouts: + connect: "3s" + ssl: + bundle: "example" +---- + +TIP: Check the `spring.couchbase.env.*` properties for more details. +To take more control, one or more `ClusterEnvironmentBuilderCustomizer` beans can be used. + + + +[[data.nosql.couchbase.repositories]] +=== Spring Data Couchbase Repositories + +Spring Data includes repository support for Couchbase. + +Repositories and documents are found through scanning. +By default, the xref:using/auto-configuration.adoc#using.auto-configuration.packages[auto-configuration packages] are scanned. +You can customize the locations to look for repositories and documents by using `@EnableCouchbaseRepositories` and `@EntityScan` respectively. + +For complete details of Spring Data Couchbase, see the {url-spring-data-couchbase-docs}[reference documentation]. + +You can inject an auto-configured `CouchbaseTemplate` instance as you would with any other Spring Bean, provided a `CouchbaseClientFactory` bean is available. +This happens when a `Cluster` is available, as described above, and a bucket name has been specified: + +[configprops,yaml] +---- +spring: + data: + couchbase: + bucket-name: "my-bucket" +---- + +The following examples shows how to inject a `CouchbaseTemplate` bean: + +include-code::MyBean[] + +There are a few beans that you can define in your own configuration to override those provided by the auto-configuration: + +* A `CouchbaseMappingContext` `@Bean` with a name of `couchbaseMappingContext`. +* A `CustomConversions` `@Bean` with a name of `couchbaseCustomConversions`. +* A `CouchbaseTemplate` `@Bean` with a name of `couchbaseTemplate`. + +To avoid hard-coding those names in your own config, you can reuse `BeanNames` provided by Spring Data Couchbase. +For instance, you can customize the converters to use, as follows: + +include-code::MyCouchbaseConfiguration[] + + + +[[data.nosql.ldap]] +== LDAP + +https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol[LDAP] (Lightweight Directory Access Protocol) is an open, vendor-neutral, industry standard application protocol for accessing and maintaining distributed directory information services over an IP network. +Spring Boot offers auto-configuration for any compliant LDAP server as well as support for the embedded in-memory LDAP server from https://ldap.com/unboundid-ldap-sdk-for-java/[UnboundID]. + +LDAP abstractions are provided by https://github.com/spring-projects/spring-data-ldap[Spring Data LDAP]. +There is a `spring-boot-starter-data-ldap` starter for collecting the dependencies in a convenient way. + + + +[[data.nosql.ldap.connecting]] +=== Connecting to an LDAP Server + +To connect to an LDAP server, make sure you declare a dependency on the `spring-boot-starter-data-ldap` starter or `spring-ldap-core` and then declare the URLs of your server in your application.properties, as shown in the following example: + +[configprops,yaml] +---- +spring: + ldap: + urls: "ldap://myserver:1235" + username: "admin" + password: "secret" +---- + +If you need to customize connection settings, you can use the `spring.ldap.base` and `spring.ldap.base-environment` properties. + +An `LdapContextSource` is auto-configured based on these settings. +If a `DirContextAuthenticationStrategy` bean is available, it is associated to the auto-configured `LdapContextSource`. +If you need to customize it, for instance to use a `PooledContextSource`, you can still inject the auto-configured `LdapContextSource`. +Make sure to flag your customized `ContextSource` as `@Primary` so that the auto-configured `LdapTemplate` uses it. + + + +[[data.nosql.ldap.repositories]] +=== Spring Data LDAP Repositories + +Spring Data includes repository support for LDAP. + +Repositories and documents are found through scanning. +By default, the xref:using/auto-configuration.adoc#using.auto-configuration.packages[auto-configuration packages] are scanned. +You can customize the locations to look for repositories and documents by using `@EnableLdapRepositories` and `@EntityScan` respectively. + +For complete details of Spring Data LDAP, see the https://docs.spring.io/spring-data/ldap/docs/1.0.x/reference/html/[reference documentation]. + +You can also inject an auto-configured `LdapTemplate` instance as you would with any other Spring Bean, as shown in the following example: + + +include-code::MyBean[] + + + +[[data.nosql.ldap.embedded]] +=== Embedded In-memory LDAP Server + +For testing purposes, Spring Boot supports auto-configuration of an in-memory LDAP server from https://ldap.com/unboundid-ldap-sdk-for-java/[UnboundID]. +To configure the server, add a dependency to `com.unboundid:unboundid-ldapsdk` and declare a configprop:spring.ldap.embedded.base-dn[] property, as follows: + +[configprops,yaml] +---- +spring: + ldap: + embedded: + base-dn: "dc=spring,dc=io" +---- + +[NOTE] +==== +It is possible to define multiple base-dn values, however, since distinguished names usually contain commas, they must be defined using the correct notation. + +In yaml files, you can use the yaml list notation. In properties files, you must include the index as part of the property name: + +[configprops,yaml] +---- +spring.ldap.embedded.base-dn: +- "dc=spring,dc=io" +- "dc=vmware,dc=com" +---- +==== + +By default, the server starts on a random port and triggers the regular LDAP support. +There is no need to specify a configprop:spring.ldap.urls[] property. + +If there is a `schema.ldif` file on your classpath, it is used to initialize the server. +If you want to load the initialization script from a different resource, you can also use the configprop:spring.ldap.embedded.ldif[] property. + +By default, a standard schema is used to validate `LDIF` files. +You can turn off validation altogether by setting the configprop:spring.ldap.embedded.validation.enabled[] property. +If you have custom attributes, you can use configprop:spring.ldap.embedded.validation.schema[] to define your custom attribute types or object classes. + + + +[[data.nosql.influxdb]] +== InfluxDB + +WARNING: Auto-configuration for InfluxDB is deprecated and scheduled for removal in Spring Boot 3.4 in favor of https://github.com/influxdata/influxdb-client-java[the new InfluxDB Java client] that provides its own Spring Boot integration. + +https://www.influxdata.com/[InfluxDB] is an open-source time series database optimized for fast, high-availability storage and retrieval of time series data in fields such as operations monitoring, application metrics, Internet-of-Things sensor data, and real-time analytics. + + + +[[data.nosql.influxdb.connecting]] +=== Connecting to InfluxDB + +Spring Boot auto-configures an `InfluxDB` instance, provided the `influxdb-java` client is on the classpath and the URL of the database is set using configprop:spring.influx.url[deprecated]. + +If the connection to InfluxDB requires a user and password, you can set the configprop:spring.influx.user[deprecated] and configprop:spring.influx.password[deprecated] properties accordingly. + +InfluxDB relies on OkHttp. +If you need to tune the http client `InfluxDB` uses behind the scenes, you can register an `InfluxDbOkHttpClientBuilderProvider` bean. + +If you need more control over the configuration, consider registering an `InfluxDbCustomizer` bean. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/data/sql.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/data/sql.adoc new file mode 100644 index 000000000000..fdcd24048fce --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/data/sql.adoc @@ -0,0 +1,553 @@ +[[data.sql]] += SQL Databases + +The {url-spring-framework-site}[Spring Framework] provides extensive support for working with SQL databases, from direct JDBC access using `JdbcClient` or `JdbcTemplate` to complete "`object relational mapping`" technologies such as Hibernate. +{url-spring-data-site}[Spring Data] provides an additional level of functionality: creating `Repository` implementations directly from interfaces and using conventions to generate queries from your method names. + + + +[[data.sql.datasource]] +== Configure a DataSource + +Java's `javax.sql.DataSource` interface provides a standard method of working with database connections. +Traditionally, a `DataSource` uses a `URL` along with some credentials to establish a database connection. + +TIP: See the xref:how-to:data-access.adoc#howto.data-access.configure-custom-datasource[] section of the "`How-to Guides`" for more advanced examples, typically to take full control over the configuration of the DataSource. + + + +[[data.sql.datasource.embedded]] +=== Embedded Database Support + +It is often convenient to develop applications by using an in-memory embedded database. +Obviously, in-memory databases do not provide persistent storage. +You need to populate your database when your application starts and be prepared to throw away data when your application ends. + +TIP: The "`How-to Guides`" section includes a xref:how-to:data-initialization.adoc[section on how to initialize a database]. + +Spring Boot can auto-configure embedded https://www.h2database.com[H2], https://hsqldb.org/[HSQL], and https://db.apache.org/derby/[Derby] databases. +You need not provide any connection URLs. +You need only include a build dependency to the embedded database that you want to use. +If there are multiple embedded databases on the classpath, set the configprop:spring.datasource.embedded-database-connection[] configuration property to control which one is used. +Setting the property to `none` disables auto-configuration of an embedded database. + +[NOTE] +==== +If you are using this feature in your tests, you may notice that the same database is reused by your whole test suite regardless of the number of application contexts that you use. +If you want to make sure that each context has a separate embedded database, you should set `spring.datasource.generate-unique-name` to `true`. +==== + +For example, the typical POM dependencies would be as follows: + +[source,xml] +---- + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.hsqldb + hsqldb + runtime + +---- + +NOTE: You need a dependency on `spring-jdbc` for an embedded database to be auto-configured. +In this example, it is pulled in transitively through `spring-boot-starter-data-jpa`. + +TIP: If, for whatever reason, you do configure the connection URL for an embedded database, take care to ensure that the database's automatic shutdown is disabled. +If you use H2, you should use `DB_CLOSE_ON_EXIT=FALSE` to do so. +If you use HSQLDB, you should ensure that `shutdown=true` is not used. +Disabling the database's automatic shutdown lets Spring Boot control when the database is closed, thereby ensuring that it happens once access to the database is no longer needed. + + + +[[data.sql.datasource.production]] +=== Connection to a Production Database + +Production database connections can also be auto-configured by using a pooling `DataSource`. + + + +[[data.sql.datasource.configuration]] +=== DataSource Configuration + +DataSource configuration is controlled by external configuration properties in `+spring.datasource.*+`. +For example, you might declare the following section in `application.properties`: + +[configprops,yaml] +---- +spring: + datasource: + url: "jdbc:mysql://localhost/test" + username: "dbuser" + password: "dbpass" +---- + +NOTE: You should at least specify the URL by setting the configprop:spring.datasource.url[] property. +Otherwise, Spring Boot tries to auto-configure an embedded database. + +TIP: Spring Boot can deduce the JDBC driver class for most databases from the URL. +If you need to specify a specific class, you can use the configprop:spring.datasource.driver-class-name[] property. + +NOTE: For a pooling `DataSource` to be created, we need to be able to verify that a valid `Driver` class is available, so we check for that before doing anything. +In other words, if you set `spring.datasource.driver-class-name=com.mysql.jdbc.Driver`, then that class has to be loadable. + +See xref:api:java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.html[`DataSourceProperties`] API documentation for more of the supported options. +These are the standard options that work regardless of xref:data/sql.adoc#data.sql.datasource.connection-pool[the actual implementation]. +It is also possible to fine-tune implementation-specific settings by using their respective prefix (`+spring.datasource.hikari.*+`, `+spring.datasource.tomcat.*+`, `+spring.datasource.dbcp2.*+`, and `+spring.datasource.oracleucp.*+`). +See the documentation of the connection pool implementation you are using for more details. + +For instance, if you use the {url-tomcat-docs}/jdbc-pool.html#Common_Attributes[Tomcat connection pool], you could customize many additional settings, as shown in the following example: + +[configprops,yaml] +---- +spring: + datasource: + tomcat: + max-wait: 10000 + max-active: 50 + test-on-borrow: true +---- + +This will set the pool to wait 10000ms before throwing an exception if no connection is available, limit the maximum number of connections to 50 and validate the connection before borrowing it from the pool. + + + +[[data.sql.datasource.connection-pool]] +=== Supported Connection Pools + +Spring Boot uses the following algorithm for choosing a specific implementation: + +. We prefer https://github.com/brettwooldridge/HikariCP[HikariCP] for its performance and concurrency. +If HikariCP is available, we always choose it. +. Otherwise, if the Tomcat pooling `DataSource` is available, we use it. +. Otherwise, if https://commons.apache.org/proper/commons-dbcp/[Commons DBCP2] is available, we use it. +. If none of HikariCP, Tomcat, and DBCP2 are available and if Oracle UCP is available, we use it. + +NOTE: If you use the `spring-boot-starter-jdbc` or `spring-boot-starter-data-jpa` starters, you automatically get a dependency to `HikariCP`. + +You can bypass that algorithm completely and specify the connection pool to use by setting the configprop:spring.datasource.type[] property. +This is especially important if you run your application in a Tomcat container, as `tomcat-jdbc` is provided by default. + +Additional connection pools can always be configured manually, using `DataSourceBuilder`. +If you define your own `DataSource` bean, auto-configuration does not occur. +The following connection pools are supported by `DataSourceBuilder`: + +* HikariCP +* Tomcat pooling `DataSource` +* Commons DBCP2 +* Oracle UCP & `OracleDataSource` +* Spring Framework's `SimpleDriverDataSource` +* H2 `JdbcDataSource` +* PostgreSQL `PGSimpleDataSource` +* C3P0 + + + +[[data.sql.datasource.jndi]] +=== Connection to a JNDI DataSource + +If you deploy your Spring Boot application to an Application Server, you might want to configure and manage your DataSource by using your Application Server's built-in features and access it by using JNDI. + +The configprop:spring.datasource.jndi-name[] property can be used as an alternative to the configprop:spring.datasource.url[], configprop:spring.datasource.username[], and configprop:spring.datasource.password[] properties to access the `DataSource` from a specific JNDI location. +For example, the following section in `application.properties` shows how you can access a JBoss AS defined `DataSource`: + +[configprops,yaml] +---- +spring: + datasource: + jndi-name: "java:jboss/datasources/customers" +---- + + + +[[data.sql.jdbc-template]] +== Using JdbcTemplate + +Spring's `JdbcTemplate` and `NamedParameterJdbcTemplate` classes are auto-configured, and you can `@Autowire` them directly into your own beans, as shown in the following example: + +include-code::MyBean[] + +You can customize some properties of the template by using the `spring.jdbc.template.*` properties, as shown in the following example: + +[configprops,yaml] +---- +spring: + jdbc: + template: + max-rows: 500 +---- + +NOTE: The `NamedParameterJdbcTemplate` reuses the same `JdbcTemplate` instance behind the scenes. +If more than one `JdbcTemplate` is defined and no primary candidate exists, the `NamedParameterJdbcTemplate` is not auto-configured. + + + +[[data.sql.jdbc-client]] +== Using JdbcClient + +Spring's `JdbcClient` is auto-configured based on the presence of a `NamedParameterJdbcTemplate`. +You can inject it directly in your own beans as well, as shown in the following example: + +include-code::MyBean[] + +If you rely on auto-configuration to create the underlying `JdbcTemplate`, any customization using `spring.jdbc.template.*` properties is taken into account in the client as well. + + + +[[data.sql.jpa-and-spring-data]] +== JPA and Spring Data JPA + +The Java Persistence API is a standard technology that lets you "`map`" objects to relational databases. +The `spring-boot-starter-data-jpa` POM provides a quick way to get started. +It provides the following key dependencies: + +* Hibernate: One of the most popular JPA implementations. +* Spring Data JPA: Helps you to implement JPA-based repositories. +* Spring ORM: Core ORM support from the Spring Framework. + +TIP: We do not go into too many details of JPA or {url-spring-data-site}[Spring Data] here. +You can follow the https://spring.io/guides/gs/accessing-data-jpa/[Accessing Data with JPA] guide from https://spring.io and read the {url-spring-data-jpa-site}[Spring Data JPA] and https://hibernate.org/orm/documentation/[Hibernate] reference documentation. + + + +[[data.sql.jpa-and-spring-data.entity-classes]] +=== Entity Classes + +Traditionally, JPA "`Entity`" classes are specified in a `persistence.xml` file. +With Spring Boot, this file is not necessary and "`Entity Scanning`" is used instead. +By default the xref:using/auto-configuration.adoc#using.auto-configuration.packages[auto-configuration packages] are scanned. + +Any classes annotated with `@Entity`, `@Embeddable`, or `@MappedSuperclass` are considered. +A typical entity class resembles the following example: + +include-code::City[] + +TIP: You can customize entity scanning locations by using the `@EntityScan` annotation. +See the xref:how-to:data-access.adoc#howto.data-access.separate-entity-definitions-from-spring-configuration[] section of the "`How-to Guides`". + + + +[[data.sql.jpa-and-spring-data.repositories]] +=== Spring Data JPA Repositories + +{url-spring-data-jpa-site}[Spring Data JPA] repositories are interfaces that you can define to access data. +JPA queries are created automatically from your method names. +For example, a `CityRepository` interface might declare a `findAllByState(String state)` method to find all the cities in a given state. + +For more complex queries, you can annotate your method with Spring Data's {url-spring-data-jpa-javadoc}/org/springframework/data/jpa/repository/Query.html[`Query`] annotation. + +Spring Data repositories usually extend from the {url-spring-data-commons-javadoc}/org/springframework/data/repository/Repository.html[`Repository`] or {url-spring-data-commons-javadoc}/org/springframework/data/repository/CrudRepository.html[`CrudRepository`] interfaces. +If you use auto-configuration, the xref:using/auto-configuration.adoc#using.auto-configuration.packages[auto-configuration packages] are searched for repositories. + +TIP: You can customize the locations to look for repositories using `@EnableJpaRepositories`. + +The following example shows a typical Spring Data repository interface definition: + +include-code::CityRepository[] + +Spring Data JPA repositories support three different modes of bootstrapping: default, deferred, and lazy. +To enable deferred or lazy bootstrapping, set the configprop:spring.data.jpa.repositories.bootstrap-mode[] property to `deferred` or `lazy` respectively. +When using deferred or lazy bootstrapping, the auto-configured `EntityManagerFactoryBuilder` will use the context's `AsyncTaskExecutor`, if any, as the bootstrap executor. +If more than one exists, the one named `applicationTaskExecutor` will be used. + +[NOTE] +==== +When using deferred or lazy bootstrapping, make sure to defer any access to the JPA infrastructure after the application context bootstrap phase. +You can use `SmartInitializingSingleton` to invoke any initialization that requires the JPA infrastructure. +For JPA components (such as converters) that are created as Spring beans, use `ObjectProvider` to delay the resolution of dependencies, if any. +==== + +TIP: We have barely scratched the surface of Spring Data JPA. +For complete details, see the {url-spring-data-jpa-docs}[Spring Data JPA reference documentation]. + + + +[[data.sql.jpa-and-spring-data.envers-repositories]] +=== Spring Data Envers Repositories + +If {url-spring-data-envers-site}[Spring Data Envers] is available, JPA repositories are auto-configured to support typical Envers queries. + +To use Spring Data Envers, make sure your repository extends from `RevisionRepository` as shown in the following example: + +include-code::CountryRepository[] + +NOTE: For more details, check the {url-spring-data-jpa-docs}/envers.html[Spring Data Envers reference documentation]. + + + +[[data.sql.jpa-and-spring-data.creating-and-dropping]] +=== Creating and Dropping JPA Databases + +By default, JPA databases are automatically created *only* if you use an embedded database (H2, HSQL, or Derby). +You can explicitly configure JPA settings by using `+spring.jpa.*+` properties. +For example, to create and drop tables you can add the following line to your `application.properties`: + +[configprops,yaml] +---- +spring: + jpa: + hibernate.ddl-auto: "create-drop" +---- + +NOTE: Hibernate's own internal property name for this (if you happen to remember it better) is `hibernate.hbm2ddl.auto`. +You can set it, along with other Hibernate native properties, by using `+spring.jpa.properties.*+` (the prefix is stripped before adding them to the entity manager). +The following line shows an example of setting JPA properties for Hibernate: + +[configprops,yaml] +---- +spring: + jpa: + properties: + hibernate: + "globally_quoted_identifiers": "true" +---- + +The line in the preceding example passes a value of `true` for the `hibernate.globally_quoted_identifiers` property to the Hibernate entity manager. + +By default, the DDL execution (or validation) is deferred until the `ApplicationContext` has started. + + + +[[data.sql.jpa-and-spring-data.open-entity-manager-in-view]] +=== Open EntityManager in View + +If you are running a web application, Spring Boot by default registers {url-spring-framework-javadoc}/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.html[`OpenEntityManagerInViewInterceptor`] to apply the "`Open EntityManager in View`" pattern, to allow for lazy loading in web views. +If you do not want this behavior, you should set `spring.jpa.open-in-view` to `false` in your `application.properties`. + + + +[[data.sql.jdbc]] +== Spring Data JDBC + +Spring Data includes repository support for JDBC and will automatically generate SQL for the methods on `CrudRepository`. +For more advanced queries, a `@Query` annotation is provided. + +Spring Boot will auto-configure Spring Data's JDBC repositories when the necessary dependencies are on the classpath. +They can be added to your project with a single dependency on `spring-boot-starter-data-jdbc`. +If necessary, you can take control of Spring Data JDBC's configuration by adding the `@EnableJdbcRepositories` annotation or an `AbstractJdbcConfiguration` subclass to your application. + +TIP: For complete details of Spring Data JDBC, see the {url-spring-data-jdbc-docs}[reference documentation]. + + + +[[data.sql.h2-web-console]] +== Using H2's Web Console + +The https://www.h2database.com[H2 database] provides a https://www.h2database.com/html/quickstart.html#h2_console[browser-based console] that Spring Boot can auto-configure for you. +The console is auto-configured when the following conditions are met: + +* You are developing a servlet-based web application. +* `com.h2database:h2` is on the classpath. +* You are using xref:using/devtools.adoc[Spring Boot's developer tools]. + +TIP: If you are not using Spring Boot's developer tools but would still like to make use of H2's console, you can configure the configprop:spring.h2.console.enabled[] property with a value of `true`. + +NOTE: The H2 console is only intended for use during development, so you should take care to ensure that `spring.h2.console.enabled` is not set to `true` in production. + + + +[[data.sql.h2-web-console.custom-path]] +=== Changing the H2 Console's Path + +By default, the console is available at `/h2-console`. +You can customize the console's path by using the configprop:spring.h2.console.path[] property. + + + +[[data.sql.h2-web-console.spring-security]] +=== Accessing the H2 Console in a Secured Application + +H2 Console uses frames and, as it is intended for development only, does not implement CSRF protection measures. +If your application uses Spring Security, you need to configure it to + +* disable CSRF protection for requests against the console, +* set the header `X-Frame-Options` to `SAMEORIGIN` on responses from the console. + +More information on {url-spring-security-docs}/features/exploits/csrf.html[CSRF] and the header {url-spring-security-docs}/features/exploits/headers.html#headers-frame-options[X-Frame-Options] can be found in the Spring Security Reference Guide. + +In simple setups, a `SecurityFilterChain` like the following can be used: + +include-code::DevProfileSecurityConfiguration[tag=!customizer] + +WARNING: The H2 console is only intended for use during development. +In production, disabling CSRF protection or allowing frames for a website may create severe security risks. + +TIP: `PathRequest.toH2Console()` returns the correct request matcher also when the console's path has been customized. + + + +[[data.sql.jooq]] +== Using jOOQ + +jOOQ Object Oriented Querying (https://www.jooq.org/[jOOQ]) is a popular product from https://www.datageekery.com/[Data Geekery] which generates Java code from your database and lets you build type-safe SQL queries through its fluent API. +Both the commercial and open source editions can be used with Spring Boot. + + + +[[data.sql.jooq.codegen]] +=== Code Generation + +In order to use jOOQ type-safe queries, you need to generate Java classes from your database schema. +You can follow the instructions in the {url-jooq-docs}/#jooq-in-7-steps-step3[jOOQ user manual]. +If you use the `jooq-codegen-maven` plugin and you also use the `spring-boot-starter-parent` "`parent POM`", you can safely omit the plugin's `` tag. +You can also use Spring Boot-defined version variables (such as `h2.version`) to declare the plugin's database dependency. +The following listing shows an example: + +[source,xml] +---- + + org.jooq + jooq-codegen-maven + + ... + + + + com.h2database + h2 + ${h2.version} + + + + + org.h2.Driver + jdbc:h2:~/yourdatabase + + + ... + + + +---- + + + +[[data.sql.jooq.dslcontext]] +=== Using DSLContext + +The fluent API offered by jOOQ is initiated through the `org.jooq.DSLContext` interface. +Spring Boot auto-configures a `DSLContext` as a Spring Bean and connects it to your application `DataSource`. +To use the `DSLContext`, you can inject it, as shown in the following example: + +include-code::MyBean[tag=!method] + +TIP: The jOOQ manual tends to use a variable named `create` to hold the `DSLContext`. + +You can then use the `DSLContext` to construct your queries, as shown in the following example: + +include-code::MyBean[tag=method] + + + +[[data.sql.jooq.sqldialect]] +=== jOOQ SQL Dialect + +Unless the configprop:spring.jooq.sql-dialect[] property has been configured, Spring Boot determines the SQL dialect to use for your datasource. +If Spring Boot could not detect the dialect, it uses `DEFAULT`. + +NOTE: Spring Boot can only auto-configure dialects supported by the open source version of jOOQ. + + + +[[data.sql.jooq.customizing]] +=== Customizing jOOQ + +More advanced customizations can be achieved by defining your own `DefaultConfigurationCustomizer` bean that will be invoked prior to creating the `org.jooq.Configuration` `@Bean`. +This takes precedence to anything that is applied by the auto-configuration. + +You can also create your own `org.jooq.Configuration` `@Bean` if you want to take complete control of the jOOQ configuration. + + + +[[data.sql.r2dbc]] +== Using R2DBC + +The Reactive Relational Database Connectivity (https://r2dbc.io[R2DBC]) project brings reactive programming APIs to relational databases. +R2DBC's `io.r2dbc.spi.Connection` provides a standard method of working with non-blocking database connections. +Connections are provided by using a `ConnectionFactory`, similar to a `DataSource` with jdbc. + +`ConnectionFactory` configuration is controlled by external configuration properties in `+spring.r2dbc.*+`. +For example, you might declare the following section in `application.properties`: + +[configprops,yaml] +---- +spring: + r2dbc: + url: "r2dbc:postgresql://localhost/test" + username: "dbuser" + password: "dbpass" +---- + +TIP: You do not need to specify a driver class name, since Spring Boot obtains the driver from R2DBC's Connection Factory discovery. + +NOTE: At least the url should be provided. +Information specified in the URL takes precedence over individual properties, that is `name`, `username`, `password` and pooling options. + +TIP: The "`How-to Guides`" section includes a xref:how-to:data-initialization.adoc#howto.data-initialization.using-basic-sql-scripts[section on how to initialize a database]. + +To customize the connections created by a `ConnectionFactory`, that is, set specific parameters that you do not want (or cannot) configure in your central database configuration, you can use a `ConnectionFactoryOptionsBuilderCustomizer` `@Bean`. +The following example shows how to manually override the database port while the rest of the options are taken from the application configuration: + +include-code::MyR2dbcConfiguration[] + +The following examples show how to set some PostgreSQL connection options: + +include-code::MyPostgresR2dbcConfiguration[] + +When a `ConnectionFactory` bean is available, the regular JDBC `DataSource` auto-configuration backs off. +If you want to retain the JDBC `DataSource` auto-configuration, and are comfortable with the risk of using the blocking JDBC API in a reactive application, add `@Import(DataSourceAutoConfiguration.class)` on a `@Configuration` class in your application to re-enable it. + + + +[[data.sql.r2dbc.embedded]] +=== Embedded Database Support + +Similarly to xref:data/sql.adoc#data.sql.datasource.embedded[the JDBC support], Spring Boot can automatically configure an embedded database for reactive usage. +You need not provide any connection URLs. +You need only include a build dependency to the embedded database that you want to use, as shown in the following example: + +[source,xml] +---- + + io.r2dbc + r2dbc-h2 + runtime + +---- + +[NOTE] +==== +If you are using this feature in your tests, you may notice that the same database is reused by your whole test suite regardless of the number of application contexts that you use. +If you want to make sure that each context has a separate embedded database, you should set `spring.r2dbc.generate-unique-name` to `true`. +==== + + + +[[data.sql.r2dbc.using-database-client]] +=== Using DatabaseClient + +A `DatabaseClient` bean is auto-configured, and you can `@Autowire` it directly into your own beans, as shown in the following example: + +include-code::MyBean[] + + + +[[data.sql.r2dbc.repositories]] +=== Spring Data R2DBC Repositories + +https://spring.io/projects/spring-data-r2dbc[Spring Data R2DBC] repositories are interfaces that you can define to access data. +Queries are created automatically from your method names. +For example, a `CityRepository` interface might declare a `findAllByState(String state)` method to find all the cities in a given state. + +For more complex queries, you can annotate your method with Spring Data's {url-spring-data-r2dbc-javadoc}/org/springframework/data/r2dbc/repository/Query.html[`Query`] annotation. + +Spring Data repositories usually extend from the {url-spring-data-commons-javadoc}/org/springframework/data/repository/Repository.html[`Repository`] or {url-spring-data-commons-javadoc}/org/springframework/data/repository/CrudRepository.html[`CrudRepository`] interfaces. +If you use auto-configuration, the xref:using/auto-configuration.adoc#using.auto-configuration.packages[auto-configuration packages] are searched for repositories. + +The following example shows a typical Spring Data repository interface definition: + +include-code::CityRepository[] + +TIP: We have barely scratched the surface of Spring Data R2DBC. For complete details, see the {url-spring-data-r2dbc-docs}[Spring Data R2DBC reference documentation]. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/aop.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/aop.adoc new file mode 100644 index 000000000000..6d5803789f09 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/aop.adoc @@ -0,0 +1,10 @@ +[[features.aop]] += Aspect-Oriented Programming + +Spring Boot provides auto-configuration for aspect-oriented programming (AOP). +You can learn more about AOP with Spring in the {url-spring-framework-docs}/core/aop-api.html[Spring Framework reference documentation]. + +By default, Spring Boot's auto-configuration configures Spring AOP to use CGLib proxies. +To use JDK proxies instead, set `configprop:spring.aop.proxy-target-class` to `false`. + +If AspectJ is on the classpath, Spring Boot's auto-configuration will automatically enable AspectJ auto proxy such that `@EnableAspectJAutoProxy` is not required. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc new file mode 100644 index 000000000000..7190c0ee3202 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc @@ -0,0 +1,404 @@ +[[features.dev-services]] += Development-time Services + +Development-time services provide external dependencies needed to run the application while developing it. +They are only supposed to be used while developing and are disabled when the application is deployed. + +Spring Boot offers support for two development time services, Docker Compose and Testcontainers. +The next sections will provide more details about them. + +[[features.dev-services.docker-compose]] +== Docker Compose Support + +Docker Compose is a popular technology that can be used to define and manage multiple containers for services that your application needs. +A `compose.yml` file is typically created next to your application which defines and configures service containers. + +A typical workflow with Docker Compose is to run `docker compose up`, work on your application with it connecting to started services, then run `docker compose down` when you are finished. + +The `spring-boot-docker-compose` module can be included in a project to provide support for working with containers using Docker Compose. +Add the module dependency to your build, as shown in the following listings for Maven and Gradle: + +.Maven +[source,xml] +---- + + + org.springframework.boot + spring-boot-docker-compose + true + + +---- + +.Gradle +[source,gradle] +---- +dependencies { + developmentOnly("org.springframework.boot:spring-boot-docker-compose") +} +---- + +When this module is included as a dependency Spring Boot will do the following: + +* Search for a `compose.yml` and other common compose filenames in your working directory +* Call `docker compose up` with the discovered `compose.yml` +* Create service connection beans for each supported container +* Call `docker compose stop` when the application is shutdown + +If the Docker Compose services are already running when starting the application, Spring Boot will only create the service connection beans for each supported container. +It will not call `docker compose up` again and it will not call `docker compose stop` when the application is shutdown. + +TIP: Repackaged archives do not contain Spring Boot's Docker Compose by default. +If you want to use this support, you need to include it. +When using the Maven plugin, set the `excludeDockerCompose` property to `false`. +When using the Gradle plugin, xref:gradle-plugin:packaging.adoc#packaging-executable.configuring.including-development-only-dependencies[configure the task's classpath to include the `developmentOnly` configuration]. + + + +[[features.dev-services.docker-compose.prerequisites]] +=== Prerequisites + +You need to have the `docker` and `docker compose` (or `docker-compose`) CLI applications on your path. +The minimum supported Docker Compose version is 2.2.0. + + + +[[features.dev-services.docker-compose.service-connections]] +=== Service Connections + +A service connection is a connection to any remote service. +Spring Boot’s auto-configuration can consume the details of a service connection and use them to establish a connection to a remote service. +When doing so, the connection details take precedence over any connection-related configuration properties. + +When using Spring Boot’s Docker Compose support, service connections are established to the port mapped by the container. + +NOTE: Docker compose is usually used in such a way that the ports inside the container are mapped to ephemeral ports on your computer. +For example, a Postgres server may run inside the container using port 5432 but be mapped to a totally different port locally. +The service connection will always discover and use the locally mapped port. + +Service connections are established by using the image name of the container. +The following service connections are currently supported: + + +|=== +| Connection Details | Matched on + +| `ActiveMQConnectionDetails` +| Containers named "symptoma/activemq" or "apache/activemq-classic" + +| `ArtemisConnectionDetails` +| Containers named "apache/activemq-artemis" + +| `CassandraConnectionDetails` +| Containers named "cassandra" or "bitnami/cassandra" + +| `ElasticsearchConnectionDetails` +| Containers named "elasticsearch" or "bitnami/elasticsearch" + +| `JdbcConnectionDetails` +| Containers named "gvenzl/oracle-free", "gvenzl/oracle-xe", "mariadb", "bitnami/mariadb", "mssql/server", "mysql", "bitnami/mysql", "postgres", or "bitnami/postgresql" + +| `LdapConnectionDetails` +| Containers named "osixia/openldap" + +| `MongoConnectionDetails` +| Containers named "mongo" or "bitnami/mongodb" + +| `Neo4jConnectionDetails` +| Containers named "neo4j" or "bitnami/neo4j" + +| `OtlpMetricsConnectionDetails` +| Containers named "otel/opentelemetry-collector-contrib" + +| `OtlpTracingConnectionDetails` +| Containers named "otel/opentelemetry-collector-contrib" + +| `PulsarConnectionDetails` +| Containers named "apachepulsar/pulsar" + +| `R2dbcConnectionDetails` +| Containers named "gvenzl/oracle-free", "gvenzl/oracle-xe", "mariadb", "bitnami/mariadb", "mssql/server", "mysql", "bitnami/mysql", "postgres", or "bitnami/postgresql" + +| `RabbitConnectionDetails` +| Containers named "rabbitmq" or "bitnami/rabbitmq" + +| `RedisConnectionDetails` +| Containers named "redis", "bitnami/redis", "redis/redis-stack" or "redis/redis-stack-server" + +| `ZipkinConnectionDetails` +| Containers named "openzipkin/zipkin". +|=== + + + +[[features.dev-services.docker-compose.custom-images]] +=== Custom Images + +Sometimes you may need to use your own version of an image to provide a service. +You can use any custom image as long as it behaves in the same way as the standard image. +Specifically, any environment variables that the standard image supports must also be used in your custom image. + +If your image uses a different name, you can use a label in your `compose.yml` file so that Spring Boot can provide a service connection. +Use a label named `org.springframework.boot.service-connection` to provide the service name. + +For example: + +[source,yaml,] +---- +services: + redis: + image: 'mycompany/mycustomredis:7.0' + ports: + - '6379' + labels: + org.springframework.boot.service-connection: redis +---- + + + +[[features.dev-services.docker-compose.skipping]] +=== Skipping Specific Containers + +If you have a container image defined in your `compose.yml` that you don’t want connected to your application you can use a label to ignore it. +Any container with labeled with `org.springframework.boot.ignore` will be ignored by Spring Boot. + +For example: + +[source,yaml] +---- +services: + redis: + image: 'redis:7.0' + ports: + - '6379' + labels: + org.springframework.boot.ignore: true +---- + + + +[[features.dev-services.docker-compose.specific-file]] +=== Using a Specific Compose File + +If your compose file is not in the same directory as your application, or if it’s named differently, you can use configprop:spring.docker.compose.file[] in your `application.properties` or `application.yaml` to point to a different file. +Properties can be defined as an exact path or a path that’s relative to your application. + +For example: + +[configprops,yaml] +---- +spring: + docker: + compose: + file: "../my-compose.yml" +---- + + + +[[features.dev-services.docker-compose.readiness]] +=== Waiting for Container Readiness + +Containers started by Docker Compose may take some time to become fully ready. +The recommended way of checking for readiness is to add a `healthcheck` section under the service definition in your `compose.yml` file. + +Since it's not uncommon for `healthcheck` configuration to be omitted from `compose.yml` files, Spring Boot also checks directly for service readiness. +By default, a container is considered ready when a TCP/IP connection can be established to its mapped port. + +You can disable this on a per-container basis by adding a `org.springframework.boot.readiness-check.tcp.disable` label in your `compose.yml` file. + +For example: + +[source,yaml] +---- +services: + redis: + image: 'redis:7.0' + ports: + - '6379' + labels: + org.springframework.boot.readiness-check.tcp.disable: true +---- + +You can also change timeout values in your `application.properties` or `application.yaml` file: + +[configprops,yaml] +---- +spring: + docker: + compose: + readiness: + tcp: + connect-timeout: 10s + read-timeout: 5s +---- + +The overall timeout can be configured using configprop:spring.docker.compose.readiness.timeout[]. + + + +[[features.dev-services.docker-compose.lifecycle]] +=== Controlling the Docker Compose Lifecycle + +By default Spring Boot calls `docker compose up` when your application starts and `docker compose stop` when it's shut down. +If you prefer to have different lifecycle management you can use the configprop:spring.docker.compose.lifecycle-management[] property. + +The following values are supported: + +* `none` - Do not start or stop Docker Compose +* `start-only` - Start Docker Compose when the application starts and leave it running +* `start-and-stop` - Start Docker Compose when the application starts and stop it when the JVM exits + +In addition you can use the configprop:spring.docker.compose.start.command[] property to change whether `docker compose up` or `docker compose start` is used. +The configprop:spring.docker.compose.stop.command[] allows you to configure if `docker compose down` or `docker compose stop` is used. + +The following example shows how lifecycle management can be configured: + +[configprops,yaml] +---- +spring: + docker: + compose: + lifecycle-management: start-and-stop + start: + command: start + stop: + command: down + timeout: 1m +---- + + + +[[features.dev-services.docker-compose.profiles]] +=== Activating Docker Compose Profiles + +Docker Compose profiles are similar to Spring profiles in that they let you adjust your Docker Compose configuration for specific environments. +If you want to activate a specific Docker Compose profile you can use the configprop:spring.docker.compose.profiles.active[] property in your `application.properties` or `application.yaml` file: + +[configprops,yaml] +---- +spring: + docker: + compose: + profiles: + active: "myprofile" +---- + + + +[[features.dev-services.docker-compose.tests]] +=== Using Docker Compose in Tests + +By default, Spring Boot's Docker Compose support is disabled when running tests. + +To enable Docker Compose support in tests, set configprop:spring.docker.compose.skip.in-tests[] to `false`. + +When using Gradle, you also need to change the configuration of the `spring-boot-docker-compose` dependency from `developmentOnly` to `testAndDevelopmentOnly`: + +.Gradle +[source,gradle,indent=0,subs="verbatim"] +---- + dependencies { + testAndDevelopmentOnly("org.springframework.boot:spring-boot-docker-compose") + } +---- + + + +[[features.dev-services.testcontainers]] +== Testcontainers Support + +As well as xref:testing/testcontainers.adoc#testing.testcontainers[using Testcontainers for integration testing], it's also possible to use them at development time. +The next sections will provide more details about that. + + + +[[features.dev-services.testcontainers.at-development-time]] +=== Using Testcontainers at Development Time + +This approach allows developers to quickly start containers for the services that the application depends on, removing the need to manually provision things like database servers. +Using Testcontainers in this way provides functionality similar to Docker Compose, except that your container configuration is in Java rather than YAML. + +To use Testcontainers at development time you need to launch your application using your "`test`" classpath rather than "`main`". +This will allow you to access all declared test dependencies and give you a natural place to write your test configuration. + +To create a test launchable version of your application you should create an "`Application`" class in the `src/test` directory. +For example, if your main application is in `src/main/java/com/example/MyApplication.java`, you should create `src/test/java/com/example/TestMyApplication.java` + +The `TestMyApplication` class can use the `SpringApplication.from(...)` method to launch the real application: + +include-code::launch/TestMyApplication[] + +You'll also need to define the `Container` instances that you want to start along with your application. +To do this, you need to make sure that the `spring-boot-testcontainers` module has been added as a `test` dependency. +Once that has been done, you can create a `@TestConfiguration` class that declares `@Bean` methods for the containers you want to start. + +You can also annotate your `@Bean` methods with `@ServiceConnection` in order to create `ConnectionDetails` beans. +See xref:testing/testcontainers.adoc#testing.testcontainers.service-connections[the service connections] section for details of the supported technologies. + +A typical Testcontainers configuration would look like this: + +include-code::test/MyContainersConfiguration[] + +NOTE: The lifecycle of `Container` beans is automatically managed by Spring Boot. +Containers will be started and stopped automatically. + +TIP: You can use the configprop:spring.testcontainers.beans.startup[] property to change how containers are started. +By default `sequential` startup is used, but you may also choose `parallel` if you wish to start multiple containers in parallel. + +Once you have defined your test configuration, you can use the `with(...)` method to attach it to your test launcher: + +include-code::test/TestMyApplication[] + +You can now launch `TestMyApplication` as you would any regular Java `main` method application to start your application and the containers that it needs to run. + +TIP: You can use the Maven goal `spring-boot:test-run` or the Gradle task `bootTestRun` to do this from the command line. + + + +[[features.dev-services.testcontainers.at-development-time.dynamic-properties]] +==== Contributing Dynamic Properties at Development Time + +If you want to contribute dynamic properties at development time from your `Container` `@Bean` methods, you can do so by injecting a `DynamicPropertyRegistry`. +This works in a similar way to the xref:testing/testcontainers.adoc#testing.testcontainers.dynamic-properties[`@DynamicPropertySource` annotation] that you can use in your tests. +It allows you to add properties that will become available once your container has started. + +A typical configuration would look like this: + +include-code::MyContainersConfiguration[] + +NOTE: Using a `@ServiceConnection` is recommended whenever possible, however, dynamic properties can be a useful fallback for technologies that don't yet have `@ServiceConnection` support. + + + +[[features.dev-services.testcontainers.at-development-time.importing-container-declarations]] +==== Importing Testcontainer Declaration Classes + +A common pattern when using Testcontainers is to declare `Container` instances as static fields. +Often these fields are defined directly on the test class. +They can also be declared on a parent class or on an interface that the test implements. + +For example, the following `MyContainers` interface declares `mongo` and `neo4j` containers: + +include-code::MyContainers[] + +If you already have containers defined in this way, or you just prefer this style, you can import these declaration classes rather than defining your containers as `@Bean` methods. +To do so, add the `@ImportTestcontainers` annotation to your test configuration class: + +include-code::MyContainersConfiguration[] + +TIP: If you don't intend to use the xref:testing/testcontainers.adoc#testing.testcontainers.service-connections[service connections feature] but want to use xref:testing/testcontainers.adoc#testing.testcontainers.dynamic-properties[`@DynamicPropertySource`] instead, remove the `@ServiceConnection` annotation from the `Container` fields. +You can also add `@DynamicPropertySource` annotated methods to your declaration class. + + + +[[features.dev-services.testcontainers.at-development-time.devtools]] +==== Using DevTools with Testcontainers at Development Time + +When using devtools, you can annotate beans and bean methods with `@RestartScope`. +Such beans won't be recreated when the devtools restart the application. +This is especially useful for Testcontainer `Container` beans, as they keep their state despite the application restart. + +include-code::MyContainersConfiguration[] + +WARNING: If you're using Gradle and want to use this feature, you need to change the configuration of the `spring-boot-devtools` dependency from `developmentOnly` to `testAndDevelopmentOnly`. +With the default scope of `developmentOnly`, the `bootTestRun` task will not pick up changes in your code, as the devtools are not active. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/developing-auto-configuration.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/developing-auto-configuration.adoc similarity index 80% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/developing-auto-configuration.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/developing-auto-configuration.adoc index 4a4a1eff9453..64138885ef79 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/developing-auto-configuration.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/developing-auto-configuration.adoc @@ -1,35 +1,37 @@ [[features.developing-auto-configuration]] -== Creating Your Own Auto-configuration += Creating Your Own Auto-configuration + If you work in a company that develops shared libraries, or if you work on an open-source or commercial library, you might want to develop your own auto-configuration. Auto-configuration classes can be bundled in external jars and still be picked up by Spring Boot. Auto-configuration can be associated to a "`starter`" that provides the auto-configuration code as well as the typical libraries that you would use with it. -We first cover what you need to know to build your own auto-configuration and then we move on to the <>. +We first cover what you need to know to build your own auto-configuration and then we move on to the xref:features/developing-auto-configuration.adoc#features.developing-auto-configuration.custom-starter[typical steps required to create a custom starter]. [[features.developing-auto-configuration.understanding-auto-configured-beans]] -=== Understanding Auto-configured Beans +== Understanding Auto-configured Beans + Classes that implement auto-configuration are annotated with `@AutoConfiguration`. This annotation itself is meta-annotated with `@Configuration`, making auto-configurations standard `@Configuration` classes. Additional `@Conditional` annotations are used to constrain when the auto-configuration should apply. Usually, auto-configuration classes use `@ConditionalOnClass` and `@ConditionalOnMissingBean` annotations. This ensures that auto-configuration applies only when relevant classes are found and when you have not declared your own `@Configuration`. -You can browse the source code of {spring-boot-autoconfigure-module-code}[`spring-boot-autoconfigure`] to see the `@AutoConfiguration` classes that Spring provides (see the {spring-boot-code}/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports[`META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`] file). +You can browse the source code of {code-spring-boot-autoconfigure-src}[`spring-boot-autoconfigure`] to see the `@AutoConfiguration` classes that Spring provides (see the {code-spring-boot}/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports[`META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`] file). [[features.developing-auto-configuration.locating-auto-configuration-candidates]] -=== Locating Auto-configuration Candidates +== Locating Auto-configuration Candidates Spring Boot checks for the presence of a `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` file within your published jar. The file should list your configuration classes, with one class name per line, as shown in the following example: -[indent=0] +[source] ---- - com.mycorp.libx.autoconfigure.LibXAutoConfiguration - com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration +com.mycorp.libx.autoconfigure.LibXAutoConfiguration +com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration ---- TIP: You can add comments to the imports file using the `#` character. @@ -39,7 +41,7 @@ Make sure that they are defined in a specific package space and that they are ne Furthermore, auto-configuration classes should not enable component scanning to find additional components. Specific `@Import` annotations should be used instead. -If your configuration needs to be applied in a specific order, you can use the `before`, `beforeName`, `after` and `afterName` attributes on the {spring-boot-autoconfigure-module-code}/AutoConfiguration.java[`@AutoConfiguration`] annotation or the dedicated {spring-boot-autoconfigure-module-code}/AutoConfigureBefore.java[`@AutoConfigureBefore`] and {spring-boot-autoconfigure-module-code}/AutoConfigureAfter.java[`@AutoConfigureAfter`] annotations. +If your configuration needs to be applied in a specific order, you can use the `before`, `beforeName`, `after` and `afterName` attributes on the xref:api:java/org/springframework/boot/autoconfigure/AutoConfiguration.html[`@AutoConfiguration`] annotation or the dedicated xref:api:java/org/springframework/boot/autoconfigure/AutoConfigureBefore.html[`@AutoConfigureBefore`] and xref:api:java/org/springframework/boot/autoconfigure/AutoConfigureAfter.html[`@AutoConfigureAfter`] annotations. For example, if you provide web-specific configuration, your class may need to be applied after `WebMvcAutoConfiguration`. If you want to order certain auto-configurations that should not have any direct knowledge of each other, you can also use `@AutoConfigureOrder`. @@ -51,24 +53,26 @@ The order in which those beans are subsequently created is unaffected and is det [[features.developing-auto-configuration.condition-annotations]] -=== Condition Annotations +== Condition Annotations + You almost always want to include one or more `@Conditional` annotations on your auto-configuration class. The `@ConditionalOnMissingBean` annotation is one common example that is used to allow developers to override auto-configuration if they are not happy with your defaults. Spring Boot includes a number of `@Conditional` annotations that you can reuse in your own code by annotating `@Configuration` classes or individual `@Bean` methods. These annotations include: -* <> -* <> -* <> -* <> -* <> -* <> +* xref:features/developing-auto-configuration.adoc#features.developing-auto-configuration.condition-annotations.class-conditions[] +* xref:features/developing-auto-configuration.adoc#features.developing-auto-configuration.condition-annotations.bean-conditions[] +* xref:features/developing-auto-configuration.adoc#features.developing-auto-configuration.condition-annotations.property-conditions[] +* xref:features/developing-auto-configuration.adoc#features.developing-auto-configuration.condition-annotations.resource-conditions[] +* xref:features/developing-auto-configuration.adoc#features.developing-auto-configuration.condition-annotations.web-application-conditions[] +* xref:features/developing-auto-configuration.adoc#features.developing-auto-configuration.condition-annotations.spel-conditions[] [[features.developing-auto-configuration.condition-annotations.class-conditions]] -==== Class Conditions +=== Class Conditions + The `@ConditionalOnClass` and `@ConditionalOnMissingClass` annotations let `@Configuration` classes be included based on the presence or absence of specific classes. Due to the fact that annotation metadata is parsed by using https://asm.ow2.io/[ASM], you can use the `value` attribute to refer to the real class, even though that class might not actually appear on the running application classpath. You can also use the `name` attribute if you prefer to specify the class name by using a `String` value. @@ -77,21 +81,22 @@ This mechanism does not apply the same way to `@Bean` methods where typically th To handle this scenario, a separate `@Configuration` class can be used to isolate the condition, as shown in the following example: -include::code:MyAutoConfiguration[] +include-code::MyAutoConfiguration[] TIP: If you use `@ConditionalOnClass` or `@ConditionalOnMissingClass` as a part of a meta-annotation to compose your own composed annotations, you must use `name` as referring to the class in such a case is not handled. [[features.developing-auto-configuration.condition-annotations.bean-conditions]] -==== Bean Conditions +=== Bean Conditions + The `@ConditionalOnBean` and `@ConditionalOnMissingBean` annotations let a bean be included based on the presence or absence of specific beans. You can use the `value` attribute to specify beans by type or `name` to specify beans by name. The `search` attribute lets you limit the `ApplicationContext` hierarchy that should be considered when searching for beans. When placed on a `@Bean` method, the target type defaults to the return type of the method, as shown in the following example: -include::code:MyAutoConfiguration[] +include-code::MyAutoConfiguration[] In the preceding example, the `someService` bean is going to be created if no bean of type `SomeService` is already contained in the `ApplicationContext`. @@ -108,7 +113,8 @@ Providing as much type information as possible in `@Bean` methods is particularl [[features.developing-auto-configuration.condition-annotations.property-conditions]] -==== Property Conditions +=== Property Conditions + The `@ConditionalOnProperty` annotation lets configuration be included based on a Spring Environment property. Use the `prefix` and `name` attributes to specify the property that should be checked. By default, any property that exists and is not equal to `false` is matched. @@ -119,14 +125,16 @@ If multiple names are given in the `name` attribute, all of the properties have [[features.developing-auto-configuration.condition-annotations.resource-conditions]] -==== Resource Conditions +=== Resource Conditions + The `@ConditionalOnResource` annotation lets configuration be included only when a specific resource is present. Resources can be specified by using the usual Spring conventions, as shown in the following example: `file:/home/user/test.dat`. [[features.developing-auto-configuration.condition-annotations.web-application-conditions]] -==== Web Application Conditions +=== Web Application Conditions + The `@ConditionalOnWebApplication` and `@ConditionalOnNotWebApplication` annotations let configuration be included depending on whether the application is a web application. A servlet-based web application is any application that uses a Spring `WebApplicationContext`, defines a `session` scope, or has a `ConfigurableWebEnvironment`. A reactive web application is any application that uses a `ReactiveWebApplicationContext`, or has a `ConfigurableReactiveWebEnvironment`. @@ -137,8 +145,9 @@ This condition will not match for applications that are run with an embedded web [[features.developing-auto-configuration.condition-annotations.spel-conditions]] -==== SpEL Expression Conditions -The `@ConditionalOnExpression` annotation lets configuration be included based on the result of a {spring-framework-docs}/core/expressions.html[SpEL expression]. +=== SpEL Expression Conditions + +The `@ConditionalOnExpression` annotation lets configuration be included based on the result of a {url-spring-framework-docs}/core/expressions.html[SpEL expression]. NOTE: Referencing a bean in the expression will cause that bean to be initialized very early in context refresh processing. As a result, the bean won't be eligible for post-processing (such as configuration properties binding) and its state may be incomplete. @@ -146,7 +155,8 @@ As a result, the bean won't be eligible for post-processing (such as configurati [[features.developing-auto-configuration.testing]] -=== Testing your Auto-configuration +== Testing your Auto-configuration + An auto-configuration can be affected by many factors: user configuration (`@Bean` definition and `Environment` customization), condition evaluation (presence of a particular library), and others. Concretely, each test should create a well defined `ApplicationContext` that represents a combination of those customizations. `ApplicationContextRunner` provides a great way to achieve that. @@ -156,7 +166,7 @@ WARNING: `ApplicationContextRunner` doesn't work when running the tests in a nat `ApplicationContextRunner` is usually defined as a field of the test class to gather the base, common configuration. The following example makes sure that `MyServiceAutoConfiguration` is always invoked: -include::code:MyServiceAutoConfigurationTests[tag=runner] +include-code::MyServiceAutoConfigurationTests[tag=runner] TIP: If multiple auto-configurations have to be defined, there is no need to order their declarations as they are invoked in the exact same order as when running the application. @@ -164,38 +174,41 @@ Each test can use the runner to represent a particular use case. For instance, the sample below invokes a user configuration (`UserConfiguration`) and checks that the auto-configuration backs off properly. Invoking `run` provides a callback context that can be used with `AssertJ`. -include::code:MyServiceAutoConfigurationTests[tag=test-user-config] +include-code::MyServiceAutoConfigurationTests[tag=test-user-config] It is also possible to easily customize the `Environment`, as shown in the following example: -include::code:MyServiceAutoConfigurationTests[tag=test-env] +include-code::MyServiceAutoConfigurationTests[tag=test-env] The runner can also be used to display the `ConditionEvaluationReport`. The report can be printed at `INFO` or `DEBUG` level. The following example shows how to use the `ConditionEvaluationReportLoggingListener` to print the report in auto-configuration tests. -include::code:MyConditionEvaluationReportingTests[] +include-code::MyConditionEvaluationReportingTests[] [[features.developing-auto-configuration.testing.simulating-a-web-context]] -==== Simulating a Web Context +=== Simulating a Web Context + If you need to test an auto-configuration that only operates in a servlet or reactive web application context, use the `WebApplicationContextRunner` or `ReactiveWebApplicationContextRunner` respectively. [[features.developing-auto-configuration.testing.overriding-classpath]] -==== Overriding the Classpath +=== Overriding the Classpath + It is also possible to test what happens when a particular class and/or package is not present at runtime. Spring Boot ships with a `FilteredClassLoader` that can easily be used by the runner. In the following example, we assert that if `MyService` is not present, the auto-configuration is properly disabled: -include::code:../MyServiceAutoConfigurationTests[tag=test-classloader] +include-code::../MyServiceAutoConfigurationTests[tag=test-classloader] [[features.developing-auto-configuration.custom-starter]] -=== Creating Your Own Starter +== Creating Your Own Starter + A typical Spring Boot starter contains code to auto-configure and customize the infrastructure of a given technology, let's call that "acme". To make it easily extensible, a number of configuration keys in a dedicated namespace can be exposed to the environment. Finally, a single "starter" dependency is provided to help users get started as easily as possible. @@ -216,7 +229,8 @@ If the auto-configuration is relatively straightforward and does not have option [[features.developing-auto-configuration.custom-starter.naming]] -==== Naming +=== Naming + You should make sure to provide a proper namespace for your starter. Do not start your module names with `spring-boot`, even if you use a different Maven `groupId`. We may offer official support for the thing you auto-configure in the future. @@ -228,18 +242,21 @@ If you only have one module that combines the two, name it `acme-spring-boot-sta [[features.developing-auto-configuration.custom-starter.configuration-keys]] -==== Configuration keys +=== Configuration keys + If your starter provides configuration keys, use a unique namespace for them. In particular, do not include your keys in the namespaces that Spring Boot uses (such as `server`, `management`, `spring`, and so on). If you use the same namespace, we may modify these namespaces in the future in ways that break your modules. As a rule of thumb, prefix all your keys with a namespace that you own (for example `acme`). -Make sure that configuration keys are documented by adding field javadoc for each property, as shown in the following example: +Make sure that configuration keys are documented by adding field Javadoc for each property, as shown in the following example: -include::code:AcmeProperties[] +include-code::AcmeProperties[] NOTE: You should only use plain text with `@ConfigurationProperties` field Javadoc, since they are not processed before being added to the JSON. +If you use `@ConfigurationProperties` with record class then record components' descriptions should be provided via class-level Javadoc tag `@param` (there are no explicit instance fields in record classes to put regular field-level Javadocs on). + Here are some rules we follow internally to make sure descriptions are consistent: * Do not start the description by "The" or "A". @@ -248,14 +265,15 @@ Here are some rules we follow internally to make sure descriptions are consisten * Use `java.time.Duration` rather than `long` and describe the default unit if it differs from milliseconds, such as "If a duration suffix is not specified, seconds will be used". * Do not provide the default value in the description unless it has to be determined at runtime. -Make sure to <> so that IDE assistance is available for your keys as well. +Make sure to xref:specification:configuration-metadata/annotation-processor.adoc[trigger meta-data generation] so that IDE assistance is available for your keys as well. You may want to review the generated metadata (`META-INF/spring-configuration-metadata.json`) to make sure your keys are properly documented. Using your own starter in a compatible IDE is also a good idea to validate that quality of the metadata. [[features.developing-auto-configuration.custom-starter.autoconfigure-module]] -==== The "`autoconfigure`" Module +=== The "`autoconfigure`" Module + The `autoconfigure` module contains everything that is necessary to get started with the library. It may also contain configuration key definitions (such as `@ConfigurationProperties`) and any callback interface that can be used to further customize how the components are initialized. @@ -267,52 +285,53 @@ If that file is present, it is used to eagerly filter auto-configurations that d When building with Maven, it is recommended to add the following dependency in a module that contains auto-configurations: -[source,xml,indent=0,subs="verbatim"] +[source,xml] ---- - - org.springframework.boot - spring-boot-autoconfigure-processor - true - + + org.springframework.boot + spring-boot-autoconfigure-processor + true + ---- -If you have defined auto-configurations directly in your application, make sure to configure the `spring-boot-maven-plugin` to prevent the `repackage` goal from adding the dependency into the fat jar: +If you have defined auto-configurations directly in your application, make sure to configure the `spring-boot-maven-plugin` to prevent the `repackage` goal from adding the dependency into the uber jar: -[source,xml,indent=0,subs="verbatim"] +[source,xml] ---- - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.springframework.boot - spring-boot-autoconfigure-processor - - - - - - - + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.springframework.boot + spring-boot-autoconfigure-processor + + + + + + + ---- With Gradle, the dependency should be declared in the `annotationProcessor` configuration, as shown in the following example: -[source,gradle,indent=0,subs="verbatim"] +[source,gradle] ---- - dependencies { - annotationProcessor "org.springframework.boot:spring-boot-autoconfigure-processor" - } +dependencies { + annotationProcessor "org.springframework.boot:spring-boot-autoconfigure-processor" +} ---- [[features.developing-auto-configuration.custom-starter.starter-module]] -==== Starter Module +=== Starter Module + The starter is really an empty jar. Its only purpose is to provide the necessary dependencies to work with the library. You can think of it as an opinionated view of what is required to get started. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/external-config.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/external-config.adoc similarity index 80% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/external-config.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/external-config.adoc index f28b232dca37..0ad5ae563173 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/external-config.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/external-config.adoc @@ -1,16 +1,17 @@ [[features.external-config]] -== Externalized Configuration += Externalized Configuration + Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments. You can use a variety of external configuration sources including Java properties files, YAML files, environment variables, and command-line arguments. -Property values can be injected directly into your beans by using the `@Value` annotation, accessed through Spring's `Environment` abstraction, or be <> through `@ConfigurationProperties`. +Property values can be injected directly into your beans by using the `@Value` annotation, accessed through Spring's `Environment` abstraction, or be xref:features/external-config.adoc#features.external-config.typesafe-configuration-properties[bound to structured objects] through `@ConfigurationProperties`. Spring Boot uses a very particular `PropertySource` order that is designed to allow sensible overriding of values. Later property sources can override the values defined in earlier ones. Sources are considered in the following order: . Default properties (specified by setting `SpringApplication.setDefaultProperties`). -. {spring-framework-api}/context/annotation/PropertySource.html[`@PropertySource`] annotations on your `@Configuration` classes. +. {url-spring-framework-javadoc}/org/springframework/context/annotation/PropertySource.html[`@PropertySource`] annotations on your `@Configuration` classes. Please note that such property sources are not added to the `Environment` until the application context is being refreshed. This is too late to configure certain properties such as `+logging.*+` and `+spring.main.*+` which are read before refresh begins. . Config data (such as `application.properties` files). @@ -23,29 +24,29 @@ Sources are considered in the following order: . Properties from `SPRING_APPLICATION_JSON` (inline JSON embedded in an environment variable or system property). . Command line arguments. . `properties` attribute on your tests. - Available on {spring-boot-test-module-api}/context/SpringBootTest.html[`@SpringBootTest`] and the <>. -. {spring-framework-api}/test/context/DynamicPropertySource.html[`@DynamicPropertySource`] annotations in your tests. -. {spring-framework-api}/test/context/TestPropertySource.html[`@TestPropertySource`] annotations on your tests. -. <> in the `$HOME/.config/spring-boot` directory when devtools is active. + Available on xref:api:java/org/springframework/boot/test/context/SpringBootTest.html[`@SpringBootTest`] and the xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-tests[test annotations for testing a particular slice of your application]. +. {url-spring-framework-javadoc}/org/springframework/test/context/DynamicPropertySource.html[`@DynamicPropertySource`] annotations in your tests. +. {url-spring-framework-javadoc}/org/springframework/test/context/TestPropertySource.html[`@TestPropertySource`] annotations on your tests. +. xref:using/devtools.adoc#using.devtools.globalsettings[Devtools global settings properties] in the `$HOME/.config/spring-boot` directory when devtools is active. Config data files are considered in the following order: -. <> packaged inside your jar (`application.properties` and YAML variants). -. <> packaged inside your jar (`application-\{profile}.properties` and YAML variants). -. <> outside of your packaged jar (`application.properties` and YAML variants). -. <> outside of your packaged jar (`application-\{profile}.properties` and YAML variants). +. xref:features/external-config.adoc#features.external-config.files[Application properties] packaged inside your jar (`application.properties` and YAML variants). +. xref:features/external-config.adoc#features.external-config.files.profile-specific[Profile-specific application properties] packaged inside your jar (`application-\{profile}.properties` and YAML variants). +. xref:features/external-config.adoc#features.external-config.files[Application properties] outside of your packaged jar (`application.properties` and YAML variants). +. xref:features/external-config.adoc#features.external-config.files.profile-specific[Profile-specific application properties] outside of your packaged jar (`application-\{profile}.properties` and YAML variants). NOTE: It is recommended to stick with one format for your entire application. If you have configuration files with both `.properties` and YAML format in the same location, `.properties` takes precedence. NOTE: If you use environment variables rather than system properties, most operating systems disallow period-separated key names, but you can use underscores instead (for example, configprop:spring.config.name[format=envvar] instead of configprop:spring.config.name[]). -See <> for details. +See xref:features/external-config.adoc#features.external-config.typesafe-configuration-properties.relaxed-binding.environment-variables[] for details. NOTE: If your application runs in a servlet container or application server, then JNDI properties (in `java:comp/env`) or servlet context initialization parameters can be used instead of, or as well as, environment variables or system properties. To provide a concrete example, suppose you develop a `@Component` that uses a `name` property, as shown in the following example: -include::code:MyBean[] +include-code::MyBean[] On your application classpath (for example, inside your jar) you can have an `application.properties` file that provides a sensible default property value for `name`. When running in a new environment, an `application.properties` file can be provided outside of your jar that overrides the `name`. @@ -53,12 +54,13 @@ For one-off testing, you can launch with a specific command line switch (for exa TIP: The `env` and `configprops` endpoints can be useful in determining why a property has a particular value. You can use these two endpoints to diagnose unexpected property values. -See the "<>" section for details. +See the xref:actuator/endpoints.adoc[Production ready features] section for details. [[features.external-config.command-line-args]] -=== Accessing Command Line Properties +== Accessing Command Line Properties + By default, `SpringApplication` converts any command line option arguments (that is, arguments starting with `--`, such as `--server.port=9000`) to a `property` and adds them to the Spring `Environment`. As mentioned previously, command line properties always take precedence over file-based property sources. @@ -67,7 +69,8 @@ If you do not want command line properties to be added to the `Environment`, you [[features.external-config.application-json]] -=== JSON Application Properties +== JSON Application Properties + Environment variables and system properties often have restrictions that mean some property names cannot be used. To help with this, Spring Boot allows you to encode a block of properties into a single JSON structure. @@ -75,25 +78,25 @@ When your application starts, any `spring.application.json` or `SPRING_APPLICATI For example, the `SPRING_APPLICATION_JSON` property can be supplied on the command line in a UN{asterisk}X shell as an environment variable: -[source,shell,indent=0,subs="verbatim"] +[source,shell] ---- - $ SPRING_APPLICATION_JSON='{"my":{"name":"test"}}' java -jar myapp.jar +$ SPRING_APPLICATION_JSON='{"my":{"name":"test"}}' java -jar myapp.jar ---- In the preceding example, you end up with `my.name=test` in the Spring `Environment`. The same JSON can also be provided as a system property: -[source,shell,indent=0,subs="verbatim"] +[source,shell] ---- - $ java -Dspring.application.json='{"my":{"name":"test"}}' -jar myapp.jar +$ java -Dspring.application.json='{"my":{"name":"test"}}' -jar myapp.jar ---- Or you could supply the JSON by using a command line argument: -[source,shell,indent=0,subs="verbatim"] +[source,shell] ---- - $ java -jar myapp.jar --spring.application.json='{"my":{"name":"test"}}' +$ java -jar myapp.jar --spring.application.json='{"my":{"name":"test"}}' ---- If you are deploying to a classic Application Server, you could also use a JNDI variable named `java:comp/env/spring.application.json`. @@ -104,7 +107,8 @@ This means that the JSON cannot override properties from lower order property so [[features.external-config.files]] -=== External Application Properties +== External Application Properties + Spring Boot will automatically find and load `application.properties` and `application.yaml` files from the following locations when your application starts: . From the classpath @@ -121,9 +125,9 @@ Documents from the loaded files are added as `PropertySources` to the Spring `En If you do not like `application` as the configuration file name, you can switch to another file name by specifying a configprop:spring.config.name[] environment property. For example, to look for `myproject.properties` and `myproject.yaml` files you can run your application as follows: -[source,shell,indent=0,subs="verbatim"] +[source,shell] ---- - $ java -jar myproject.jar --spring.config.name=myproject +$ java -jar myproject.jar --spring.config.name=myproject ---- You can also refer to an explicit location by using the configprop:spring.config.location[] environment property. @@ -131,14 +135,14 @@ This property accepts a comma-separated list of one or more locations to check. The following example shows how to specify two distinct files: -[source,shell,indent=0,subs="verbatim"] +[source,shell] ---- - $ java -jar myproject.jar --spring.config.location=\ - optional:classpath:/default.properties,\ - optional:classpath:/override.properties +$ java -jar myproject.jar --spring.config.location=\ + optional:classpath:/default.properties,\ + optional:classpath:/override.properties ---- -TIP: Use the prefix `optional:` if the <> and you do not mind if they do not exist. +TIP: Use the prefix `optional:` if the xref:features/external-config.adoc#features.external-config.files.optional-prefix[locations are optional] and you do not mind if they do not exist. WARNING: `spring.config.name`, `spring.config.location`, and `spring.config.additional-location` are used very early to determine which files have to be loaded. They must be defined as an environment property (typically an OS environment variable, a system property, or a command-line argument). @@ -147,7 +151,7 @@ If `spring.config.location` contains directories (as opposed to files), they sho At runtime they will be appended with the names generated from `spring.config.name` before being loaded. Files specified in `spring.config.location` are imported directly. -NOTE: Both directory and file location values are also expanded to check for <>. +NOTE: Both directory and file location values are also expanded to check for xref:features/external-config.adoc#features.external-config.files.profile-specific[profile-specific files]. For example, if you have a `spring.config.location` of `classpath:myconfig.properties`, you will also find appropriate `classpath:myconfig-.properties` files are loaded. In most situations, each configprop:spring.config.location[] item you add will reference a single file or directory. @@ -158,7 +162,7 @@ If you have a complex location setup, and you use profile-specific configuration A location group is a collection of locations that are all considered at the same level. For example, you might want to group all classpath locations, then all external locations. Items within a location group should be separated with `;`. -See the example in the "`<>`" section for more details. +See the example in the xref:features/external-config.adoc#features.external-config.files.profile-specific[] section for more details. Locations configured by using `spring.config.location` replace the default locations. For example, if `spring.config.location` is configured with the value `optional:classpath:/custom-config/,optional:file:./custom-config/`, the complete set of locations considered is: @@ -182,11 +186,12 @@ These default values can then be overridden at runtime with a different file loc [[features.external-config.files.optional-prefix]] -==== Optional Locations +=== Optional Locations + By default, when a specified config data location does not exist, Spring Boot will throw a `ConfigDataLocationNotFoundException` and your application will not start. If you want to specify a location, but you do not mind if it does not always exist, you can use the `optional:` prefix. -You can use this prefix with the `spring.config.location` and `spring.config.additional-location` properties, as well as with <> declarations. +You can use this prefix with the `spring.config.location` and `spring.config.additional-location` properties, as well as with xref:features/external-config.adoc#features.external-config.files.importing[`spring.config.import`] declarations. For example, a `spring.config.import` value of `optional:file:./myconfig.properties` allows your application to start, even if the `myconfig.properties` file is missing. @@ -196,7 +201,8 @@ Set the value to `ignore` using `SpringApplication.setDefaultProperties(...)` or [[features.external-config.files.wildcard-locations]] -==== Wildcard Locations +=== Wildcard Locations + If a config file location includes the `{asterisk}` character for the last path segment, it is considered a wildcard location. Wildcards are expanded when the config is loaded so that immediate subdirectories are also checked. Wildcard locations are particularly useful in an environment such as Kubernetes when there are multiple sources of config properties. @@ -219,7 +225,8 @@ You cannot use a wildcard in a `classpath:` location. [[features.external-config.files.profile-specific]] -==== Profile Specific Files +=== Profile Specific Files + As well as `application` property files, Spring Boot will also attempt to load profile-specific files using the naming convention `application-\{profile}`. For example, if your application activates a profile named `prod` and uses YAML files, then both `application.yaml` and `application-prod.yaml` will be considered. @@ -229,7 +236,7 @@ For example, if profiles `prod,live` are specified by the configprop:spring.prof [NOTE] ==== -The last-wins strategy applies at the <> level. +The last-wins strategy applies at the xref:features/external-config.adoc#features.external-config.files.location-groups[location group] level. A configprop:spring.config.location[] of `classpath:/cfg/,classpath:/ext/` will not have the same override rules as `classpath:/cfg/;classpath:/ext/`. For example, continuing our `prod,live` example above, we might have the following files: @@ -260,24 +267,25 @@ The `Environment` has a set of default profiles (by default, `[default]`) that a In other words, if no profiles are explicitly activated, then properties from `application-default` are considered. NOTE: Properties files are only ever loaded once. -If you have already directly <> a profile specific property files then it will not be imported a second time. +If you have already directly xref:features/external-config.adoc#features.external-config.files.importing[imported] a profile specific property files then it will not be imported a second time. [[features.external-config.files.importing]] -==== Importing Additional Data +=== Importing Additional Data + Application properties may import further config data from other locations using the `spring.config.import` property. Imports are processed as they are discovered, and are treated as additional documents inserted immediately below the one that declares the import. For example, you might have the following in your classpath `application.properties` file: -[source,yaml,indent=0,subs="verbatim",configblocks] +[configprops,yaml] ---- - spring: - application: - name: "myapp" - config: - import: "optional:file:./dev.properties" +spring: + application: + name: "myapp" + config: + import: "optional:file:./dev.properties" ---- This will trigger the import of a `dev.properties` file in current directory (if such a file exists). @@ -288,22 +296,22 @@ An import will only be imported once no matter how many times it is declared. The order an import is defined inside a single document within the properties/yaml file does not matter. For instance, the two examples below produce the same result: -[source,yaml,indent=0,subs="verbatim",configblocks] +[configprops%novalidate,yaml] ---- - spring: - config: - import: "my.properties" - my: - property: "value" +spring: + config: + import: "my.properties" +my: + property: "value" ---- -[source,yaml,indent=0,subs="verbatim",configblocks] +[configprops%novalidate,yaml] ---- - my: - property: "value" - spring: - config: - import: "my.properties" +my: + property: "value" +spring: + config: + import: "my.properties" ---- In both of the above examples, the values from the `my.properties` file will take precedence over the file that triggered its import. @@ -311,13 +319,13 @@ In both of the above examples, the values from the `my.properties` file will tak Several locations can be specified under a single `spring.config.import` key. Locations will be processed in the order that they are defined, with later imports taking precedence. -NOTE: When appropriate, <> are also considered for import. +NOTE: When appropriate, xref:features/external-config.adoc#features.external-config.files.profile-specific[Profile-specific variants] are also considered for import. The example above would import both `my.properties` as well as any `my-.properties` variants. [TIP] ==== Spring Boot includes pluggable API that allows various different location addresses to be supported. -By default you can import Java Properties, YAML and "`<>`". +By default you can import Java Properties, YAML and xref:features/external-config.adoc#features.external-config.files.configtree[configuration trees]. Third-party jars can offer support for additional technologies (there is no requirement for files to be local). For example, you can imagine config data being from external stores such as Consul, Apache ZooKeeper or Netflix Archaius. @@ -328,7 +336,8 @@ If you want to support your own locations, see the `ConfigDataLocationResolver` [[features.external-config.files.importing-extensionless]] -==== Importing Extensionless Files +=== Importing Extensionless Files + Some cloud platforms cannot add a file extension to volume mounted files. To import these extensionless files, you need to give Spring Boot a hint so that it knows how to load them. You can do this by putting an extension hint in square brackets. @@ -336,17 +345,18 @@ You can do this by putting an extension hint in square brackets. For example, suppose you have a `/etc/config/myconfig` file that you wish to import as yaml. You can import it from your `application.properties` using the following: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - config: - import: "file:/etc/config/myconfig[.yaml]" +spring: + config: + import: "file:/etc/config/myconfig[.yaml]" ---- [[features.external-config.files.configtree]] -==== Using Configuration Trees +=== Using Configuration Trees + When running applications on a cloud platform (such as Kubernetes) you often need to read config values that the platform supplies. It is not uncommon to use environment variables for such purposes, but this can have drawbacks, especially if the value is supposed to be kept secret. @@ -358,29 +368,29 @@ There are two common volume mount patterns that can be used: . A single file contains a complete set of properties (usually written as YAML). . Multiple files are written to a directory tree, with the filename becoming the '`key`' and the contents becoming the '`value`'. -For the first case, you can import the YAML or Properties file directly using `spring.config.import` as described <>. +For the first case, you can import the YAML or Properties file directly using `spring.config.import` as described xref:features/external-config.adoc#features.external-config.files.importing[above]. For the second case, you need to use the `configtree:` prefix so that Spring Boot knows it needs to expose all the files as properties. As an example, let's imagine that Kubernetes has mounted the following volume: -[indent=0] +[source] ---- - etc/ - config/ - myapp/ - username - password +etc/ + config/ + myapp/ + username + password ---- The contents of the `username` file would be a config value, and the contents of `password` would be a secret. To import these properties, you can add the following to your `application.properties` or `application.yaml` file: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - config: - import: "optional:configtree:/etc/config/" +spring: + config: + import: "optional:configtree:/etc/config/" ---- You can then access or inject `myapp.username` and `myapp.password` properties from the `Environment` in the usual way. @@ -399,27 +409,27 @@ As with a non-wildcard import, the names of the folders and files under each con For example, given the following volume: -[indent=0] +[source] ---- - etc/ - config/ - dbconfig/ - db/ - username - password - mqconfig/ - mq/ - username - password +etc/ + config/ + dbconfig/ + db/ + username + password + mqconfig/ + mq/ + username + password ---- You can use `configtree:/etc/config/*/` as the import location: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - config: - import: "optional:configtree:/etc/config/*/" +spring: + config: + import: "optional:configtree:/etc/config/*/" ---- This will add `db.username`, `db.password`, `mq.username` and `mq.password` properties. @@ -432,28 +442,29 @@ Configuration trees can also be used for Docker secrets. When a Docker swarm service is granted access to a secret, the secret gets mounted into the container. For example, if a secret named `db.password` is mounted at location `/run/secrets/`, you can make `db.password` available to the Spring environment using the following: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - config: - import: "optional:configtree:/run/secrets/" +spring: + config: + import: "optional:configtree:/run/secrets/" ---- [[features.external-config.files.property-placeholders]] -==== Property Placeholders +=== Property Placeholders + The values in `application.properties` and `application.yaml` are filtered through the existing `Environment` when they are used, so you can refer back to previously defined values (for example, from System properties or environment variables). The standard `$\{name}` property-placeholder syntax can be used anywhere within a value. Property placeholders can also specify a default value using a `:` to separate the default value from the property name, for example `${name:default}`. The use of placeholders with and without defaults is shown in the following example: -[source,yaml,indent=0,subs="verbatim",configblocks] +[configprops%novalidate,yaml] ---- - app: - name: "MyApp" - description: "${app.name} is a Spring Boot application written by ${username:Unknown}" +app: + name: "MyApp" + description: "${app.name} is a Spring Boot application written by ${username:Unknown}" ---- Assuming that the `username` property has not been set elsewhere, `app.description` will have the value `MyApp is a Spring Boot application written by Unknown`. @@ -461,19 +472,20 @@ Assuming that the `username` property has not been set elsewhere, `app.descripti [NOTE] ==== You should always refer to property names in the placeholder using their canonical form (kebab-case using only lowercase letters). -This will allow Spring Boot to use the same logic as it does when <> `@ConfigurationProperties`. +This will allow Spring Boot to use the same logic as it does when xref:features/external-config.adoc#features.external-config.typesafe-configuration-properties.relaxed-binding[relaxed binding] `@ConfigurationProperties`. For example, `${demo.item-price}` will pick up `demo.item-price` and `demo.itemPrice` forms from the `application.properties` file, as well as `DEMO_ITEMPRICE` from the system environment. If you used `${demo.itemPrice}` instead, `demo.item-price` and `DEMO_ITEMPRICE` would not be considered. ==== TIP: You can also use this technique to create "`short`" variants of existing Spring Boot properties. -See the _<>_ how-to for details. +See the xref:how-to:properties-and-configuration.adoc#howto.properties-and-configuration.short-command-line-arguments[] section in "`How-to Guides`" for details. [[features.external-config.files.multi-document]] -==== Working With Multi-Document Files +=== Working With Multi-Document Files + Spring Boot allows you to split a single physical file into multiple logical documents which are each added independently. Documents are processed in order, from top to bottom. Later documents can override the properties defined in earlier ones. @@ -483,42 +495,43 @@ Three consecutive hyphens represent the end of one document, and the start of th For example, the following file has two logical documents: -[source,yaml,indent=0,subs="verbatim"] +[source,yaml] ---- - spring: - application: - name: "MyApp" - --- - spring: - application: - name: "MyCloudApp" - config: - activate: - on-cloud-platform: "kubernetes" +spring: + application: + name: "MyApp" +--- +spring: + application: + name: "MyCloudApp" + config: + activate: + on-cloud-platform: "kubernetes" ---- For `application.properties` files a special `#---` or `!---` comment is used to mark the document splits: -[source,properties,indent=0,subs="verbatim"] +[source,properties] ---- - spring.application.name=MyApp - #--- - spring.application.name=MyCloudApp - spring.config.activate.on-cloud-platform=kubernetes +spring.application.name=MyApp +#--- +spring.application.name=MyCloudApp +spring.config.activate.on-cloud-platform=kubernetes ---- NOTE: Property file separators must not have any leading whitespace and must have exactly three hyphen characters. The lines immediately before and after the separator must not be same comment prefix. TIP: Multi-document property files are often used in conjunction with activation properties such as `spring.config.activate.on-profile`. -See the <> for details. +See the xref:features/external-config.adoc#features.external-config.files.activation-properties[next section] for details. WARNING: Multi-document property files cannot be loaded by using the `@PropertySource` or `@TestPropertySource` annotations. [[features.external-config.files.activation-properties]] -==== Activation Properties +=== Activation Properties + It is sometimes useful to only activate a given set of properties when certain conditions are met. For example, you might have properties that are only relevant when a specific profile is active. @@ -540,88 +553,91 @@ The following activation properties are available: For example, the following specifies that the second document is only active when running on Kubernetes, and only when either the "`prod`" or "`staging`" profiles are active: -[source,yaml,indent=0,subs="verbatim",configblocks] +[configprops%novalidate,yaml] ---- - myprop: - "always-set" - --- - spring: - config: - activate: - on-cloud-platform: "kubernetes" - on-profile: "prod | staging" - myotherprop: "sometimes-set" +myprop: + "always-set" +--- +spring: + config: + activate: + on-cloud-platform: "kubernetes" + on-profile: "prod | staging" +myotherprop: "sometimes-set" ---- [[features.external-config.encrypting]] -=== Encrypting Properties +== Encrypting Properties + Spring Boot does not provide any built-in support for encrypting property values, however, it does provide the hook points necessary to modify values contained in the Spring `Environment`. The `EnvironmentPostProcessor` interface allows you to manipulate the `Environment` before the application starts. -See <> for details. +See xref:how-to:application.adoc#howto.application.customize-the-environment-or-application-context[] for details. If you need a secure way to store credentials and passwords, the https://cloud.spring.io/spring-cloud-vault/[Spring Cloud Vault] project provides support for storing externalized configuration in https://www.vaultproject.io/[HashiCorp Vault]. [[features.external-config.yaml]] -=== Working With YAML +== Working With YAML + https://yaml.org[YAML] is a superset of JSON and, as such, is a convenient format for specifying hierarchical configuration data. The `SpringApplication` class automatically supports YAML as an alternative to properties whenever you have the https://github.com/snakeyaml/snakeyaml[SnakeYAML] library on your classpath. -NOTE: If you use "`Starters`", SnakeYAML is automatically provided by `spring-boot-starter`. +NOTE: If you use starters, SnakeYAML is automatically provided by `spring-boot-starter`. [[features.external-config.yaml.mapping-to-properties]] -==== Mapping YAML to Properties +=== Mapping YAML to Properties + YAML documents need to be converted from their hierarchical format to a flat structure that can be used with the Spring `Environment`. For example, consider the following YAML document: -[source,yaml,indent=0,subs="verbatim"] +[source,yaml] ---- - environments: - dev: - url: "https://dev.example.com" - name: "Developer Setup" - prod: - url: "https://another.example.com" - name: "My Cool App" +environments: + dev: + url: "https://dev.example.com" + name: "Developer Setup" + prod: + url: "https://another.example.com" + name: "My Cool App" ---- In order to access these properties from the `Environment`, they would be flattened as follows: -[source,properties,indent=0,subs="verbatim"] +[source,properties] ---- - environments.dev.url=https://dev.example.com - environments.dev.name=Developer Setup - environments.prod.url=https://another.example.com - environments.prod.name=My Cool App +environments.dev.url=https://dev.example.com +environments.dev.name=Developer Setup +environments.prod.url=https://another.example.com +environments.prod.name=My Cool App ---- Likewise, YAML lists also need to be flattened. They are represented as property keys with `[index]` dereferencers. For example, consider the following YAML: -[source,yaml,indent=0,subs="verbatim"] +[source,yaml] ---- - my: - servers: - - "dev.example.com" - - "another.example.com" + my: + servers: + - "dev.example.com" + - "another.example.com" ---- The preceding example would be transformed into these properties: -[source,properties,indent=0,subs="verbatim"] +[source,properties] ---- - my.servers[0]=dev.example.com - my.servers[1]=another.example.com +my.servers[0]=dev.example.com +my.servers[1]=another.example.com ---- TIP: Properties that use the `[index]` notation can be bound to Java `List` or `Set` objects using Spring Boot's `Binder` class. -For more details see the "`<>`" section below. +For more details see the xref:features/external-config.adoc#features.external-config.typesafe-configuration-properties[] section below. WARNING: YAML files cannot be loaded by using the `@PropertySource` or `@TestPropertySource` annotations. So, in the case that you need to load values that way, you need to use a properties file. @@ -629,8 +645,8 @@ So, in the case that you need to load values that way, you need to use a propert [[features.external-config.yaml.directly-loading]] -[[features.external-config.yaml.directly-loading]] -==== Directly Loading YAML +=== Directly Loading YAML + Spring Framework provides two convenient classes that can be used to load YAML documents. The `YamlPropertiesFactoryBean` loads YAML as `Properties` and the `YamlMapFactoryBean` loads YAML as a `Map`. @@ -639,19 +655,20 @@ You can also use the `YamlPropertySourceLoader` class if you want to load YAML a [[features.external-config.random-values]] -=== Configuring Random Values +== Configuring Random Values + The `RandomValuePropertySource` is useful for injecting random values (for example, into secrets or test cases). It can produce integers, longs, uuids, or strings, as shown in the following example: -[source,yaml,indent=0,subs="verbatim",configblocks] +[configprops%novalidate,yaml] ---- - my: - secret: "${random.value}" - number: "${random.int}" - bignumber: "${random.long}" - uuid: "${random.uuid}" - number-less-than-ten: "${random.int(10)}" - number-in-range: "${random.int[1024,65536]}" +my: + secret: "${random.value}" + number: "${random.int}" + bignumber: "${random.long}" + uuid: "${random.uuid}" + number-less-than-ten: "${random.int(10)}" + number-in-range: "${random.int[1024,65536]}" ---- The `+random.int*+` syntax is `OPEN value (,max) CLOSE` where the `OPEN,CLOSE` are any character and `value,max` are integers. @@ -660,7 +677,8 @@ If `max` is provided, then `value` is the minimum value and `max` is the maximum [[features.external-config.system-environment]] -=== Configuring System Environment Properties +== Configuring System Environment Properties + Spring Boot supports setting a prefix for environment properties. This is useful if the system environment is shared by multiple Spring Boot applications with different configuration requirements. The prefix for system environment properties can be set directly on `SpringApplication`. @@ -670,19 +688,21 @@ For example, if you set the prefix to `input`, a property such as `remote.timeou [[features.external-config.typesafe-configuration-properties]] -=== Type-safe Configuration Properties +== Type-safe Configuration Properties + Using the `@Value("$\{property}")` annotation to inject configuration properties can sometimes be cumbersome, especially if you are working with multiple properties or your data is hierarchical in nature. Spring Boot provides an alternative method of working with properties that lets strongly typed beans govern and validate the configuration of your application. -TIP: See also the <>. +TIP: See also the xref:features/external-config.adoc#features.external-config.typesafe-configuration-properties.vs-value-annotation[differences between `@Value` and type-safe configuration properties]. [[features.external-config.typesafe-configuration-properties.java-bean-binding]] -==== JavaBean Properties Binding +=== JavaBean Properties Binding + It is possible to bind a bean declaring standard JavaBean properties as shown in the following example: -include::code:MyProperties[] +include-code::MyProperties[] The preceding POJO defines the following properties: @@ -717,10 +737,11 @@ Finally, only standard Java Bean properties are considered and binding on static [[features.external-config.typesafe-configuration-properties.constructor-binding]] -==== Constructor Binding +=== Constructor Binding + The example in the previous section can be rewritten in an immutable fashion as shown in the following example: -include::code:MyProperties[] +include-code::MyProperties[] In this setup, the presence of a single parameterized constructor implies that constructor binding should be used. This means that the binder will find a constructor with the parameters that you wish to have bound. @@ -737,12 +758,12 @@ The conversion service will be applied to coerce the annotation's `String` value Referring to the previous example, if no properties are bound to `Security`, the `MyProperties` instance will contain a `null` value for `security`. To make it contain a non-null instance of `Security` even when no properties are bound to it (when using Kotlin, this will require the `username` and `password` parameters of `Security` to be declared as nullable as they do not have default values), use an empty `@DefaultValue` annotation: -include::code:nonnull/MyProperties[tag=*] +include-code::nonnull/MyProperties[tag=*] NOTE: To use constructor binding the class must be enabled using `@EnableConfigurationProperties` or configuration property scanning. You cannot use constructor binding with beans that are created by the regular Spring mechanisms (for example `@Component` beans, beans created by using `@Bean` methods or beans loaded by using `@Import`) -NOTE: To use constructor binding in a native image the class must be compiled with `-parameters`. +NOTE: To use constructor binding the class must be compiled with `-parameters`. This will happen automatically if you use Spring Boot's Gradle plugin or if you use Maven and `spring-boot-starter-parent`. NOTE: The use of `java.util.Optional` with `@ConfigurationProperties` is not recommended as it is primarily intended for use as a return type. @@ -752,7 +773,8 @@ For consistency with properties of other types, if you do declare an `Optional` [[features.external-config.typesafe-configuration-properties.enabling-annotated-types]] -==== Enabling @ConfigurationProperties-annotated Types +=== Enabling @ConfigurationProperties-annotated Types + Spring Boot provides infrastructure to bind `@ConfigurationProperties` types and register them as beans. You can either enable configuration properties on a class-by-class basis or enable configuration property scanning that works in a similar manner to component scanning. @@ -760,15 +782,15 @@ Sometimes, classes annotated with `@ConfigurationProperties` might not be suitab In these cases, specify the list of types to process using the `@EnableConfigurationProperties` annotation. This can be done on any `@Configuration` class, as shown in the following example: -include::code:MyConfiguration[] -include::code:SomeProperties[] +include-code::MyConfiguration[] +include-code::SomeProperties[] To use configuration property scanning, add the `@ConfigurationPropertiesScan` annotation to your application. Typically, it is added to the main application class that is annotated with `@SpringBootApplication` but it can be added to any `@Configuration` class. By default, scanning will occur from the package of the class that declares the annotation. If you want to define specific packages to scan, you can do so as shown in the following example: -include::code:MyApplication[] +include-code::MyApplication[] [NOTE] ==== @@ -785,51 +807,54 @@ If you still want to inject other beans using the constructor, the configuration [[features.external-config.typesafe-configuration-properties.using-annotated-types]] -==== Using @ConfigurationProperties-annotated Types +=== Using @ConfigurationProperties-annotated Types + This style of configuration works particularly well with the `SpringApplication` external YAML configuration, as shown in the following example: -[source,yaml,indent=0,subs="verbatim"] +[source,yaml] ---- - my: - service: - remote-address: 192.168.1.1 - security: - username: "admin" - roles: - - "USER" - - "ADMIN" +my: + service: + remote-address: 192.168.1.1 + security: + username: "admin" + roles: + - "USER" + - "ADMIN" ---- To work with `@ConfigurationProperties` beans, you can inject them in the same way as any other bean, as shown in the following example: -include::code:MyService[] +include-code::MyService[] TIP: Using `@ConfigurationProperties` also lets you generate metadata files that can be used by IDEs to offer auto-completion for your own keys. -See the <> for details. +See the xref:specification:configuration-metadata/index.adoc[appendix] for details. [[features.external-config.typesafe-configuration-properties.third-party-configuration]] -==== Third-party Configuration +=== Third-party Configuration + As well as using `@ConfigurationProperties` to annotate a class, you can also use it on public `@Bean` methods. Doing so can be particularly useful when you want to bind properties to third-party components that are outside of your control. To configure a bean from the `Environment` properties, add `@ConfigurationProperties` to its bean registration, as shown in the following example: -include::code:ThirdPartyConfiguration[] +include-code::ThirdPartyConfiguration[] Any JavaBean property defined with the `another` prefix is mapped onto that `AnotherComponent` bean in manner similar to the preceding `SomeProperties` example. [[features.external-config.typesafe-configuration-properties.relaxed-binding]] -==== Relaxed Binding +=== Relaxed Binding + Spring Boot uses some relaxed rules for binding `Environment` properties to `@ConfigurationProperties` beans, so there does not need to be an exact match between the `Environment` property name and the bean property name. Common examples where this is useful include dash-separated environment properties (for example, `context-path` binds to `contextPath`), and capitalized environment properties (for example, `PORT` binds to `port`). As an example, consider the following `@ConfigurationProperties` class: -include::code:MyPersonProperties[] +include-code::MyPersonProperties[] With the preceding code, the following properties names can all be used: @@ -867,8 +892,8 @@ NOTE: The `prefix` value for the annotation _must_ be in kebab case (lowercase a | Standard YAML list syntax or comma-separated values | Environment Variables -| Upper case format with underscore as the delimiter (see <>). -| Numeric values surrounded by underscores (see <>) +| Upper case format with underscore as the delimiter (see xref:features/external-config.adoc#features.external-config.typesafe-configuration-properties.relaxed-binding.environment-variables[]). +| Numeric values surrounded by underscores (see xref:features/external-config.adoc#features.external-config.typesafe-configuration-properties.relaxed-binding.environment-variables[]) | System properties | Camel case, kebab case, or underscore notation @@ -880,29 +905,20 @@ TIP: We recommend that, when possible, properties are stored in lower-case kebab [[features.external-config.typesafe-configuration-properties.relaxed-binding.maps]] -===== Binding Maps +==== Binding Maps + When binding to `Map` properties you may need to use a special bracket notation so that the original `key` value is preserved. If the key is not surrounded by `[]`, any characters that are not alpha-numeric, `-` or `.` are removed. For example, consider binding the following properties to a `Map`: - -[source,properties,indent=0,subs="verbatim",role="primary"] -.Properties ----- - my.map.[/key1]=value1 - my.map.[/key2]=value2 - my.map./key3=value3 ----- - -[source,yaml,indent=0,subs="verbatim",role="secondary"] -.Yaml +[configprops%novalidate,yaml] ---- - my: - map: - "[/key1]": "value1" - "[/key2]": "value2" - "/key3": "value3" +my: + map: + "[/key1]": "value1" + "[/key2]": "value2" + "/key3": "value3" ---- NOTE: For YAML files, the brackets need to be surrounded by quotes for the keys to be parsed properly. @@ -919,7 +935,8 @@ For example, binding `a.b=c` to `Map` will return a Map with the [[features.external-config.typesafe-configuration-properties.relaxed-binding.environment-variables]] -===== Binding From Environment Variables +==== Binding From Environment Variables + Most operating systems impose strict rules around the names that can be used for environment variables. For example, Linux shell variables can contain only letters (`a` to `z` or `A` to `Z`), numbers (`0` to `9`) or the underscore character (`_`). By convention, Unix shell variables will also have their names in UPPERCASE. @@ -942,37 +959,39 @@ For example, the configuration property `my.service[0].other` would use an envir [[features.external-config.typesafe-configuration-properties.relaxed-binding.caching]] -===== Caching +==== Caching + Relaxed binding uses a cache to improve performance. By default, this caching is only applied to immutable property sources. To customize this behavior, for example to enable caching for mutable property sources, use `ConfigurationPropertyCaching`. [[features.external-config.typesafe-configuration-properties.merging-complex-types]] -==== Merging Complex Types +=== Merging Complex Types + When lists are configured in more than one place, overriding works by replacing the entire list. For example, assume a `MyPojo` object with `name` and `description` attributes that are `null` by default. The following example exposes a list of `MyPojo` objects from `MyProperties`: -include::code:list/MyProperties[] +include-code::list/MyProperties[] Consider the following configuration: -[source,yaml,indent=0,subs="verbatim",configblocks] +[configprops%novalidate,yaml] ---- - my: - list: - - name: "my name" - description: "my description" - --- - spring: - config: - activate: - on-profile: "dev" - my: - list: - - name: "my another name" +my: + list: + - name: "my name" + description: "my description" +--- +spring: + config: + activate: + on-profile: "dev" +my: + list: + - name: "my another name" ---- If the `dev` profile is not active, `MyProperties.list` contains one `MyPojo` entry, as previously defined. @@ -982,22 +1001,22 @@ This configuration _does not_ add a second `MyPojo` instance to the list, and it When a `List` is specified in multiple profiles, the one with the highest priority (and only that one) is used. Consider the following example: -[source,yaml,indent=0,subs="verbatim",configblocks] +[configprops%novalidate,yaml] ---- - my: - list: - - name: "my name" - description: "my description" - - name: "another name" - description: "another description" - --- - spring: - config: - activate: - on-profile: "dev" - my: - list: - - name: "my another name" +my: + list: + - name: "my name" + description: "my description" + - name: "another name" + description: "another description" +--- +spring: + config: + activate: + on-profile: "dev" +my: + list: + - name: "my another name" ---- In the preceding example, if the `dev` profile is active, `MyProperties.list` contains _one_ `MyPojo` entry (with a name of `my another name` and a description of `null`). @@ -1007,29 +1026,29 @@ For `Map` properties, you can bind with property values drawn from multiple sour However, for the same property in multiple sources, the one with the highest priority is used. The following example exposes a `Map` from `MyProperties`: -include::code:map/MyProperties[] +include-code::map/MyProperties[] Consider the following configuration: -[source,yaml,indent=0,subs="verbatim",configblocks] +[configprops%novalidate,yaml] ---- - my: - map: - key1: - name: "my name 1" - description: "my description 1" - --- - spring: - config: - activate: - on-profile: "dev" - my: - map: - key1: - name: "dev name 1" - key2: - name: "dev name 2" - description: "dev description 2" +my: + map: + key1: + name: "my name 1" + description: "my description 1" +--- +spring: + config: + activate: + on-profile: "dev" +my: + map: + key1: + name: "dev name 1" + key2: + name: "dev name 2" + description: "dev description 2" ---- If the `dev` profile is not active, `MyProperties.map` contains one entry with key `key1` (with a name of `my name 1` and a description of `my description 1`). @@ -1040,7 +1059,8 @@ NOTE: The preceding merging rules apply to properties from all property sources, [[features.external-config.typesafe-configuration-properties.conversion]] -==== Properties Conversion +=== Properties Conversion + Spring Boot attempts to coerce the external application properties to the right type when it binds to the `@ConfigurationProperties` beans. If you need custom type conversion, you can provide a `ConversionService` bean (with a bean named `conversionService`) or custom property editors (through a `CustomEditorConfigurer` bean) or custom `Converters` (with bean definitions annotated as `@ConfigurationPropertiesBinding`). @@ -1051,17 +1071,18 @@ You may want to rename your custom `ConversionService` if it is not required for [[features.external-config.typesafe-configuration-properties.conversion.durations]] -===== Converting Durations +==== Converting Durations + Spring Boot has dedicated support for expressing durations. If you expose a `java.time.Duration` property, the following formats in application properties are available: * A regular `long` representation (using milliseconds as the default unit unless a `@DurationUnit` has been specified) -* The standard ISO-8601 format {java-api}/java.base/java/time/Duration.html#parse(java.lang.CharSequence)[used by `java.time.Duration`] +* The standard ISO-8601 format {apiref-openjdk}/java.base/java/time/Duration.html#parse(java.lang.CharSequence)[used by `java.time.Duration`] * A more readable format where the value and the unit are coupled (`10s` means 10 seconds) Consider the following example: -include::code:javabeanbinding/MyProperties[] +include-code::javabeanbinding/MyProperties[] To specify a session timeout of 30 seconds, `30`, `PT30S` and `30s` are all equivalent. A read timeout of 500ms can be specified in any of the following form: `500`, `PT0.5S` and `500ms`. @@ -1081,7 +1102,7 @@ The default unit is milliseconds and can be overridden using `@DurationUnit` as If you prefer to use constructor binding, the same properties can be exposed, as shown in the following example: -include::code:constructorbinding/MyProperties[] +include-code::constructorbinding/MyProperties[] TIP: If you are upgrading a `Long` property, make sure to define the unit (using `@DurationUnit`) if it is not milliseconds. @@ -1090,12 +1111,13 @@ Doing so gives a transparent upgrade path while supporting a much richer format. [[features.external-config.typesafe-configuration-properties.conversion.periods]] -===== Converting Periods +==== Converting Periods + In addition to durations, Spring Boot can also work with `java.time.Period` type. The following formats can be used in application properties: * An regular `int` representation (using days as the default unit unless a `@PeriodUnit` has been specified) -* The standard ISO-8601 format {java-api}/java.base/java/time/Period.html#parse(java.lang.CharSequence)[used by `java.time.Period`] +* The standard ISO-8601 format {apiref-openjdk}/java.base/java/time/Period.html#parse(java.lang.CharSequence)[used by `java.time.Period`] * A simpler format where the value and the unit pairs are coupled (`1y3d` means 1 year and 3 days) The following units are supported with the simple format: @@ -1110,7 +1132,8 @@ NOTE: The `java.time.Period` type never actually stores the number of weeks, it [[features.external-config.typesafe-configuration-properties.conversion.data-sizes]] -===== Converting Data Sizes +==== Converting Data Sizes + Spring Framework has a `DataSize` value type that expresses a size in bytes. If you expose a `DataSize` property, the following formats in application properties are available: @@ -1119,7 +1142,7 @@ If you expose a `DataSize` property, the following formats in application proper Consider the following example: -include::code:javabeanbinding/MyProperties[] +include-code::javabeanbinding/MyProperties[] To specify a buffer size of 10 megabytes, `10` and `10MB` are equivalent. A size threshold of 256 bytes can be specified as `256` or `256B`. @@ -1137,7 +1160,7 @@ The default unit is bytes and can be overridden using `@DataSizeUnit` as illustr If you prefer to use constructor binding, the same properties can be exposed, as shown in the following example: -include::code:constructorbinding/MyProperties[] +include-code::constructorbinding/MyProperties[] TIP: If you are upgrading a `Long` property, make sure to define the unit (using `@DataSizeUnit`) if it is not bytes. Doing so gives a transparent upgrade path while supporting a much richer format. @@ -1145,19 +1168,20 @@ Doing so gives a transparent upgrade path while supporting a much richer format. [[features.external-config.typesafe-configuration-properties.validation]] -==== @ConfigurationProperties Validation +=== @ConfigurationProperties Validation + Spring Boot attempts to validate `@ConfigurationProperties` classes whenever they are annotated with Spring's `@Validated` annotation. You can use JSR-303 `jakarta.validation` constraint annotations directly on your configuration class. To do so, ensure that a compliant JSR-303 implementation is on your classpath and then add constraint annotations to your fields, as shown in the following example: -include::code:MyProperties[] +include-code::MyProperties[] TIP: You can also trigger validation by annotating the `@Bean` method that creates the configuration properties with `@Validated`. -To ensure that validation is always triggered for nested properties, even when no properties are found, the associated field must be annotated with `@Valid`. +To cascade validation to nested properties the associated field must be annotated with `@Valid`. The following example builds on the preceding `MyProperties` example: -include::code:nested/MyProperties[] +include-code::nested/MyProperties[] You can also add a custom Spring `Validator` by creating a bean definition called `configurationPropertiesValidator`. The `@Bean` method should be declared `static`. @@ -1166,12 +1190,13 @@ Doing so avoids any problems that may be caused by early instantiation. TIP: The `spring-boot-actuator` module includes an endpoint that exposes all `@ConfigurationProperties` beans. Point your web browser to `/actuator/configprops` or use the equivalent JMX endpoint. -See the "<>" section for details. +See the xref:actuator/endpoints.adoc[Production ready features] section for details. [[features.external-config.typesafe-configuration-properties.vs-value-annotation]] -==== @ConfigurationProperties vs. @Value +=== @ConfigurationProperties vs. @Value + The `@Value` annotation is a core container feature, and it does not provide the same features as type-safe configuration properties. The following table summarizes the features that are supported by `@ConfigurationProperties` and `@Value`: @@ -1179,11 +1204,11 @@ The following table summarizes the features that are supported by `@Configuratio |=== | Feature |`@ConfigurationProperties` |`@Value` -| <> +| xref:features/external-config.adoc#features.external-config.typesafe-configuration-properties.relaxed-binding[Relaxed binding] | Yes -| Limited (see <>) +| Limited (see xref:features/external-config.adoc#features.external-config.typesafe-configuration-properties.vs-value-annotation.note[note below]) -| <> +| xref:specification:configuration-metadata/index.adoc[Meta-data support] | Yes | No @@ -1196,7 +1221,7 @@ The following table summarizes the features that are supported by `@Configuratio [NOTE] ==== If you do want to use `@Value`, we recommend that you refer to property names using their canonical form (kebab-case using only lowercase letters). -This will allow Spring Boot to use the same logic as it does when <> `@ConfigurationProperties`. +This will allow Spring Boot to use the same logic as it does when xref:features/external-config.adoc#features.external-config.typesafe-configuration-properties.relaxed-binding[relaxed binding] `@ConfigurationProperties`. For example, `@Value("${demo.item-price}")` will pick up `demo.item-price` and `demo.itemPrice` forms from the `application.properties` file, as well as `DEMO_ITEMPRICE` from the system environment. If you used `@Value("${demo.itemPrice}")` instead, `demo.item-price` and `DEMO_ITEMPRICE` would not be considered. @@ -1205,6 +1230,6 @@ If you used `@Value("${demo.itemPrice}")` instead, `demo.item-price` and `DEMO_I If you define a set of configuration keys for your own components, we recommend you group them in a POJO annotated with `@ConfigurationProperties`. Doing so will provide you with structured, type-safe object that you can inject into your own beans. -`SpEL` expressions from <> are not processed at time of parsing these files and populating the environment. +`SpEL` expressions from xref:features/external-config.adoc#features.external-config.files[application property files] are not processed at time of parsing these files and populating the environment. However, it is possible to write a `SpEL` expression in `@Value`. If the value of a property from an application property file is a `SpEL` expression, it will be evaluated when consumed through `@Value`. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/index.adoc new file mode 100644 index 000000000000..6c901400c153 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/index.adoc @@ -0,0 +1,7 @@ +[[features]] += Core Features + +This section dives into the details of Spring Boot. +Here you can learn about the key features that you may want to use and customize. +If you have not already done so, you might want to read the xref:tutorial:index.adoc[] and xref:using/index.adoc[] sections, so that you have a good grounding of the basics. + diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/internationalization.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/internationalization.adoc new file mode 100644 index 000000000000..198f1d2ef4ad --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/internationalization.adoc @@ -0,0 +1,23 @@ +[[features.internationalization]] += Internationalization + +Spring Boot supports localized messages so that your application can cater to users of different language preferences. +By default, Spring Boot looks for the presence of a `messages` resource bundle at the root of the classpath. + +NOTE: The auto-configuration applies when the default properties file for the configured resource bundle is available (`messages.properties` by default). +If your resource bundle contains only language-specific properties files, you are required to add the default. +If no properties file is found that matches any of the configured base names, there will be no auto-configured `MessageSource`. + +The basename of the resource bundle as well as several other attributes can be configured using the `spring.messages` namespace, as shown in the following example: + +[configprops,yaml] +---- +spring: + messages: + basename: "messages,config.i18n.messages" + fallback-to-system-locale: false +---- + +TIP: `spring.messages.basename` supports comma-separated list of locations, either a package qualifier or a resource resolved from the classpath root. + +See xref:api:java/org/springframework/boot/autoconfigure/context/MessageSourceProperties.html[`MessageSourceProperties`] for more supported options. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/json.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/json.adoc new file mode 100644 index 000000000000..e812c244546b --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/json.adoc @@ -0,0 +1,70 @@ +[[features.json]] += JSON + +Spring Boot provides integration with three JSON mapping libraries: + +- Gson +- Jackson +- JSON-B + +Jackson is the preferred and default library. + + + +[[features.json.jackson]] +== Jackson + +Auto-configuration for Jackson is provided and Jackson is part of `spring-boot-starter-json`. +When Jackson is on the classpath an `ObjectMapper` bean is automatically configured. +Several configuration properties are provided for xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-jackson-objectmapper[customizing the configuration of the `ObjectMapper`]. + + + +[[features.json.jackson.custom-serializers-and-deserializers]] +=== Custom Serializers and Deserializers + +If you use Jackson to serialize and deserialize JSON data, you might want to write your own `JsonSerializer` and `JsonDeserializer` classes. +Custom serializers are usually https://github.com/FasterXML/jackson-docs/wiki/JacksonHowToCustomSerializers[registered with Jackson through a module], but Spring Boot provides an alternative `@JsonComponent` annotation that makes it easier to directly register Spring Beans. + +You can use the `@JsonComponent` annotation directly on `JsonSerializer`, `JsonDeserializer` or `KeyDeserializer` implementations. +You can also use it on classes that contain serializers/deserializers as inner classes, as shown in the following example: + +include-code::MyJsonComponent[] + +All `@JsonComponent` beans in the `ApplicationContext` are automatically registered with Jackson. +Because `@JsonComponent` is meta-annotated with `@Component`, the usual component-scanning rules apply. + +Spring Boot also provides xref:api:java/org/springframework/boot/jackson/JsonObjectSerializer.html[`JsonObjectSerializer`] and xref:api:java/org/springframework/boot/jackson/JsonObjectDeserializer.html[`JsonObjectDeserializer`] base classes that provide useful alternatives to the standard Jackson versions when serializing objects. +See xref:api:java/org/springframework/boot/jackson/JsonObjectSerializer.html[`JsonObjectSerializer`] and xref:api:java/org/springframework/boot/jackson/JsonObjectDeserializer.html[`JsonObjectDeserializer`] in the API documentation for details. + +The example above can be rewritten to use `JsonObjectSerializer`/`JsonObjectDeserializer` as follows: + +include-code::object/MyJsonComponent[] + + + +[[features.json.jackson.mixins]] +=== Mixins + +Jackson has support for mixins that can be used to mix additional annotations into those already declared on a target class. +Spring Boot's Jackson auto-configuration will scan your application's packages for classes annotated with `@JsonMixin` and register them with the auto-configured `ObjectMapper`. +The registration is performed by Spring Boot's `JsonMixinModule`. + + + +[[features.json.gson]] +== Gson + +Auto-configuration for Gson is provided. +When Gson is on the classpath a `Gson` bean is automatically configured. +Several `+spring.gson.*+` configuration properties are provided for customizing the configuration. +To take more control, one or more `GsonBuilderCustomizer` beans can be used. + + + +[[features.json.json-b]] +== JSON-B + +Auto-configuration for JSON-B is provided. +When the JSON-B API and an implementation are on the classpath a `Jsonb` bean will be automatically configured. +The preferred JSON-B implementation is Eclipse Yasson for which dependency management is provided. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/kotlin.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/kotlin.adoc similarity index 75% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/kotlin.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/kotlin.adoc index a50eb957cc29..c933e7f9d63c 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/kotlin.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/kotlin.adoc @@ -1,9 +1,10 @@ [[features.kotlin]] -== Kotlin Support -https://kotlinlang.org[Kotlin] is a statically-typed language targeting the JVM (and other platforms) which allows writing concise and elegant code while providing {kotlin-docs}java-interop.html[interoperability] with existing libraries written in Java. += Kotlin Support + +https://kotlinlang.org[Kotlin] is a statically-typed language targeting the JVM (and other platforms) which allows writing concise and elegant code while providing {url-kotlin-docs}/java-interop.html[interoperability] with existing libraries written in Java. Spring Boot provides Kotlin support by leveraging the support in other Spring projects such as Spring Framework, Spring Data, and Reactor. -See the {spring-framework-docs}/languages/kotlin.html[Spring Framework Kotlin support documentation] for more information. +See the {url-spring-framework-docs}/languages/kotlin.html[Spring Framework Kotlin support documentation] for more information. The easiest way to start with Spring Boot and Kotlin is to follow https://spring.io/guides/tutorials/spring-boot-kotlin/[this comprehensive tutorial]. You can create new Kotlin projects by using https://start.spring.io/#!language=kotlin[start.spring.io]. @@ -12,12 +13,13 @@ Feel free to join the #spring channel of https://slack.kotlinlang.org/[Kotlin Sl [[features.kotlin.requirements]] -=== Requirements +== Requirements + Spring Boot requires at least Kotlin 1.7.x and manages a suitable Kotlin version through dependency management. To use Kotlin, `org.jetbrains.kotlin:kotlin-stdlib` and `org.jetbrains.kotlin:kotlin-reflect` must be present on the classpath. The `kotlin-stdlib` variants `kotlin-stdlib-jdk7` and `kotlin-stdlib-jdk8` can also be used. -Since https://discuss.kotlinlang.org/t/classes-final-by-default/166[Kotlin classes are final by default], you are likely to want to configure {kotlin-docs}compiler-plugins.html#spring-support[kotlin-spring] plugin in order to automatically open Spring-annotated classes so that they can be proxied. +Since https://discuss.kotlinlang.org/t/classes-final-by-default/166[Kotlin classes are final by default], you are likely to want to configure {url-kotlin-docs}/compiler-plugins.html#spring-support[kotlin-spring] plugin in order to automatically open Spring-annotated classes so that they can be proxied. https://github.com/FasterXML/jackson-module-kotlin[Jackson's Kotlin module] is required for serializing / deserializing JSON data in Kotlin. It is automatically registered when found on the classpath. @@ -28,15 +30,16 @@ TIP: These dependencies and plugins are provided by default if one bootstraps a [[features.kotlin.null-safety]] -=== Null-safety -One of Kotlin's key features is {kotlin-docs}null-safety.html[null-safety]. +== Null-safety + +One of Kotlin's key features is {url-kotlin-docs}/null-safety.html[null-safety]. It deals with `null` values at compile time rather than deferring the problem to runtime and encountering a `NullPointerException`. This helps to eliminate a common source of bugs without paying the cost of wrappers like `Optional`. Kotlin also allows using functional constructs with nullable values as described in this https://www.baeldung.com/kotlin-null-safety[comprehensive guide to null-safety in Kotlin]. Although Java does not allow one to express null-safety in its type system, Spring Framework, Spring Data, and Reactor now provide null-safety of their API through tooling-friendly annotations. -By default, types from Java APIs used in Kotlin are recognized as {kotlin-docs}java-interop.html#null-safety-and-platform-types[platform types] for which null-checks are relaxed. -{kotlin-docs}java-interop.html#jsr-305-support[Kotlin's support for JSR 305 annotations] combined with nullability annotations provide null-safety for the related Spring API in Kotlin. +By default, types from Java APIs used in Kotlin are recognized as {url-kotlin-docs}/java-interop.html#null-safety-and-platform-types[platform types] for which null-checks are relaxed. +{url-kotlin-docs}/java-interop.html#jsr-305-support[Kotlin's support for JSR 305 annotations] combined with nullability annotations provide null-safety for the related Spring API in Kotlin. The JSR 305 checks can be configured by adding the `-Xjsr305` compiler flag with the following options: `-Xjsr305={strict|warn|ignore}`. The default behavior is the same as `-Xjsr305=warn`. @@ -44,47 +47,49 @@ The `strict` value is required to have null-safety taken in account in Kotlin ty WARNING: Generic type arguments, varargs and array elements nullability are not yet supported. See https://jira.spring.io/browse/SPR-15942[SPR-15942] for up-to-date information. -Also be aware that Spring Boot's own API is {github-issues}10712[not yet annotated]. +Also be aware that Spring Boot's own API is {url-github-issues}/10712[not yet annotated]. [[features.kotlin.api]] -=== Kotlin API +== Kotlin API [[features.kotlin.api.run-application]] -==== runApplication +=== runApplication + Spring Boot provides an idiomatic way to run an application with `runApplication(*args)` as shown in the following example: -[source,kotlin,indent=0,subs="verbatim"] +[source,kotlin] ---- - import org.springframework.boot.autoconfigure.SpringBootApplication - import org.springframework.boot.runApplication +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication - @SpringBootApplication - class MyApplication +@SpringBootApplication +class MyApplication - fun main(args: Array) { - runApplication(*args) - } +fun main(args: Array) { + runApplication(*args) +} ---- This is a drop-in replacement for `SpringApplication.run(MyApplication::class.java, *args)`. It also allows customization of the application as shown in the following example: -[source,kotlin,indent=0,subs="verbatim"] +[source,kotlin] ---- - runApplication(*args) { - setBannerMode(OFF) - } +runApplication(*args) { + setBannerMode(OFF) +} ---- [[features.kotlin.api.extensions]] -==== Extensions -Kotlin {kotlin-docs}extensions.html[extensions] provide the ability to extend existing classes with additional functionality. +=== Extensions + +Kotlin {url-kotlin-docs}/extensions.html[extensions] provide the ability to extend existing classes with additional functionality. The Spring Boot Kotlin API makes use of these extensions to add new Kotlin specific conveniences to existing APIs. `TestRestTemplate` extensions, similar to those provided by Spring Framework for `RestOperations` in Spring Framework, are provided. @@ -93,7 +98,8 @@ Among other things, the extensions make it possible to take advantage of Kotlin [[features.kotlin.dependency-management]] -=== Dependency management +== Dependency Management + In order to avoid mixing different versions of Kotlin dependencies on the classpath, Spring Boot imports the Kotlin BOM. With Maven, the Kotlin version can be customized by setting the `kotlin.version` property and plugin management is provided for `kotlin-maven-plugin`. @@ -107,10 +113,11 @@ TIP: `org.jetbrains.kotlinx:kotlinx-coroutines-reactor` dependency is provided b [[features.kotlin.configuration-properties]] -=== @ConfigurationProperties -`@ConfigurationProperties` when used in combination with <> supports classes with immutable `val` properties as shown in the following example: +== @ConfigurationProperties + +`@ConfigurationProperties` when used in combination with xref:features/external-config.adoc#features.external-config.typesafe-configuration-properties.constructor-binding[constructor binding] supports classes with immutable `val` properties as shown in the following example: -[source,kotlin,indent=0,subs="verbatim"] +[source,kotlin] ---- @ConfigurationProperties("example.kotlin") data class KotlinExampleProperties( @@ -125,30 +132,32 @@ data class KotlinExampleProperties( } ---- -TIP: To generate <> using the annotation processor, {kotlin-docs}kapt.html[`kapt` should be configured] with the `spring-boot-configuration-processor` dependency. +TIP: To generate xref:specification:configuration-metadata/annotation-processor.adoc[your own metadata] using the annotation processor, {url-kotlin-docs}/kapt.html[`kapt` should be configured] with the `spring-boot-configuration-processor` dependency. Note that some features (such as detecting the default value or deprecated items) are not working due to limitations in the model kapt provides. [[features.kotlin.testing]] -=== Testing +== Testing + While it is possible to use JUnit 4 to test Kotlin code, JUnit 5 is provided by default and is recommended. JUnit 5 enables a test class to be instantiated once and reused for all of the class's tests. This makes it possible to use `@BeforeAll` and `@AfterAll` annotations on non-static methods, which is a good fit for Kotlin. To mock Kotlin classes, https://mockk.io/[MockK] is recommended. -If you need the `MockK` equivalent of the Mockito specific <>, you can use https://github.com/Ninja-Squad/springmockk[SpringMockK] which provides similar `@MockkBean` and `@SpykBean` annotations. +If you need the `MockK` equivalent of the Mockito specific xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.mocking-beans[`@MockitoBean` and `@MockitoSpyBean` annotations], you can use https://github.com/Ninja-Squad/springmockk[SpringMockK] which provides similar `@MockkBean` and `@SpykBean` annotations. [[features.kotlin.resources]] -=== Resources +== Resources [[features.kotlin.resources.further-reading]] -==== Further reading -* {kotlin-docs}[Kotlin language reference] +=== Further Reading + +* {url-kotlin-docs}[Kotlin language reference] * https://kotlinlang.slack.com/[Kotlin Slack] (with a dedicated #spring channel) * https://stackoverflow.com/questions/tagged/spring+kotlin[Stack Overflow with `spring` and `kotlin` tags] * https://try.kotlinlang.org/[Try Kotlin in your browser] @@ -163,7 +172,8 @@ If you need the `MockK` equivalent of the Mockito specific <=+` where `level` is one of TRACE, DEBUG, INFO, WARN, ERROR, FATAL, or OFF. +The `root` logger can be configured by using `logging.level.root`. + +The following example shows potential logging settings in `application.properties`: + +[configprops,yaml] +---- +logging: + level: + root: "warn" + org.springframework.web: "debug" + org.hibernate: "error" +---- + +It is also possible to set logging levels using environment variables. +For example, `LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_WEB=DEBUG` will set `org.springframework.web` to `DEBUG`. + +NOTE: The above approach will only work for package level logging. +Since relaxed binding always converts environment variables to lowercase, it is not possible to configure logging for an individual class in this way. +If you need to configure logging for a class, you can use xref:features/external-config.adoc#features.external-config.application-json[the `SPRING_APPLICATION_JSON`] variable. + + + +[[features.logging.log-groups]] +== Log Groups + +It is often useful to be able to group related loggers together so that they can all be configured at the same time. +For example, you might commonly change the logging levels for _all_ Tomcat related loggers, but you can not easily remember top level packages. + +To help with this, Spring Boot allows you to define logging groups in your Spring `Environment`. +For example, here is how you could define a "`tomcat`" group by adding it to your `application.properties`: + +[configprops,yaml] +---- +logging: + group: + tomcat: "org.apache.catalina,org.apache.coyote,org.apache.tomcat" +---- + +Once defined, you can change the level for all the loggers in the group with a single line: + +[configprops,yaml] +---- +logging: + level: + tomcat: "trace" +---- + +Spring Boot includes the following pre-defined logging groups that can be used out-of-the-box: + +[cols="1,4"] +|=== +| Name | Loggers + +| web +| `org.springframework.core.codec`, `org.springframework.http`, `org.springframework.web`, `org.springframework.boot.actuate.endpoint.web`, `org.springframework.boot.web.servlet.ServletContextInitializerBeans` + +| sql +| `org.springframework.jdbc.core`, `org.hibernate.SQL`, `org.jooq.tools.LoggerListener` +|=== + + + +[[features.logging.shutdown-hook]] +== Using a Log Shutdown Hook + +In order to release logging resources when your application terminates, a shutdown hook that will trigger log system cleanup when the JVM exits is provided. +This shutdown hook is registered automatically unless your application is deployed as a war file. +If your application has complex context hierarchies the shutdown hook may not meet your needs. +If it does not, disable the shutdown hook and investigate the options provided directly by the underlying logging system. +For example, Logback offers https://logback.qos.ch/manual/loggingSeparation.html[context selectors] which allow each Logger to be created in its own context. +You can use the configprop:logging.register-shutdown-hook[] property to disable the shutdown hook. +Setting it to `false` will disable the registration. +You can set the property in your `application.properties` or `application.yaml` file: + +[configprops,yaml] +---- +logging: + register-shutdown-hook: false +---- + + + +[[features.logging.custom-log-configuration]] +== Custom Log Configuration + +The various logging systems can be activated by including the appropriate libraries on the classpath and can be further customized by providing a suitable configuration file in the root of the classpath or in a location specified by the following Spring `Environment` property: configprop:logging.config[]. + +You can force Spring Boot to use a particular logging system by using the `org.springframework.boot.logging.LoggingSystem` system property. +The value should be the fully qualified class name of a `LoggingSystem` implementation. +You can also disable Spring Boot's logging configuration entirely by using a value of `none`. + +NOTE: Since logging is initialized *before* the `ApplicationContext` is created, it is not possible to control logging from `@PropertySources` in Spring `@Configuration` files. +The only way to change the logging system or disable it entirely is through System properties. + +Depending on your logging system, the following files are loaded: + +|=== +| Logging System | Customization + +| Logback +| `logback-spring.xml`, `logback-spring.groovy`, `logback.xml`, or `logback.groovy` + +| Log4j2 +| `log4j2-spring.xml` or `log4j2.xml` + +| JDK (Java Util Logging) +| `logging.properties` +|=== + +NOTE: When possible, we recommend that you use the `-spring` variants for your logging configuration (for example, `logback-spring.xml` rather than `logback.xml`). +If you use standard configuration locations, Spring cannot completely control log initialization. + +WARNING: There are known classloading issues with Java Util Logging that cause problems when running from an 'executable jar'. +We recommend that you avoid it when running from an 'executable jar' if at all possible. + +To help with the customization, some other properties are transferred from the Spring `Environment` to System properties. +This allows the properties to be consumed by logging system configuration. For example, setting `logging.file.name` in `application.properties` or `LOGGING_FILE_NAME` as an environment variable will result in the `LOG_FILE` System property being set. +The properties that are transferred are described in the following table: + +|=== +| Spring Environment | System Property | Comments + +| configprop:logging.exception-conversion-word[] +| `LOG_EXCEPTION_CONVERSION_WORD` +| The conversion word used when logging exceptions. + +| configprop:logging.file.name[] +| `LOG_FILE` +| If defined, it is used in the default log configuration. + +| configprop:logging.file.path[] +| `LOG_PATH` +| If defined, it is used in the default log configuration. + +| configprop:logging.pattern.console[] +| `CONSOLE_LOG_PATTERN` +| The log pattern to use on the console (stdout). + +| configprop:logging.pattern.dateformat[] +| `LOG_DATEFORMAT_PATTERN` +| Appender pattern for log date format. + +| configprop:logging.charset.console[] +| `CONSOLE_LOG_CHARSET` +| The charset to use for console logging. + +| configprop:logging.threshold.console[] +| `CONSOLE_LOG_THRESHOLD` +| The log level threshold to use for console logging. + +| configprop:logging.pattern.file[] +| `FILE_LOG_PATTERN` +| The log pattern to use in a file (if `LOG_FILE` is enabled). + +| configprop:logging.charset.file[] +| `FILE_LOG_CHARSET` +| The charset to use for file logging (if `LOG_FILE` is enabled). + +| configprop:logging.threshold.file[] +| `FILE_LOG_THRESHOLD` +| The log level threshold to use for file logging. + +| configprop:logging.pattern.level[] +| `LOG_LEVEL_PATTERN` +| The format to use when rendering the log level (default `%5p`). + +| configprop:logging.structured.format.console[] +| `CONSOLE_LOG_STRUCTURED_FORMAT` +| The structured logging format to use for console logging. + +| configprop:logging.structured.format.file[] +| `FILE_LOG_STRUCTURED_FORMAT` +| The structured logging format to use for file logging. + +| `PID` +| `PID` +| The current process ID (discovered if possible and when not already defined as an OS environment variable). +|=== + +If you use Logback, the following properties are also transferred: + +|=== +| Spring Environment | System Property | Comments + +| configprop:logging.logback.rollingpolicy.file-name-pattern[] +| `LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN` +| Pattern for rolled-over log file names (default `$\{LOG_FILE}.%d\{yyyy-MM-dd}.%i.gz`). + +| configprop:logging.logback.rollingpolicy.clean-history-on-start[] +| `LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START` +| Whether to clean the archive log files on startup. + +| configprop:logging.logback.rollingpolicy.max-file-size[] +| `LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE` +| Maximum log file size. + +| configprop:logging.logback.rollingpolicy.total-size-cap[] +| `LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP` +| Total size of log backups to be kept. + +| configprop:logging.logback.rollingpolicy.max-history[] +| `LOGBACK_ROLLINGPOLICY_MAX_HISTORY` +| Maximum number of archive log files to keep. +|=== + + +All the supported logging systems can consult System properties when parsing their configuration files. +See the default configurations in `spring-boot.jar` for examples: + +* {code-spring-boot}/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml[Logback] +* {code-spring-boot}/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml[Log4j 2] +* {code-spring-boot}/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/java/logging-file.properties[Java Util logging] + +[TIP] +==== +If you want to use a placeholder in a logging property, you should use xref:features/external-config.adoc#features.external-config.files.property-placeholders[Spring Boot's syntax] and not the syntax of the underlying framework. +Notably, if you use Logback, you should use `:` as the delimiter between a property name and its default value and not use `:-`. +==== + +[TIP] +==== +You can add MDC and other ad-hoc content to log lines by overriding only the `LOG_LEVEL_PATTERN` (or `logging.pattern.level` with Logback). +For example, if you use `logging.pattern.level=user:%X\{user} %5p`, then the default log format contains an MDC entry for "user", if it exists, as shown in the following example. + +[source] +---- +2019-08-30 12:30:04.031 user:someone INFO 22174 --- [ nio-8080-exec-0] demo.Controller +Handling authenticated request +---- +==== + + + +[[features.logging.structured]] +== Structured Logging + +Structured logging is a technique where the log output is written in a well-defined, often machine-readable format. +Spring Boot supports structured logging and has support for the following formats out of the box: + +* xref:#features.logging.structured.ecs[Elastic Common Schema (ECS)] +* xref:#features.logging.structured.logstash[Logstash] + +To enable structured logging, set the property configprop:logging.structured.format.console[] (for console output) or configprop:logging.structured.format.file[] (for file output) to the id of the format you want to use. + + + +[[features.logging.structured.ecs]] +=== Elastic Common Schema +https://www.elastic.co/guide/en/ecs/8.11/ecs-reference.html[Elastic Common Schema] is a JSON based logging format. + +To enable the Elastic Common Schema log format, set the appropriate `format` property to `ecs`: + +[configprops,yaml] +---- +logging: + structured: + format: + console: ecs + file: ecs +---- + +A log line looks like this: + +[source,json] +---- +{"@timestamp":"2024-01-01T10:15:00.067462556Z","log.level":"INFO","process.pid":39599,"process.thread.name":"main","service.name":"simple","log.logger":"org.example.Application","message":"No active profile set, falling back to 1 default profile: \"default\"","ecs.version":"8.11"} +---- + +This format also adds every key value pair contained in the MDC to the JSON object. +You can also use the https://www.slf4j.org/manual.html#fluent[SLF4J fluent logging API] to add key value pairs to the logged JSON object with the https://www.slf4j.org/apidocs/org/slf4j/spi/LoggingEventBuilder.html#addKeyValue(java.lang.String,java.lang.Object)[addKeyValue] method. + + + +[[features.logging.structured.logstash]] +=== Logstash JSON format + +The https://github.com/logfellow/logstash-logback-encoder?tab=readme-ov-file#standard-fields[Logstash JSON format] is a JSON based logging format. + +To enable the Logstash JSON log format, set the appropriate `format` property to `logstash`: + +[configprops,yaml] +---- +logging: + structured: + format: + console: logstash + file: logstash +---- + +A log line looks like this: + +[source,json] +---- +{"@timestamp":"2024-01-01T10:15:00.111037681+02:00","@version":"1","message":"No active profile set, falling back to 1 default profile: \"default\"","logger_name":"org.example.Application","thread_name":"main","level":"INFO","level_value":20000} +---- + +This format also adds every key value pair contained in the MDC to the JSON object. +You can also use the https://www.slf4j.org/manual.html#fluent[SLF4J fluent logging API] to add key value pairs to the logged JSON object with the https://www.slf4j.org/apidocs/org/slf4j/spi/LoggingEventBuilder.html#addKeyValue(java.lang.String,java.lang.Object)[addKeyValue] method. + +If you add https://www.slf4j.org/api/org/slf4j/Marker.html[markers], these will show up in a `tags` string array in the JSON. + + + +[[features.logging.structured.custom-format]] +=== Custom Structured Logging formats + +The structured logging support in Spring Boot is extensible, allowing you to define your own custom format. +To do this, implement the `StructuredLoggingFormatter` interface. The generic type argument has to be `ILoggingEvent` when using Logback and `LogEvent` when using Log4j2 (that means your implementation is tied to a specific logging system). +Your implementation is then called with the log event and returns the `String` to be logged, as seen in this example: + +include-code::MyCustomFormat[] + +As you can see in the example, you can return any format, it doesn't have to be JSON. + +To enable your custom format, set the property configprop:logging.structured.format.console[] or configprop:logging.structured.format.file[] to the fully qualified class name of your implementation. + +Your implementation can use some constructor parameters, which are injected automatically. +Please see the JavaDoc of xref:api:java/org/springframework/boot/logging/structured/StructuredLogFormatter.html[`StructuredLogFormatter`] for more details. + + + +[[features.logging.logback-extensions]] +== Logback Extensions + +Spring Boot includes a number of extensions to Logback that can help with advanced configuration. +You can use these extensions in your `logback-spring.xml` configuration file. + +NOTE: Because the standard `logback.xml` configuration file is loaded too early, you cannot use extensions in it. +You need to either use `logback-spring.xml` or define a configprop:logging.config[] property. + +WARNING: The extensions cannot be used with Logback's https://logback.qos.ch/manual/configuration.html#autoScan[configuration scanning]. +If you attempt to do so, making changes to the configuration file results in an error similar to one of the following being logged: + +[source] +---- +ERROR in ch.qos.logback.core.joran.spi.Interpreter@4:71 - no applicable action for [springProperty], current ElementPath is [[configuration][springProperty]] +ERROR in ch.qos.logback.core.joran.spi.Interpreter@4:71 - no applicable action for [springProfile], current ElementPath is [[configuration][springProfile]] +---- + + + +[[features.logging.logback-extensions.profile-specific]] +=== Profile-specific Configuration + +The `` tag lets you optionally include or exclude sections of configuration based on the active Spring profiles. +Profile sections are supported anywhere within the `` element. +Use the `name` attribute to specify which profile accepts the configuration. +The `` tag can contain a profile name (for example `staging`) or a profile expression. +A profile expression allows for more complicated profile logic to be expressed, for example `production & (eu-central | eu-west)`. +Check the {url-spring-framework-docs}/core/beans/environment.html#beans-definition-profiles-java[Spring Framework reference guide] for more details. +The following listing shows three sample profiles: + +[source,xml] +---- + + + + + + + + + + + +---- + + + +[[features.logging.logback-extensions.environment-properties]] +=== Environment Properties + +The `` tag lets you expose properties from the Spring `Environment` for use within Logback. +Doing so can be useful if you want to access values from your `application.properties` file in your Logback configuration. +The tag works in a similar way to Logback's standard `` tag. +However, rather than specifying a direct `value`, you specify the `source` of the property (from the `Environment`). +If you need to store the property somewhere other than in `local` scope, you can use the `scope` attribute. +If you need a fallback value (in case the property is not set in the `Environment`), you can use the `defaultValue` attribute. +The following example shows how to expose properties for use within Logback: + +[source,xml] +---- + + + ${fluentHost} + ... + +---- + +NOTE: The `source` must be specified in kebab case (such as `my.property-name`). +However, properties can be added to the `Environment` by using the relaxed rules. + + + +[[features.logging.log4j2-extensions]] +== Log4j2 Extensions + +Spring Boot includes a number of extensions to Log4j2 that can help with advanced configuration. +You can use these extensions in any `log4j2-spring.xml` configuration file. + +NOTE: Because the standard `log4j2.xml` configuration file is loaded too early, you cannot use extensions in it. +You need to either use `log4j2-spring.xml` or define a configprop:logging.config[] property. + +NOTE: The extensions supersede the https://logging.apache.org/log4j/2.x/log4j-spring-boot/index.html[Spring Boot support] provided by Log4J. +You should make sure not to include the `org.apache.logging.log4j:log4j-spring-boot` module in your build. + + + +[[features.logging.log4j2-extensions.profile-specific]] +=== Profile-specific Configuration + +The `` tag lets you optionally include or exclude sections of configuration based on the active Spring profiles. +Profile sections are supported anywhere within the `` element. +Use the `name` attribute to specify which profile accepts the configuration. +The `` tag can contain a profile name (for example `staging`) or a profile expression. +A profile expression allows for more complicated profile logic to be expressed, for example `production & (eu-central | eu-west)`. +Check the {url-spring-framework-docs}/core/beans/environment.html#beans-definition-profiles-java[Spring Framework reference guide] for more details. +The following listing shows three sample profiles: + +[source,xml] +---- + + + + + + + + + + + +---- + + + +[[features.logging.log4j2-extensions.environment-properties-lookup]] +=== Environment Properties Lookup + +If you want to refer to properties from your Spring `Environment` within your Log4j2 configuration you can use `spring:` prefixed https://logging.apache.org/log4j/2.x/manual/lookups.html[lookups]. +Doing so can be useful if you want to access values from your `application.properties` file in your Log4j2 configuration. + +The following example shows how to set a Log4j2 property named `applicationName` and `applicationGroup` that reads `spring.application.name` and `spring.application.group` from the Spring `Environment`: + +[source,xml] +---- + + ${spring:spring.application.name} + ${spring:spring.application.property} + +---- + +NOTE: The lookup key should be specified in kebab case (such as `my.property-name`). + + + +[[features.logging.log4j2-extensions.environment-property-source]] +=== Log4j2 System Properties + +Log4j2 supports a number of https://logging.apache.org/log4j/2.x/manual/configuration.html#SystemProperties[System Properties] that can be used to configure various items. +For example, the `log4j2.skipJansi` system property can be used to configure if the `ConsoleAppender` will try to use a https://github.com/fusesource/jansi[Jansi] output stream on Windows. + +All system properties that are loaded after the Log4j2 initialization can be obtained from the Spring `Environment`. +For example, you could add `log4j2.skipJansi=false` to your `application.properties` file to have the `ConsoleAppender` use Jansi on Windows. + +NOTE: The Spring `Environment` is only considered when system properties and OS environment variables do not contain the value being loaded. + +WARNING: System properties that are loaded during early Log4j2 initialization cannot reference the Spring `Environment`. +For example, the property Log4j2 uses to allow the default Log4j2 implementation to be chosen is used before the Spring Environment is available. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/profiles.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/profiles.adoc new file mode 100644 index 000000000000..af187b35e11b --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/profiles.adoc @@ -0,0 +1,126 @@ +[[features.profiles]] += Profiles + +Spring Profiles provide a way to segregate parts of your application configuration and make it be available only in certain environments. +Any `@Component`, `@Configuration` or `@ConfigurationProperties` can be marked with `@Profile` to limit when it is loaded, as shown in the following example: + +include-code::ProductionConfiguration[] + +NOTE: If `@ConfigurationProperties` beans are registered through `@EnableConfigurationProperties` instead of automatic scanning, the `@Profile` annotation needs to be specified on the `@Configuration` class that has the `@EnableConfigurationProperties` annotation. +In the case where `@ConfigurationProperties` are scanned, `@Profile` can be specified on the `@ConfigurationProperties` class itself. + +You can use a configprop:spring.profiles.active[] `Environment` property to specify which profiles are active. +You can specify the property in any of the ways described earlier in this chapter. +For example, you could include it in your `application.properties`, as shown in the following example: + +[configprops,yaml] +---- +spring: + profiles: + active: "dev,hsqldb" +---- + +You could also specify it on the command line by using the following switch: `--spring.profiles.active=dev,hsqldb`. + +If no profile is active, a default profile is enabled. +The name of the default profile is `default` and it can be tuned using the configprop:spring.profiles.default[] `Environment` property, as shown in the following example: + +[configprops,yaml] +---- +spring: + profiles: + default: "none" +---- + +`spring.profiles.active` and `spring.profiles.default` can only be used in non-profile-specific documents. +This means they cannot be included in xref:features/external-config.adoc#features.external-config.files.profile-specific[profile specific files] or xref:features/external-config.adoc#features.external-config.files.activation-properties[documents activated] by `spring.config.activate.on-profile`. + +For example, the second document configuration is invalid: + +[configprops,yaml] +---- +# this document is valid +spring: + profiles: + active: "prod" +--- +# this document is invalid +spring: + config: + activate: + on-profile: "prod" + profiles: + active: "metrics" +---- + + + +[[features.profiles.adding-active-profiles]] +== Adding Active Profiles + +The configprop:spring.profiles.active[] property follows the same ordering rules as other properties: The highest `PropertySource` wins. +This means that you can specify active profiles in `application.properties` and then *replace* them by using the command line switch. + +Sometimes, it is useful to have properties that *add* to the active profiles rather than replace them. +The `spring.profiles.include` property can be used to add active profiles on top of those activated by the configprop:spring.profiles.active[] property. +The `SpringApplication` entry point also has a Java API for setting additional profiles. +See the `setAdditionalProfiles()` method in xref:api:java/org/springframework/boot/SpringApplication.html[`SpringApplication`]. + +For example, when an application with the following properties is run, the common and local profiles will be activated even when it runs using the `--spring.profiles.active` switch: + +[configprops,yaml] +---- +spring: + profiles: + include: + - "common" + - "local" +---- + +WARNING: Similar to `spring.profiles.active`, `spring.profiles.include` can only be used in non-profile-specific documents. +This means it cannot be included in xref:features/external-config.adoc#features.external-config.files.profile-specific[profile specific files] or xref:features/external-config.adoc#features.external-config.files.activation-properties[documents activated] by `spring.config.activate.on-profile`. + +Profile groups, which are described in the xref:features/profiles.adoc#features.profiles.groups[next section] can also be used to add active profiles if a given profile is active. + + + +[[features.profiles.groups]] +== Profile Groups + +Occasionally the profiles that you define and use in your application are too fine-grained and become cumbersome to use. +For example, you might have `proddb` and `prodmq` profiles that you use to enable database and messaging features independently. + +To help with this, Spring Boot lets you define profile groups. +A profile group allows you to define a logical name for a related group of profiles. + +For example, we can create a `production` group that consists of our `proddb` and `prodmq` profiles. + +[configprops,yaml] +---- +spring: + profiles: + group: + production: + - "proddb" + - "prodmq" +---- + +Our application can now be started using `--spring.profiles.active=production` to activate the `production`, `proddb` and `prodmq` profiles in one hit. + +WARNING: Similar to `spring.profiles.active` and `spring.profiles.include`, `spring.profiles.group` can only be used in non-profile-specific documents. +This means it cannot be included in xref:features/external-config.adoc#features.external-config.files.profile-specific[profile specific files] or xref:features/external-config.adoc#features.external-config.files.activation-properties[documents activated] by `spring.config.activate.on-profile`. + + +[[features.profiles.programmatically-setting-profiles]] +== Programmatically Setting Profiles + +You can programmatically set active profiles by calling `SpringApplication.setAdditionalProfiles(...)` before your application runs. +It is also possible to activate profiles by using Spring's `ConfigurableEnvironment` interface. + + + +[[features.profiles.profile-specific-configuration-files]] +== Profile-specific Configuration Files + +Profile-specific variants of both `application.properties` (or `application.yaml`) and files referenced through `@ConfigurationProperties` are considered as files and loaded. +See xref:features/external-config.adoc#features.external-config.files.profile-specific[] for details. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/spring-application.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/spring-application.adoc similarity index 76% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/spring-application.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/spring-application.adoc index 8e7eb8f2b6f6..aae5ff78ce79 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/spring-application.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/spring-application.adoc @@ -1,21 +1,22 @@ [[features.spring-application]] -== SpringApplication += SpringApplication + The `SpringApplication` class provides a convenient way to bootstrap a Spring application that is started from a `main()` method. In many situations, you can delegate to the static `SpringApplication.run` method, as shown in the following example: -include::code:MyApplication[] +include-code::MyApplication[] When your application starts, you should see something similar to the following output: -[indent=0,subs="verbatim,attributes"] +[source,subs="verbatim,attributes"] ---- -include::{spring-application-output}[] +include::ROOT:partial$application/spring-application.txt[] ---- By default, `INFO` logging messages are shown, including some relevant startup details, such as the user that launched the application. -If you need a log level other than `INFO`, you can set it, as described in <>. +If you need a log level other than `INFO`, you can set it, as described in xref:features/logging.adoc#features.logging.log-levels[]. The application version is determined using the implementation version from the main application class's package. Startup information logging can be turned off by setting `spring.main.log-startup-info` to `false`. This will also turn off logging of the application's active profiles. @@ -25,41 +26,43 @@ TIP: To add additional logging during startup, you can override `logStartupInfo( [[features.spring-application.startup-failure]] -=== Startup Failure +== Startup Failure + If your application fails to start, registered `FailureAnalyzers` get a chance to provide a dedicated error message and a concrete action to fix the problem. For instance, if you start a web application on port `8080` and that port is already in use, you should see something similar to the following message: -[indent=0] +[source] ---- - *************************** - APPLICATION FAILED TO START - *************************** +*************************** +APPLICATION FAILED TO START +*************************** - Description: +Description: - Embedded servlet container failed to start. Port 8080 was already in use. +Embedded servlet container failed to start. Port 8080 was already in use. - Action: +Action: - Identify and stop the process that is listening on port 8080 or configure this application to listen on another port. +Identify and stop the process that is listening on port 8080 or configure this application to listen on another port. ---- -NOTE: Spring Boot provides numerous `FailureAnalyzer` implementations, and you can <>. +NOTE: Spring Boot provides numerous `FailureAnalyzer` implementations, and you can xref:how-to:application.adoc#howto.application.failure-analyzer[add your own]. If no failure analyzers are able to handle the exception, you can still display the full conditions report to better understand what went wrong. -To do so, you need to <> or <> for `org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener`. +To do so, you need to xref:features/external-config.adoc[enable the `debug` property] or xref:features/logging.adoc#features.logging.log-levels[enable `DEBUG` logging] for `org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener`. For instance, if you are running your application by using `java -jar`, you can enable the `debug` property as follows: -[source,shell,indent=0,subs="verbatim"] +[source,shell] ---- - $ java -jar myproject-0.0.1-SNAPSHOT.jar --debug +$ java -jar myproject-0.0.1-SNAPSHOT.jar --debug ---- [[features.spring-application.lazy-initialization]] -=== Lazy Initialization +== Lazy Initialization + `SpringApplication` allows an application to be initialized lazily. When lazy initialization is enabled, beans are created as they are needed rather than during application startup. As a result, enabling lazy initialization can reduce the time that it takes your application to start. @@ -73,11 +76,11 @@ For these reasons, lazy initialization is not enabled by default and it is recom Lazy initialization can be enabled programmatically using the `lazyInitialization` method on `SpringApplicationBuilder` or the `setLazyInitialization` method on `SpringApplication`. Alternatively, it can be enabled using the configprop:spring.main.lazy-initialization[] property as shown in the following example: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - main: - lazy-initialization: true +spring: + main: + lazy-initialization: true ---- TIP: If you want to disable lazy initialization for certain beans while using lazy initialization for the rest of the application, you can explicitly set their lazy attribute to false using the `@Lazy(false)` annotation. @@ -85,7 +88,8 @@ TIP: If you want to disable lazy initialization for certain beans while using la [[features.spring-application.banner]] -=== Customizing the Banner +== Customizing the Banner + The banner that is printed on start up can be changed by adding a `banner.txt` file to your classpath or by setting the configprop:spring.banner.location[] property to the location of such a file. If the file has an encoding other than UTF-8, you can set `spring.banner.charset`. @@ -105,15 +109,15 @@ Inside your `banner.txt` file, you can use any key available in the `Environment | `${spring-boot.version}` | The Spring Boot version that you are using. - For example `{spring-boot-version}`. + For example `{version-spring-boot}`. | `${spring-boot.formatted-version}` | The Spring Boot version that you are using, formatted for display (surrounded with brackets and prefixed with `v`). - For example `(v{spring-boot-version})`. + For example `(v{version-spring-boot})`. | `${Ansi.NAME}` (or `${AnsiColor.NAME}`, `${AnsiBackground.NAME}`, `${AnsiStyle.NAME}`) | Where `NAME` is the name of an ANSI escape code. - See {spring-boot-module-code}/ansi/AnsiPropertySource.java[`AnsiPropertySource`] for details. + See xref:api:java/org/springframework/boot/ansi/AnsiPropertySource.html[`AnsiPropertySource`] for details. | `${application.title}` | The title of your application, as declared in `MANIFEST.MF`. @@ -133,45 +137,48 @@ The `application.title`, `application.version`, and `application.formatted-versi The values will not be resolved if you are running an unpacked jar and starting it with `java -cp ` or running your application as a native image. -To use the `application.*` properties, launch your application as a packed jar using `java -jar` or as an unpacked jar using `java org.springframework.boot.loader.JarLauncher`. +To use the `application.*` properties, launch your application as a packed jar using `java -jar` or as an unpacked jar using `java org.springframework.boot.loader.launch.JarLauncher`. This will initialize the `application.*` banner properties before building the classpath and launching your app. ==== [[features.spring-application.customizing-spring-application]] -=== Customizing SpringApplication +== Customizing SpringApplication + If the `SpringApplication` defaults are not to your taste, you can instead create a local instance and customize it. For example, to turn off the banner, you could write: -include::code:MyApplication[] +include-code::MyApplication[] NOTE: The constructor arguments passed to `SpringApplication` are configuration sources for Spring beans. In most cases, these are references to `@Configuration` classes, but they could also be direct references `@Component` classes. It is also possible to configure the `SpringApplication` by using an `application.properties` file. -See _<>_ for details. +See xref:features/external-config.adoc[] for details. -For a complete list of the configuration options, see the {spring-boot-module-api}/SpringApplication.html[`SpringApplication` Javadoc]. +For a complete list of the configuration options, see the xref:api:java/org/springframework/boot/SpringApplication.html[`SpringApplication`] API documentation. [[features.spring-application.fluent-builder-api]] -=== Fluent Builder API -If you need to build an `ApplicationContext` hierarchy (multiple contexts with a parent/child relationship) or if you prefer using a "`fluent`" builder API, you can use the `SpringApplicationBuilder`. +== Fluent Builder API + +If you need to build an `ApplicationContext` hierarchy (multiple contexts with a parent/child relationship) or if you prefer using a fluent builder API, you can use the `SpringApplicationBuilder`. The `SpringApplicationBuilder` lets you chain together multiple method calls and includes `parent` and `child` methods that let you create a hierarchy, as shown in the following example: -include::code:MyApplication[tag=*] +include-code::MyApplication[tag=*] NOTE: There are some restrictions when creating an `ApplicationContext` hierarchy. For example, Web components *must* be contained within the child context, and the same `Environment` is used for both parent and child contexts. -See the {spring-boot-module-api}/builder/SpringApplicationBuilder.html[`SpringApplicationBuilder` Javadoc] for full details. +See the xref:api:java/org/springframework/boot/builder/SpringApplicationBuilder.html[`SpringApplicationBuilder`] API documentation for full details. [[features.spring-application.application-availability]] -=== Application Availability +== Application Availability + When deployed on platforms, applications can provide information about their availability to the platform using infrastructure such as https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/[Kubernetes Probes]. Spring Boot includes out-of-the box support for the commonly used "`liveness`" and "`readiness`" availability states. If you are using Spring Boot's "`actuator`" support then these states are exposed as health endpoint groups. @@ -181,52 +188,56 @@ In addition, you can also obtain availability states by injecting the `Applicati [[features.spring-application.application-availability.liveness]] -==== Liveness State +=== Liveness State + The "`Liveness`" state of an application tells whether its internal state allows it to work correctly, or recover by itself if it is currently failing. A broken "`Liveness`" state means that the application is in a state that it cannot recover from, and the infrastructure should restart the application. -NOTE: In general, the "Liveness" state should not be based on external checks, such as <>. +NOTE: In general, the "Liveness" state should not be based on external checks, such as xref:actuator/endpoints.adoc#actuator.endpoints.health[health checks]. If it did, a failing external system (a database, a Web API, an external cache) would trigger massive restarts and cascading failures across the platform. The internal state of Spring Boot applications is mostly represented by the Spring `ApplicationContext`. If the application context has started successfully, Spring Boot assumes that the application is in a valid state. -An application is considered live as soon as the context has been refreshed, see <>. +An application is considered live as soon as the context has been refreshed, see xref:features/spring-application.adoc#features.spring-application.application-events-and-listeners[Spring Boot application lifecycle and related Application Events]. [[features.spring-application.application-availability.readiness]] -==== Readiness State +=== Readiness State + The "`Readiness`" state of an application tells whether the application is ready to handle traffic. A failing "`Readiness`" state tells the platform that it should not route traffic to the application for now. This typically happens during startup, while `CommandLineRunner` and `ApplicationRunner` components are being processed, or at any time if the application decides that it is too busy for additional traffic. -An application is considered ready as soon as application and command-line runners have been called, see <>. +An application is considered ready as soon as application and command-line runners have been called, see xref:features/spring-application.adoc#features.spring-application.application-events-and-listeners[Spring Boot application lifecycle and related Application Events]. TIP: Tasks expected to run during startup should be executed by `CommandLineRunner` and `ApplicationRunner` components instead of using Spring component lifecycle callbacks such as `@PostConstruct`. [[features.spring-application.application-availability.managing]] -==== Managing the Application Availability State +=== Managing the Application Availability State + Application components can retrieve the current availability state at any time, by injecting the `ApplicationAvailability` interface and calling methods on it. More often, applications will want to listen to state updates or update the state of the application. For example, we can export the "Readiness" state of the application to a file so that a Kubernetes "exec Probe" can look at this file: -include::code:MyReadinessStateExporter[] +include-code::MyReadinessStateExporter[] We can also update the state of the application, when the application breaks and cannot recover: -include::code:MyLocalCacheVerifier[] +include-code::MyLocalCacheVerifier[] -Spring Boot provides <>. -You can get more guidance about <>. +Spring Boot provides xref:actuator/endpoints.adoc#actuator.endpoints.kubernetes-probes[Kubernetes HTTP probes for "Liveness" and "Readiness" with Actuator Health Endpoints]. +You can get more guidance about xref:how-to:deployment/cloud.adoc#howto.deployment.cloud.kubernetes[deploying Spring Boot applications on Kubernetes in the dedicated section]. [[features.spring-application.application-events-and-listeners]] -=== Application Events and Listeners -In addition to the usual Spring Framework events, such as {spring-framework-api}/context/event/ContextRefreshedEvent.html[`ContextRefreshedEvent`], a `SpringApplication` sends some additional application events. +== Application Events and Listeners + +In addition to the usual Spring Framework events, such as {url-spring-framework-javadoc}/org/springframework/context/event/ContextRefreshedEvent.html[`ContextRefreshedEvent`], a `SpringApplication` sends some additional application events. [NOTE] ==== @@ -235,9 +246,9 @@ You can register them with the `SpringApplication.addListeners(...)` method or t If you want those listeners to be registered automatically, regardless of the way the application is created, you can add a `META-INF/spring.factories` file to your project and reference your listener(s) by using the `org.springframework.context.ApplicationListener` key, as shown in the following example: -[indent=0] +[source] ---- - org.springframework.context.ApplicationListener=com.example.project.MyListener +org.springframework.context.ApplicationListener=com.example.project.MyListener ---- ==== @@ -250,7 +261,7 @@ Application events are sent in the following order, as your application runs: . An `ApplicationPreparedEvent` is sent just before the refresh is started but after bean definitions have been loaded. . An `ApplicationStartedEvent` is sent after the context has been refreshed but before any application and command-line runners have been called. . An `AvailabilityChangeEvent` is sent right after with `LivenessState.CORRECT` to indicate that the application is considered as live. -. An `ApplicationReadyEvent` is sent after any <> have been called. +. An `ApplicationReadyEvent` is sent after any xref:features/spring-application.adoc#features.spring-application.command-line-runner[application and command-line runners] have been called. . An `AvailabilityChangeEvent` is sent right after with `ReadinessState.ACCEPTING_TRAFFIC` to indicate that the application is ready to service requests. . An `ApplicationFailedEvent` is sent if there is an exception on startup. @@ -265,7 +276,7 @@ TIP: You often need not use application events, but it can be handy to know that Internally, Spring Boot uses events to handle a variety of tasks. NOTE: Event listeners should not run potentially lengthy tasks as they execute in the same thread by default. -Consider using <> instead. +Consider using xref:features/spring-application.adoc#features.spring-application.command-line-runner[application and command-line runners] instead. Application events are sent by using Spring Framework's event publishing mechanism. Part of this mechanism ensures that an event published to the listeners in a child context is also published to the listeners in any ancestor contexts. @@ -277,7 +288,8 @@ The context can be injected by implementing `ApplicationContextAware` or, if the [[features.spring-application.web-environment]] -=== Web Environment +== Web Environment + A `SpringApplication` attempts to create the right type of `ApplicationContext` on your behalf. The algorithm used to determine a `WebApplicationType` is the following: @@ -295,11 +307,12 @@ TIP: It is often desirable to call `setWebApplicationType(WebApplicationType.NON [[features.spring-application.application-arguments]] -=== Accessing Application Arguments +== Accessing Application Arguments + If you need to access the application arguments that were passed to `SpringApplication.run(...)`, you can inject a `org.springframework.boot.ApplicationArguments` bean. The `ApplicationArguments` interface provides access to both the raw `String[]` arguments as well as parsed `option` and `non-option` arguments, as shown in the following example: -include::code:MyBean[] +include-code::MyBean[] TIP: Spring Boot also registers a `CommandLinePropertySource` with the Spring `Environment`. This lets you also inject single application arguments by using the `@Value` annotation. @@ -307,7 +320,8 @@ This lets you also inject single application arguments by using the `@Value` ann [[features.spring-application.command-line-runner]] -=== Using the ApplicationRunner or CommandLineRunner +== Using the ApplicationRunner or CommandLineRunner + If you need to run some specific code once the `SpringApplication` has started, you can implement the `ApplicationRunner` or `CommandLineRunner` interfaces. Both interfaces work in the same way and offer a single `run` method, which is called just before `SpringApplication.run(...)` completes. @@ -317,21 +331,22 @@ NOTE: This contract is well suited for tasks that should run after application s The `CommandLineRunner` interfaces provides access to application arguments as a string array, whereas the `ApplicationRunner` uses the `ApplicationArguments` interface discussed earlier. The following example shows a `CommandLineRunner` with a `run` method: -include::code:MyCommandLineRunner[] +include-code::MyCommandLineRunner[] If several `CommandLineRunner` or `ApplicationRunner` beans are defined that must be called in a specific order, you can additionally implement the `org.springframework.core.Ordered` interface or use the `org.springframework.core.annotation.Order` annotation. [[features.spring-application.application-exit]] -=== Application Exit +== Application Exit + Each `SpringApplication` registers a shutdown hook with the JVM to ensure that the `ApplicationContext` closes gracefully on exit. All the standard Spring lifecycle callbacks (such as the `DisposableBean` interface or the `@PreDestroy` annotation) can be used. In addition, beans may implement the `org.springframework.boot.ExitCodeGenerator` interface if they wish to return a specific exit code when `SpringApplication.exit()` is called. This exit code can then be passed to `System.exit()` to return it as a status code, as shown in the following example: -include::code:MyApplication[] +include-code::MyApplication[] Also, the `ExitCodeGenerator` interface may be implemented by exceptions. When such an exception is encountered, Spring Boot returns the exit code provided by the implemented `getExitCode()` method. @@ -342,9 +357,10 @@ To control the order in which the generators are called, additionally implement [[features.spring-application.admin]] -=== Admin Features +== Admin Features + It is possible to enable admin-related features for the application by specifying the configprop:spring.application.admin.enabled[] property. -This exposes the {spring-boot-module-code}/admin/SpringApplicationAdminMXBean.java[`SpringApplicationAdminMXBean`] on the platform `MBeanServer`. +This exposes the xref:api:java/org/springframework/boot/admin/SpringApplicationAdminMXBean.html[`SpringApplicationAdminMXBean`] on the platform `MBeanServer`. You could use this feature to administer your Spring Boot application remotely. This feature could also be useful for any service wrapper implementation. @@ -353,27 +369,46 @@ TIP: If you want to know on which HTTP port the application is running, get the [[features.spring-application.startup-tracking]] -=== Application Startup tracking +== Application Startup tracking + During the application startup, the `SpringApplication` and the `ApplicationContext` perform many tasks related to the application lifecycle, the beans lifecycle or even processing application events. -With {spring-framework-api}/core/metrics/ApplicationStartup.html[`ApplicationStartup`], Spring Framework {spring-framework-docs}/core/beans/context-introduction.html#context-functionality-startup[allows you to track the application startup sequence with `StartupStep` objects]. +With {url-spring-framework-javadoc}/org/springframework/core/metrics/ApplicationStartup.html[`ApplicationStartup`], Spring Framework {url-spring-framework-docs}/core/beans/context-introduction.html#context-functionality-startup[allows you to track the application startup sequence with `StartupStep` objects]. This data can be collected for profiling purposes, or just to have a better understanding of an application startup process. You can choose an `ApplicationStartup` implementation when setting up the `SpringApplication` instance. For example, to use the `BufferingApplicationStartup`, you could write: -include::code:MyApplication[] +include-code::MyApplication[] The first available implementation, `FlightRecorderApplicationStartup` is provided by Spring Framework. It adds Spring-specific startup events to a Java Flight Recorder session and is meant for profiling applications and correlating their Spring context lifecycle with JVM events (such as allocations, GCs, class loading...). Once configured, you can record data by running the application with the Flight Recorder enabled: -[source,shell,indent=0,subs="verbatim"] +[source,shell] ---- - $ java -XX:StartFlightRecording:filename=recording.jfr,duration=10s -jar demo.jar +$ java -XX:StartFlightRecording:filename=recording.jfr,duration=10s -jar demo.jar ---- Spring Boot ships with the `BufferingApplicationStartup` variant; this implementation is meant for buffering the startup steps and draining them into an external metrics system. Applications can ask for the bean of type `BufferingApplicationStartup` in any component. -Spring Boot can also be configured to expose a {spring-boot-actuator-restapi-docs}/#startup[`startup` endpoint] that provides this information as a JSON document. +Spring Boot can also be configured to expose a xref:api:rest/actuator/startup.adoc[`startup` endpoint] that provides this information as a JSON document. + + + +[[features.spring-application.virtual-threads]] +== Virtual threads + +If you're running on Java 21 or up, you can enable virtual threads by setting the property configprop:spring.threads.virtual.enabled[] to `true`. + +Before turning on this option for your application, you should consider https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html[reading the official Java virtual threads documentation]. +In some cases, applications can experience lower throughput because of "Pinned Virtual Threads"; this page also explains how to detect such cases with JDK Flight Recorder or the `jcmd` CLI. + +WARNING: One side effect of virtual threads is that they are daemon threads. +A JVM will exit if all of its threads are daemon threads. +This behavior can be a problem when you rely on `@Scheduled` beans, for example, to keep your application alive. +If you use virtual threads, the scheduler thread is a virtual thread and therefore a daemon thread and won't keep the JVM alive. +This not only affects scheduling and can be the case with other technologies too. +To keep the JVM running in all cases, it is recommended to set the property configprop:spring.main.keep-alive[] to `true`. +This ensures that the JVM is kept alive, even if all threads are virtual threads. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/ssl.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/ssl.adoc new file mode 100644 index 000000000000..209a611307e4 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/ssl.adoc @@ -0,0 +1,170 @@ +[[features.ssl]] += SSL + +Spring Boot provides the ability to configure SSL trust material that can be applied to several types of connections in order to support secure communications. +Configuration properties with the prefix `spring.ssl.bundle` can be used to specify named sets of trust material and associated information. + + + +[[features.ssl.jks]] +== Configuring SSL With Java KeyStore Files + +Configuration properties with the prefix `spring.ssl.bundle.jks` can be used to configure bundles of trust material created with the Java `keytool` utility and stored in Java KeyStore files in the JKS or PKCS12 format. +Each bundle has a user-provided name that can be used to reference the bundle. + +When used to secure an embedded web server, a `keystore` is typically configured with a Java KeyStore containing a certificate and private key as shown in this example: + +[configprops,yaml] +---- + spring: + ssl: + bundle: + jks: + mybundle: + key: + alias: "application" + keystore: + location: "classpath:application.p12" + password: "secret" + type: "PKCS12" +---- + +When used to secure a client-side connection, a `truststore` is typically configured with a Java KeyStore containing the server certificate as shown in this example: + +[configprops,yaml] +---- + spring: + ssl: + bundle: + jks: + mybundle: + truststore: + location: "classpath:server.p12" + password: "secret" +---- + +See xref:api:java/org/springframework/boot/autoconfigure/ssl/JksSslBundleProperties.html[`JksSslBundleProperties`] for the full set of supported properties. + + + +[[features.ssl.pem]] +== Configuring SSL With PEM-encoded Certificates + +Configuration properties with the prefix `spring.ssl.bundle.pem` can be used to configure bundles of trust material in the form of PEM-encoded text. +Each bundle has a user-provided name that can be used to reference the bundle. + +When used to secure an embedded web server, a `keystore` is typically configured with a certificate and private key as shown in this example: + +[configprops,yaml] +---- + spring: + ssl: + bundle: + pem: + mybundle: + keystore: + certificate: "classpath:application.crt" + private-key: "classpath:application.key" +---- + +When used to secure a client-side connection, a `truststore` is typically configured with the server certificate as shown in this example: + +[configprops,yaml] +---- + spring: + ssl: + bundle: + pem: + mybundle: + truststore: + certificate: "classpath:server.crt" +---- + +[TIP] +==== +PEM content can be used directly for both the `certificate` and `private-key` properties. +If the property values contain `BEGIN` and `END` markers then they will be treated as PEM content rather than a resource location. + +The following example shows how a truststore certificate can be defined: + +[configprops,yaml] +---- + spring: + ssl: + bundle: + pem: + mybundle: + truststore: + certificate: | + -----BEGIN CERTIFICATE----- + MIID1zCCAr+gAwIBAgIUNM5QQv8IzVQsgSmmdPQNaqyzWs4wDQYJKoZIhvcNAQEL + BQAwezELMAkGA1UEBhMCWFgxEjAQBgNVBAgMCVN0YXRlTmFtZTERMA8GA1UEBwwI + ... + V0IJjcmYjEZbTvpjFKznvaFiOUv+8L7jHQ1/Yf+9c3C8gSjdUfv88m17pqYXd+Ds + HEmfmNNjht130UyjNCITmLVXyy5p35vWmdf95U3uEbJSnNVtXH8qRmN9oK9mUpDb + ngX6JBJI7fw7tXoqWSLHNiBODM88fUlQSho8 + -----END CERTIFICATE----- +---- +==== + +See xref:api:java/org/springframework/boot/autoconfigure/ssl/PemSslBundleProperties.html[`PemSslBundleProperties`] for the full set of supported properties. + + + +[[features.ssl.applying]] +== Applying SSL Bundles + +Once configured using properties, SSL bundles can be referred to by name in configuration properties for various types of connections that are auto-configured by Spring Boot. +See the sections on xref:how-to:webserver.adoc#howto.webserver.configure-ssl[embedded web servers], xref:data/index.adoc[data technologies], and xref:io/rest-client.adoc[REST clients] for further information. + + + +[[features.ssl.bundles]] +== Using SSL Bundles + +Spring Boot auto-configures a bean of type `SslBundles` that provides access to each of the named bundles configured using the `spring.ssl.bundle` properties. + +An `SslBundle` can be retrieved from the auto-configured `SslBundles` bean and used to create objects that are used to configure SSL connectivity in client libraries. +The `SslBundle` provides a layered approach of obtaining these SSL objects: + +- `getStores()` provides access to the key store and trust store `java.security.KeyStore` instances as well as any required key store password. +- `getManagers()` provides access to the `java.net.ssl.KeyManagerFactory` and `java.net.ssl.TrustManagerFactory` instances as well as the `java.net.ssl.KeyManager` and `java.net.ssl.TrustManager` arrays that they create. +- `createSslContext()` provides a convenient way to obtain a new `java.net.ssl.SSLContext` instance. + +In addition, the `SslBundle` provides details about the key being used, the protocol to use and any option that should be applied to the SSL engine. + +The following example shows retrieving an `SslBundle` and using it to create an `SSLContext`: + +include-code::MyComponent[] + + + +[[features.ssl.reloading]] +== Reloading SSL bundles + +SSL bundles can be reloaded when the key material changes. +The component consuming the bundle has to be compatible with reloadable SSL bundles. +Currently the following components are compatible: + +* Tomcat web server +* Netty web server + +To enable reloading, you need to opt-in via a configuration property as shown in this example: + +[configprops,yaml] +---- + spring: + ssl: + bundle: + pem: + mybundle: + reload-on-update: true + keystore: + certificate: "file:/some/directory/application.crt" + private-key: "file:/some/directory/application.key" +---- + +A file watcher is then watching the files and if they change, the SSL bundle will be reloaded. +This in turn triggers a reload in the consuming component, e.g. Tomcat rotates the certificates in the SSL enabled connectors. + +You can configure the quiet period (to make sure that there are no more changes) of the file watcher with the configprop:spring.ssl.bundle.watch.file.quiet-period[] property. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/task-execution-and-scheduling.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/task-execution-and-scheduling.adoc new file mode 100644 index 000000000000..1acc79847694 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/task-execution-and-scheduling.adoc @@ -0,0 +1,59 @@ +[[features.task-execution-and-scheduling]] += Task Execution and Scheduling + +In the absence of an `Executor` bean in the context, Spring Boot auto-configures an `AsyncTaskExecutor`. +When virtual threads are enabled (using Java 21+ and configprop:spring.threads.virtual.enabled[] set to `true`) this will be a `SimpleAsyncTaskExecutor` that uses virtual threads. +Otherwise, it will be a `ThreadPoolTaskExecutor` with sensible defaults. +In either case, the auto-configured executor will be automatically used for: + +- asynchronous task execution (`@EnableAsync`) +- Spring for GraphQL's asynchronous handling of `Callable` return values from controller methods +- Spring MVC's asynchronous request processing +- Spring WebFlux's blocking execution support + +[TIP] +==== +If you have defined a custom `Executor` in the context, both regular task execution (that is `@EnableAsync`) and Spring for GraphQL will use it. +However, the Spring MVC and Spring WebFlux support will only use it if it is an `AsyncTaskExecutor` implementation (named `applicationTaskExecutor`). +Depending on your target arrangement, you could change your `Executor` into an `AsyncTaskExecutor` or define both an `AsyncTaskExecutor` and an `AsyncConfigurer` wrapping your custom `Executor`. + +The auto-configured `ThreadPoolTaskExecutorBuilder` allows you to easily create instances that reproduce what the auto-configuration does by default. +==== + +When a `ThreadPoolTaskExecutor` is auto-configured, the thread pool uses 8 core threads that can grow and shrink according to the load. +Those default settings can be fine-tuned using the `spring.task.execution` namespace, as shown in the following example: + +[configprops,yaml] +---- +spring: + task: + execution: + pool: + max-size: 16 + queue-capacity: 100 + keep-alive: "10s" +---- + +This changes the thread pool to use a bounded queue so that when the queue is full (100 tasks), the thread pool increases to maximum 16 threads. +Shrinking of the pool is more aggressive as threads are reclaimed when they are idle for 10 seconds (rather than 60 seconds by default). + +A scheduler can also be auto-configured if it needs to be associated with scheduled task execution (using `@EnableScheduling` for instance). + +If virtual threads are enabled (using Java 21+ and configprop:spring.threads.virtual.enabled[] set to `true`) this will be a `SimpleAsyncTaskScheduler` that uses virtual threads. +This `SimpleAsyncTaskScheduler` will ignore any pooling related properties. + +If virtual threads are not enabled, it will be a `ThreadPoolTaskScheduler` with sensible defaults. +The `ThreadPoolTaskScheduler` uses one thread by default and its settings can be fine-tuned using the `spring.task.scheduling` namespace, as shown in the following example: + +[configprops,yaml] +---- +spring: + task: + scheduling: + thread-name-prefix: "scheduling-" + pool: + size: 2 +---- + +A `ThreadPoolTaskExecutorBuilder` bean, a `SimpleAsyncTaskExecutorBuilder` bean, a `ThreadPoolTaskSchedulerBuilder` bean and a `SimpleAsyncTaskSchedulerBuilder` are made available in the context if a custom executor or scheduler needs to be created. +The `SimpleAsyncTaskExecutorBuilder` and `SimpleAsyncTaskSchedulerBuilder` beans are auto-configured to use virtual threads if they are enabled (using Java 21+ and configprop:spring.threads.virtual.enabled[] set to `true`). diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/index.adoc new file mode 100644 index 000000000000..6f23912859c7 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/index.adoc @@ -0,0 +1,3 @@ += Reference + +This section provides information on using the features and capabilities of Spring Boot. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/caching.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/caching.adoc similarity index 77% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/caching.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/caching.adoc index fcbf3fc7bc1f..984c19e925e2 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/caching.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/caching.adoc @@ -1,15 +1,16 @@ [[io.caching]] -== Caching += Caching + The Spring Framework provides support for transparently adding caching to an application. At its core, the abstraction applies caching to methods, thus reducing the number of executions based on the information available in the cache. The caching logic is applied transparently, without any interference to the invoker. Spring Boot auto-configures the cache infrastructure as long as caching support is enabled by using the `@EnableCaching` annotation. -NOTE: Check the {spring-framework-docs}/integration/cache.html[relevant section] of the Spring Framework reference for more details. +NOTE: Check the {url-spring-framework-docs}/integration/cache.html[relevant section] of the Spring Framework reference for more details. In a nutshell, to add caching to an operation of your service add the relevant annotation to its method, as shown in the following example: -include::code:MyMathService[] +include-code::MyMathService[] This example demonstrates the use of caching on a potentially costly operation. Before invoking `computePiDecimal`, the abstraction looks for an entry in the `piDecimals` cache that matches the `i` argument. @@ -19,46 +20,47 @@ Otherwise, the method is invoked, and the cache is updated before returning the CAUTION: You can also use the standard JSR-107 (JCache) annotations (such as `@CacheResult`) transparently. However, we strongly advise you to not mix and match the Spring Cache and JCache annotations. -If you do not add any specific cache library, Spring Boot auto-configures a <> that uses concurrent maps in memory. +If you do not add any specific cache library, Spring Boot auto-configures a xref:io/caching.adoc#io.caching.provider.simple[simple provider] that uses concurrent maps in memory. When a cache is required (such as `piDecimals` in the preceding example), this provider creates it for you. The simple provider is not really recommended for production usage, but it is great for getting started and making sure that you understand the features. When you have made up your mind about the cache provider to use, please make sure to read its documentation to figure out how to configure the caches that your application uses. Nearly all providers require you to explicitly configure every cache that you use in the application. Some offer a way to customize the default caches defined by the configprop:spring.cache.cache-names[] property. -TIP: It is also possible to transparently {spring-framework-docs}/integration/cache/annotations.html#cache-annotations-put[update] or {spring-framework-docs}/integration/cache/annotations.html#cache-annotations-evict[evict] data from the cache. +TIP: It is also possible to transparently {url-spring-framework-docs}/integration/cache/annotations.html#cache-annotations-put[update] or {url-spring-framework-docs}/integration/cache/annotations.html#cache-annotations-evict[evict] data from the cache. [[io.caching.provider]] -=== Supported Cache Providers +== Supported Cache Providers + The cache abstraction does not provide an actual store and relies on abstraction materialized by the `org.springframework.cache.Cache` and `org.springframework.cache.CacheManager` interfaces. -If you have not defined a bean of type `CacheManager` or a `CacheResolver` named `cacheResolver` (see {spring-framework-api}/cache/annotation/CachingConfigurer.html[`CachingConfigurer`]), Spring Boot tries to detect the following providers (in the indicated order): +If you have not defined a bean of type `CacheManager` or a `CacheResolver` named `cacheResolver` (see {url-spring-framework-javadoc}/org/springframework/cache/annotation/CachingConfigurer.html[`CachingConfigurer`]), Spring Boot tries to detect the following providers (in the indicated order): -. <> -. <> (EhCache 3, Hazelcast, Infinispan, and others) -. <> -. <> -. <> -. <> -. <> -. <> -. <> +. xref:io/caching.adoc#io.caching.provider.generic[] +. xref:io/caching.adoc#io.caching.provider.jcache[] (EhCache 3, Hazelcast, Infinispan, and others) +. xref:io/caching.adoc#io.caching.provider.hazelcast[] +. xref:io/caching.adoc#io.caching.provider.infinispan[] +. xref:io/caching.adoc#io.caching.provider.couchbase[] +. xref:io/caching.adoc#io.caching.provider.redis[] +. xref:io/caching.adoc#io.caching.provider.caffeine[] +. xref:io/caching.adoc#io.caching.provider.cache2k[] +. xref:io/caching.adoc#io.caching.provider.simple[] -Additionally, {spring-boot-for-apache-geode}[Spring Boot for Apache Geode] provides {spring-boot-for-apache-geode-docs}#geode-caching-provider[auto-configuration for using Apache Geode as a cache provider]. +Additionally, {url-spring-boot-for-apache-geode-site}[Spring Boot for Apache Geode] provides {url-spring-boot-for-apache-geode-docs}#geode-caching-provider[auto-configuration for using Apache Geode as a cache provider]. TIP: If the `CacheManager` is auto-configured by Spring Boot, it is possible to _force_ a particular cache provider by setting the configprop:spring.cache.type[] property. -Use this property if you need to <> in certain environments (such as tests). +Use this property if you need to xref:io/caching.adoc#io.caching.provider.none[use no-op caches] in certain environments (such as tests). -TIP: Use the `spring-boot-starter-cache` "`Starter`" to quickly add basic caching dependencies. +TIP: Use the `spring-boot-starter-cache` starter to quickly add basic caching dependencies. The starter brings in `spring-context-support`. If you add dependencies manually, you must include `spring-context-support` in order to use the JCache or Caffeine support. If the `CacheManager` is auto-configured by Spring Boot, you can further tune its configuration before it is fully initialized by exposing a bean that implements the `CacheManagerCustomizer` interface. The following example sets a flag to say that `null` values should not be passed down to the underlying map: -include::code:MyCacheManagerConfiguration[] +include-code::MyCacheManagerConfiguration[] NOTE: In the preceding example, an auto-configured `ConcurrentMapCacheManager` is expected. If that is not the case (either you provided your own config or a different cache provider was auto-configured), the customizer is not invoked at all. @@ -67,22 +69,24 @@ You can have as many customizers as you want, and you can also order them by usi [[io.caching.provider.generic]] -==== Generic +=== Generic + Generic caching is used if the context defines _at least_ one `org.springframework.cache.Cache` bean. A `CacheManager` wrapping all beans of that type is created. [[io.caching.provider.jcache]] -==== JCache (JSR-107) -https://jcp.org/en/jsr/detail?id=107[JCache] is bootstrapped through the presence of a `javax.cache.spi.CachingProvider` on the classpath (that is, a JSR-107 compliant caching library exists on the classpath), and the `JCacheCacheManager` is provided by the `spring-boot-starter-cache` "`Starter`". +=== JCache (JSR-107) + +https://jcp.org/en/jsr/detail?id=107[JCache] is bootstrapped through the presence of a `javax.cache.spi.CachingProvider` on the classpath (that is, a JSR-107 compliant caching library exists on the classpath), and the `JCacheCacheManager` is provided by the `spring-boot-starter-cache` starter. Various compliant libraries are available, and Spring Boot provides dependency management for Ehcache 3, Hazelcast, and Infinispan. Any other compliant library can be added as well. It might happen that more than one provider is present, in which case the provider must be explicitly specified. Even if the JSR-107 standard does not enforce a standardized way to define the location of the configuration file, Spring Boot does its best to accommodate setting a cache with implementation details, as shown in the following example: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- # Only necessary if more than one provider is present spring: @@ -94,7 +98,7 @@ Even if the JSR-107 standard does not enforce a standardized way to define the l NOTE: When a cache library offers both a native implementation and JSR-107 support, Spring Boot prefers the JSR-107 support, so that the same features are available if you switch to a different JSR-107 implementation. -TIP: Spring Boot has <>. +TIP: Spring Boot has xref:io/hazelcast.adoc[general support for Hazelcast]. If a single `HazelcastInstance` is available, it is automatically reused for the `CacheManager` as well, unless the configprop:spring.cache.jcache.config[] property is specified. There are two ways to customize the underlying `javax.cache.cacheManager`: @@ -109,28 +113,30 @@ No further customization is applied to it. [[io.caching.provider.hazelcast]] -==== Hazelcast +=== Hazelcast -Spring Boot has <>. +Spring Boot has xref:io/hazelcast.adoc[general support for Hazelcast]. If a `HazelcastInstance` has been auto-configured and `com.hazelcast:hazelcast-spring` is on the classpath, it is automatically wrapped in a `CacheManager`. NOTE: Hazelcast can be used as a JCache compliant cache or as a Spring `CacheManager` compliant cache. When setting configprop:spring.cache.type[] to `hazelcast`, Spring Boot will use the `CacheManager` based implementation. If you want to use Hazelcast as a JCache compliant cache, set configprop:spring.cache.type[] to `jcache`. -If you have multiple JCache compliant cache providers and want to force the use of Hazelcast, you have to <>. +If you have multiple JCache compliant cache providers and want to force the use of Hazelcast, you have to xref:io/caching.adoc#io.caching.provider.jcache[explicitly set the JCache provider]. + + [[io.caching.provider.infinispan]] -==== Infinispan +=== Infinispan https://infinispan.org/[Infinispan] has no default configuration file location, so it must be specified explicitly. Otherwise, the default bootstrap is used. -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - cache: - infinispan: - config: "infinispan.xml" +spring: + cache: + infinispan: + config: "infinispan.xml" ---- Caches can be created on startup by setting the configprop:spring.cache.cache-names[] property. @@ -143,40 +149,42 @@ For example, `infinispan-core-jakarta` and `infinispan-commons-jakarta` must be [[io.caching.provider.couchbase]] -==== Couchbase -If Spring Data Couchbase is available and Couchbase is <>, a `CouchbaseCacheManager` is auto-configured. +=== Couchbase + +If Spring Data Couchbase is available and Couchbase is xref:data/nosql.adoc#data.nosql.couchbase[configured], a `CouchbaseCacheManager` is auto-configured. It is possible to create additional caches on startup by setting the configprop:spring.cache.cache-names[] property and cache defaults can be configured by using `spring.cache.couchbase.*` properties. For instance, the following configuration creates `cache1` and `cache2` caches with an entry _expiration_ of 10 minutes: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - cache: - cache-names: "cache1,cache2" - couchbase: - expiration: "10m" +spring: + cache: + cache-names: "cache1,cache2" + couchbase: + expiration: "10m" ---- If you need more control over the configuration, consider registering a `CouchbaseCacheManagerBuilderCustomizer` bean. The following example shows a customizer that configures a specific entry expiration for `cache1` and `cache2`: -include::code:MyCouchbaseCacheManagerConfiguration[] +include-code::MyCouchbaseCacheManagerConfiguration[] [[io.caching.provider.redis]] -==== Redis +=== Redis + If https://redis.io/[Redis] is available and configured, a `RedisCacheManager` is auto-configured. It is possible to create additional caches on startup by setting the configprop:spring.cache.cache-names[] property and cache defaults can be configured by using `spring.cache.redis.*` properties. For instance, the following configuration creates `cache1` and `cache2` caches with a _time to live_ of 10 minutes: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - cache: - cache-names: "cache1,cache2" - redis: - time-to-live: "10m" +spring: + cache: + cache-names: "cache1,cache2" + redis: + time-to-live: "10m" ---- NOTE: By default, a key prefix is added so that, if two separate caches use the same key, Redis does not have overlapping keys and cannot return invalid values. @@ -188,14 +196,15 @@ This can be useful if you need to customize the default serialization strategy. If you need more control over the configuration, consider registering a `RedisCacheManagerBuilderCustomizer` bean. The following example shows a customizer that configures a specific time to live for `cache1` and `cache2`: -include::code:MyRedisCacheManagerConfiguration[] +include-code::MyRedisCacheManagerConfiguration[] [[io.caching.provider.caffeine]] -==== Caffeine +=== Caffeine + https://github.com/ben-manes/caffeine[Caffeine] is a Java 8 rewrite of Guava's cache that supersedes support for Guava. -If Caffeine is present, a `CaffeineCacheManager` (provided by the `spring-boot-starter-cache` "`Starter`") is auto-configured. +If Caffeine is present, a `CaffeineCacheManager` (provided by the `spring-boot-starter-cache` starter) is auto-configured. Caches can be created on startup by setting the configprop:spring.cache.cache-names[] property and can be customized by one of the following (in the indicated order): . A cache spec defined by `spring.cache.caffeine.spec` @@ -204,13 +213,13 @@ Caches can be created on startup by setting the configprop:spring.cache.cache-na For instance, the following configuration creates `cache1` and `cache2` caches with a maximum size of 500 and a _time to live_ of 10 minutes -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - cache: - cache-names: "cache1,cache2" - caffeine: - spec: "maximumSize=500,expireAfterAccess=600s" +spring: + cache: + cache-names: "cache1,cache2" + caffeine: + spec: "maximumSize=500,expireAfterAccess=600s" ---- If a `com.github.benmanes.caffeine.cache.CacheLoader` bean is defined, it is automatically associated to the `CaffeineCacheManager`. @@ -220,7 +229,8 @@ The auto-configuration ignores any other generic type. [[io.caching.provider.cache2k]] -==== Cache2k +=== Cache2k + https://cache2k.org/[Cache2k] is an in-memory cache. If the Cache2k spring integration is present, a `SpringCache2kCacheManager` is auto-configured. @@ -228,22 +238,23 @@ Caches can be created on startup by setting the configprop:spring.cache.cache-na Cache defaults can be customized using a `Cache2kBuilderCustomizer` bean. The following example shows a customizer that configures the capacity of the cache to 200 entries, with an expiration of 5 minutes: -include::code:MyCache2kDefaultsConfiguration[] +include-code::MyCache2kDefaultsConfiguration[] [[io.caching.provider.simple]] -==== Simple +=== Simple + If none of the other providers can be found, a simple implementation using a `ConcurrentHashMap` as the cache store is configured. This is the default if no caching library is present in your application. By default, caches are created as needed, but you can restrict the list of available caches by setting the `cache-names` property. For instance, if you want only `cache1` and `cache2` caches, set the `cache-names` property as follows: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - cache: - cache-names: "cache1,cache2" +spring: + cache: + cache-names: "cache1,cache2" ---- If you do so and your application uses a cache not listed, then it fails at runtime when the cache is needed, but not on startup. @@ -252,16 +263,17 @@ This is similar to the way the "real" cache providers behave if you use an undec [[io.caching.provider.none]] -==== None +=== None + When `@EnableCaching` is present in your configuration, a suitable cache configuration is expected as well. If you have a custom `CacheManager`, consider defining it in a separate `@Configuration` class so that you can override it if necessary. None uses a no-op implementation that is useful in tests, and slice tests use that by default via `@AutoConfigureCache`. If you need to use a no-op cache rather than the auto-configured cache manager in a certain environment, set the cache type to `none`, as shown in the following example: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - cache: - type: "none" +spring: + cache: + type: "none" ---- diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/email.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/email.adoc new file mode 100644 index 000000000000..97d568c41d6f --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/email.adoc @@ -0,0 +1,33 @@ +[[io.email]] += Sending Email + +The Spring Framework provides an abstraction for sending email by using the `JavaMailSender` interface, and Spring Boot provides auto-configuration for it as well as a starter module. + +TIP: See the {url-spring-framework-docs}/integration/email.html[reference documentation] for a detailed explanation of how you can use `JavaMailSender`. + +If `spring.mail.host` and the relevant libraries (as defined by `spring-boot-starter-mail`) are available, a default `JavaMailSender` is created if none exists. +The sender can be further customized by configuration items from the `spring.mail` namespace. +See xref:api:java/org/springframework/boot/autoconfigure/mail/MailProperties.html[`MailProperties`] for more details. + +In particular, certain default timeout values are infinite, and you may want to change that to avoid having a thread blocked by an unresponsive mail server, as shown in the following example: + +[configprops,yaml] +---- +spring: + mail: + properties: + "[mail.smtp.connectiontimeout]": 5000 + "[mail.smtp.timeout]": 3000 + "[mail.smtp.writetimeout]": 5000 +---- + +It is also possible to configure a `JavaMailSender` with an existing `Session` from JNDI: + +[configprops,yaml] +---- +spring: + mail: + jndi-name: "mail/Session" +---- + +When a `jndi-name` is set, it takes precedence over all other Session-related settings. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/hazelcast.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/hazelcast.adoc similarity index 87% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/hazelcast.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/hazelcast.adoc index a0998eecd19e..761f8c6c293d 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/hazelcast.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/hazelcast.adoc @@ -1,5 +1,6 @@ [[io.hazelcast]] -== Hazelcast += Hazelcast + If https://hazelcast.com/[Hazelcast] is on the classpath and a suitable configuration is found, Spring Boot auto-configures a `HazelcastInstance` that you can inject in your application. Spring Boot first attempts to create a client by checking the following configuration options: @@ -16,11 +17,11 @@ If your configuration defines an instance name, Spring Boot tries to locate an e You could also specify the Hazelcast configuration file to use through configuration, as shown in the following example: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - hazelcast: - config: "classpath:config/my-hazelcast.xml" +spring: + hazelcast: + config: "classpath:config/my-hazelcast.xml" ---- Otherwise, Spring Boot tries to find the Hazelcast configuration from the default locations: `hazelcast.xml` in the working directory or at the root of the classpath, or a YAML counterpart in the same locations. @@ -30,5 +31,5 @@ See the https://docs.hazelcast.org/docs/latest/manual/html-single/[Hazelcast doc TIP: By default, `@SpringAware` on Hazelcast components is supported. The `ManagementContext` can be overridden by declaring a `HazelcastConfigCustomizer` bean with an `@Order` higher than zero. -NOTE: Spring Boot also has <>. +NOTE: Spring Boot also has xref:io/caching.adoc#io.caching.provider.hazelcast[explicit caching support for Hazelcast]. If caching is enabled, the `HazelcastInstance` is automatically wrapped in a `CacheManager` implementation. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/index.adoc new file mode 100644 index 000000000000..12be6754f78a --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/index.adoc @@ -0,0 +1,8 @@ +[[io]] += IO + +Most applications will need to deal with input and output concerns at some point. +Spring Boot provides utilities and integrations with a range of technologies to help when you need IO capabilities. +This section covers standard IO features such as caching and validation as well as more advanced topics such as scheduling and distributed transactions. +We will also cover calling remote REST or SOAP services and sending email. + diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/jta.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/jta.adoc similarity index 77% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/jta.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/jta.adoc index 5f9d1f3a2049..8e8ac9e40745 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/jta.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/jta.adoc @@ -1,5 +1,6 @@ [[io.jta]] -== Distributed Transactions With JTA += Distributed Transactions With JTA + Spring Boot supports distributed JTA transactions across multiple XA resources by using a transaction manager retrieved from JNDI. When a JTA environment is detected, Spring's `JtaTransactionManager` is used to manage transactions. @@ -10,36 +11,39 @@ If you are within a JTA environment and still want to use local transactions, yo [[io.jta.jakartaee]] -=== Using a Jakarta EE Managed Transaction Manager +== Using a Jakarta EE Managed Transaction Manager + If you package your Spring Boot application as a `war` or `ear` file and deploy it to a Jakarta EE application server, you can use your application server's built-in transaction manager. Spring Boot tries to auto-configure a transaction manager by looking at common JNDI locations (`java:comp/UserTransaction`, `java:comp/TransactionManager`, and so on). When using a transaction service provided by your application server, you generally also want to ensure that all resources are managed by the server and exposed over JNDI. -Spring Boot tries to auto-configure JMS by looking for a `ConnectionFactory` at the JNDI path (`java:/JmsXA` or `java:/XAConnectionFactory`), and you can use the <> to configure your `DataSource`. +Spring Boot tries to auto-configure JMS by looking for a `ConnectionFactory` at the JNDI path (`java:/JmsXA` or `java:/XAConnectionFactory`), and you can use the xref:data/sql.adoc#data.sql.datasource.jndi[configprop:spring.datasource.jndi-name[] property] to configure your `DataSource`. [[io.jta.mixing-xa-and-non-xa-connections]] -=== Mixing XA and Non-XA JMS Connections +== Mixing XA and Non-XA JMS Connections + When using JTA, the primary JMS `ConnectionFactory` bean is XA-aware and participates in distributed transactions. You can inject into your bean without needing to use any `@Qualifier`: -include::code:primary/MyBean[tag=*] +include-code::primary/MyBean[tag=*] In some situations, you might want to process certain JMS messages by using a non-XA `ConnectionFactory`. For example, your JMS processing logic might take longer than the XA timeout. If you want to use a non-XA `ConnectionFactory`, you can the `nonXaJmsConnectionFactory` bean: -include::code:nonxa/MyBean[tag=*] +include-code::nonxa/MyBean[tag=*] For consistency, the `jmsConnectionFactory` bean is also provided by using the bean alias `xaJmsConnectionFactory`: -include::code:xa/MyBean[tag=*] +include-code::xa/MyBean[tag=*] [[io.jta.supporting-embedded-transaction-manager]] -=== Supporting an Embedded Transaction Manager -The {spring-boot-module-code}/jms/XAConnectionFactoryWrapper.java[`XAConnectionFactoryWrapper`] and {spring-boot-module-code}/jdbc/XADataSourceWrapper.java[`XADataSourceWrapper`] interfaces can be used to support embedded transaction managers. +== Supporting an Embedded Transaction Manager + +The xref:api:java/org/springframework/boot/jms/XAConnectionFactoryWrapper.html[`XAConnectionFactoryWrapper`] and xref:api:java/org/springframework/boot/jdbc/XADataSourceWrapper.html[`XADataSourceWrapper`] interfaces can be used to support embedded transaction managers. The interfaces are responsible for wrapping `XAConnectionFactory` and `XADataSource` beans and exposing them as regular `ConnectionFactory` and `DataSource` beans, which transparently enroll in the distributed transaction. DataSource and JMS auto-configuration use JTA variants, provided you have a `JtaTransactionManager` bean and appropriate XA wrapper beans registered within your `ApplicationContext`. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/quartz.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/quartz.adoc new file mode 100644 index 000000000000..e440aa0d835c --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/quartz.adoc @@ -0,0 +1,54 @@ +[[io.quartz]] += Quartz Scheduler + +Spring Boot offers several conveniences for working with the https://www.quartz-scheduler.org/[Quartz scheduler], including the `spring-boot-starter-quartz` starter. +If Quartz is available, a `Scheduler` is auto-configured (through the `SchedulerFactoryBean` abstraction). + +Beans of the following types are automatically picked up and associated with the `Scheduler`: + +* `JobDetail`: defines a particular Job. + `JobDetail` instances can be built with the `JobBuilder` API. +* `Calendar`. +* `Trigger`: defines when a particular job is triggered. + +By default, an in-memory `JobStore` is used. +However, it is possible to configure a JDBC-based store if a `DataSource` bean is available in your application and if the configprop:spring.quartz.job-store-type[] property is configured accordingly, as shown in the following example: + +[configprops,yaml] +---- +spring: + quartz: + job-store-type: "jdbc" +---- + +When the JDBC store is used, the schema can be initialized on startup, as shown in the following example: + +[configprops,yaml] +---- +spring: + quartz: + jdbc: + initialize-schema: "always" +---- + +WARNING: By default, the database is detected and initialized by using the standard scripts provided with the Quartz library. +These scripts drop existing tables, deleting all triggers on every restart. +It is also possible to provide a custom script by setting the configprop:spring.quartz.jdbc.schema[] property. + +To have Quartz use a `DataSource` other than the application's main `DataSource`, declare a `DataSource` bean, annotating its `@Bean` method with `@QuartzDataSource`. +Doing so ensures that the Quartz-specific `DataSource` is used by both the `SchedulerFactoryBean` and for schema initialization. +Similarly, to have Quartz use a `TransactionManager` other than the application's main `TransactionManager` declare a `TransactionManager` bean, annotating its `@Bean` method with `@QuartzTransactionManager`. + +By default, jobs created by configuration will not overwrite already registered jobs that have been read from a persistent job store. +To enable overwriting existing job definitions set the configprop:spring.quartz.overwrite-existing-jobs[] property. + +Quartz Scheduler configuration can be customized using `spring.quartz` properties and `SchedulerFactoryBeanCustomizer` beans, which allow programmatic `SchedulerFactoryBean` customization. +Advanced Quartz configuration properties can be customized using `spring.quartz.properties.*`. + +NOTE: In particular, an `Executor` bean is not associated with the scheduler as Quartz offers a way to configure the scheduler through `spring.quartz.properties`. +If you need to customize the task executor, consider implementing `SchedulerFactoryBeanCustomizer`. + +Jobs can define setters to inject data map properties. +Regular beans can also be injected in a similar manner, as shown in the following example: + +include-code::MySampleJob[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/rest-client.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/rest-client.adoc new file mode 100644 index 000000000000..7a9eaac2139c --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/rest-client.adoc @@ -0,0 +1,200 @@ +[[io.rest-client]] += Calling REST Services + +Spring Boot provides various convenient ways to call remote REST services. +If you are developing a non-blocking reactive application and you're using Spring WebFlux, then you can use `WebClient`. +If you prefer blocking APIs then you can use `RestClient` or `RestTemplate`. + + + +[[io.rest-client.webclient]] +== WebClient + +If you have Spring WebFlux on your classpath we recommend that you use `WebClient` to call remote REST services. +The `WebClient` interface provides a functional style API and is fully reactive. +You can learn more about the `WebClient` in the dedicated {url-spring-framework-docs}/web/webflux-webclient.html[section in the Spring Framework docs]. + +TIP: If you are not writing a reactive Spring WebFlux application you can use the xref:io/rest-client.adoc#io.rest-client.restclient[`RestClient`] instead of a `WebClient`. +This provides a similar functional API, but is blocking rather than reactive. + +Spring Boot creates and pre-configures a prototype `WebClient.Builder` bean for you. +It is strongly advised to inject it in your components and use it to create `WebClient` instances. +Spring Boot is configuring that builder to share HTTP resources and reflect codecs setup in the same fashion as the server ones (see xref:web/reactive.adoc#web.reactive.webflux.httpcodecs[WebFlux HTTP codecs auto-configuration]), and more. + +The following code shows a typical example: + +include-code::MyService[] + + + +[[io.rest-client.webclient.runtime]] +=== WebClient Runtime + +Spring Boot will auto-detect which `ClientHttpConnector` to use to drive `WebClient` depending on the libraries available on the application classpath. +In order of preference, the following clients are supported: + +. Reactor Netty +. Jetty RS client +. Apache HttpClient +. JDK HttpClient + +If multiple clients are available on the classpath, the most preferred client will be used. + +The `spring-boot-starter-webflux` starter depends on `io.projectreactor.netty:reactor-netty` by default, which brings both server and client implementations. +If you choose to use Jetty as a reactive server instead, you should add a dependency on the Jetty Reactive HTTP client library, `org.eclipse.jetty:jetty-reactive-httpclient`. +Using the same technology for server and client has its advantages, as it will automatically share HTTP resources between client and server. + +Developers can override the resource configuration for Jetty and Reactor Netty by providing a custom `ReactorResourceFactory` or `JettyResourceFactory` bean - this will be applied to both clients and servers. + +If you wish to override that choice for the client, you can define your own `ClientHttpConnector` bean and have full control over the client configuration. + +You can learn more about the {url-spring-framework-docs}/web/webflux-webclient/client-builder.html[`WebClient` configuration options in the Spring Framework reference documentation]. + + + +[[io.rest-client.webclient.customization]] +=== WebClient Customization + +There are three main approaches to `WebClient` customization, depending on how broadly you want the customizations to apply. + +To make the scope of any customizations as narrow as possible, inject the auto-configured `WebClient.Builder` and then call its methods as required. +`WebClient.Builder` instances are stateful: Any change on the builder is reflected in all clients subsequently created with it. +If you want to create several clients with the same builder, you can also consider cloning the builder with `WebClient.Builder other = builder.clone();`. + +To make an application-wide, additive customization to all `WebClient.Builder` instances, you can declare `WebClientCustomizer` beans and change the `WebClient.Builder` locally at the point of injection. + +Finally, you can fall back to the original API and use `WebClient.create()`. +In that case, no auto-configuration or `WebClientCustomizer` is applied. + + + +[[io.rest-client.webclient.ssl]] +=== WebClient SSL Support + +If you need custom SSL configuration on the `ClientHttpConnector` used by the `WebClient`, you can inject a `WebClientSsl` instance that can be used with the builder's `apply` method. + +The `WebClientSsl` interface provides access to any xref:features/ssl.adoc#features.ssl.bundles[SSL bundles] that you have defined in your `application.properties` or `application.yaml` file. + +The following code shows a typical example: + +include-code::MyService[] + + + +[[io.rest-client.restclient]] +== RestClient + +If you are not using Spring WebFlux or Project Reactor in your application we recommend that you use `RestClient` to call remote REST services. + +The `RestClient` interface provides a functional style blocking API. + +Spring Boot creates and pre-configures a prototype `RestClient.Builder` bean for you. +It is strongly advised to inject it in your components and use it to create `RestClient` instances. +Spring Boot is configuring that builder with `HttpMessageConverters` and an appropriate `ClientHttpRequestFactory`. + +The following code shows a typical example: + +include-code::MyService[] + + + +[[io.rest-client.restclient.customization]] +=== RestClient Customization + +There are three main approaches to `RestClient` customization, depending on how broadly you want the customizations to apply. + +To make the scope of any customizations as narrow as possible, inject the auto-configured `RestClient.Builder` and then call its methods as required. +`RestClient.Builder` instances are stateful: Any change on the builder is reflected in all clients subsequently created with it. +If you want to create several clients with the same builder, you can also consider cloning the builder with `RestClient.Builder other = builder.clone();`. + +To make an application-wide, additive customization to all `RestClient.Builder` instances, you can declare `RestClientCustomizer` beans and change the `RestClient.Builder` locally at the point of injection. + +Finally, you can fall back to the original API and use `RestClient.create()`. +In that case, no auto-configuration or `RestClientCustomizer` is applied. + + + +[[io.rest-client.restclient.ssl]] +=== RestClient SSL Support + +If you need custom SSL configuration on the `ClientHttpRequestFactory` used by the `RestClient`, you can inject a `RestClientSsl` instance that can be used with the builder's `apply` method. + +The `RestClientSsl` interface provides access to any xref:features/ssl.adoc#features.ssl.bundles[SSL bundles] that you have defined in your `application.properties` or `application.yaml` file. + +The following code shows a typical example: + +include-code::MyService[] + +If you need to apply other customization in addition to an SSL bundle, you can use the `ClientHttpRequestFactorySettings` class with `ClientHttpRequestFactories`: + +include-code::settings/MyService[] + + + +[[io.rest-client.resttemplate]] +== RestTemplate + +Spring Framework's {url-spring-framework-javadoc}/org/springframework/web/client/RestTemplate.html[`RestTemplate`] class predates `RestClient` and is the classic way that many applications use to call remote REST services. +You might choose to use `RestTemplate` when you have existing code that you don't want to migrate to `RestClient`, or because you're already familiar with the `RestTemplate` API. + +Since `RestTemplate` instances often need to be customized before being used, Spring Boot does not provide any single auto-configured `RestTemplate` bean. +It does, however, auto-configure a `RestTemplateBuilder`, which can be used to create `RestTemplate` instances when needed. +The auto-configured `RestTemplateBuilder` ensures that sensible `HttpMessageConverters` and an appropriate `ClientHttpRequestFactory` are applied to `RestTemplate` instances. + +The following code shows a typical example: + +include-code::MyService[] + +`RestTemplateBuilder` includes a number of useful methods that can be used to quickly configure a `RestTemplate`. +For example, to add BASIC authentication support, you can use `builder.basicAuthentication("user", "password").build()`. + + + +[[io.rest-client.resttemplate.customization]] +=== RestTemplate Customization + +There are three main approaches to `RestTemplate` customization, depending on how broadly you want the customizations to apply. + +To make the scope of any customizations as narrow as possible, inject the auto-configured `RestTemplateBuilder` and then call its methods as required. +Each method call returns a new `RestTemplateBuilder` instance, so the customizations only affect this use of the builder. + +To make an application-wide, additive customization, use a `RestTemplateCustomizer` bean. +All such beans are automatically registered with the auto-configured `RestTemplateBuilder` and are applied to any templates that are built with it. + +The following example shows a customizer that configures the use of a proxy for all hosts except `192.168.0.5`: + +include-code::MyRestTemplateCustomizer[] + +Finally, you can define your own `RestTemplateBuilder` bean. +Doing so will replace the auto-configured builder. +If you want any `RestTemplateCustomizer` beans to be applied to your custom builder, as the auto-configuration would have done, configure it using a `RestTemplateBuilderConfigurer`. +The following example exposes a `RestTemplateBuilder` that matches what Spring Boot's auto-configuration would have done, except that custom connect and read timeouts are also specified: + +include-code::MyRestTemplateBuilderConfiguration[] + +The most extreme (and rarely used) option is to create your own `RestTemplateBuilder` bean without using a configurer. +In addition to replacing the auto-configured builder, this also prevents any `RestTemplateCustomizer` beans from being used. + + + +[[io.rest-client.resttemplate.ssl]] +=== RestTemplate SSL Support + +If you need custom SSL configuration on the `RestTemplate`, you can apply an xref:features/ssl.adoc#features.ssl.bundles[SSL bundle] to the `RestTemplateBuilder` as shown in this example: + +include-code::MyService[] + + + +[[io.rest-client.clienthttprequestfactory]] +== HTTP Client Detection for RestClient and RestTemplate + +Spring Boot will auto-detect which HTTP client to use with `RestClient` and `RestTemplate` depending on the libraries available on the application classpath. +In order of preference, the following clients are supported: + +. Apache HttpClient +. Jetty HttpClient +. OkHttp (deprecated) +. Simple JDK client (`HttpURLConnection`) + +If multiple clients are available on the classpath, the most preferred client will be used. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/validation.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/validation.adoc similarity index 85% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/validation.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/validation.adoc index 092d48870fee..59ad63a80571 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/validation.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/validation.adoc @@ -1,15 +1,16 @@ [[io.validation]] -== Validation += Validation + The method validation feature supported by Bean Validation 1.1 is automatically enabled as long as a JSR-303 implementation (such as Hibernate validator) is on the classpath. This lets bean methods be annotated with `jakarta.validation` constraints on their parameters and/or on their return value. Target classes with such annotated methods need to be annotated with the `@Validated` annotation at the type level for their methods to be searched for inline constraint annotations. For instance, the following service triggers the validation of the first argument, making sure its size is between 8 and 10: -include::code:MyBean[] +include-code::MyBean[] The application's `MessageSource` is used when resolving `+{parameters}+` in constraint messages. -This allows you to use <> for Bean Validation messages. +This allows you to use xref:features/internationalization.adoc[your application's `messages.properties` files] for Bean Validation messages. Once the parameters have been resolved, message interpolation is completed using Bean Validation's default interpolator. To customize the `Configuration` used to build the `ValidatorFactory`, define a `ValidationConfigurationCustomizer` bean. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/webservices.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/webservices.adoc new file mode 100644 index 000000000000..7793966a4b2d --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/webservices.adoc @@ -0,0 +1,35 @@ +[[io.webservices]] += Web Services + +Spring Boot provides Web Services auto-configuration so that all you must do is define your `Endpoints`. + +The {url-spring-webservices-docs}[Spring Web Services features] can be easily accessed with the `spring-boot-starter-webservices` module. + +`SimpleWsdl11Definition` and `SimpleXsdSchema` beans can be automatically created for your WSDLs and XSDs respectively. +To do so, configure their location, as shown in the following example: + + +[configprops,yaml] +---- +spring: + webservices: + wsdl-locations: "classpath:/wsdl" +---- + + + +[[io.webservices.template]] +== Calling Web Services with WebServiceTemplate + +If you need to call remote Web services from your application, you can use the {url-spring-webservices-docs}#client-web-service-template[`WebServiceTemplate`] class. +Since `WebServiceTemplate` instances often need to be customized before being used, Spring Boot does not provide any single auto-configured `WebServiceTemplate` bean. +It does, however, auto-configure a `WebServiceTemplateBuilder`, which can be used to create `WebServiceTemplate` instances when needed. + +The following code shows a typical example: + +include-code::MyService[] + +By default, `WebServiceTemplateBuilder` detects a suitable HTTP-based `WebServiceMessageSender` using the available HTTP client libraries on the classpath. +You can also customize read and connection timeouts as follows: + +include-code::MyWebServiceTemplateConfiguration[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/amqp.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/amqp.adoc similarity index 80% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/amqp.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/amqp.adoc index 42d230aaac77..6c90de6342a7 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/amqp.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/amqp.adoc @@ -1,42 +1,44 @@ [[messaging.amqp]] -== AMQP += AMQP + The Advanced Message Queuing Protocol (AMQP) is a platform-neutral, wire-level protocol for message-oriented middleware. The Spring AMQP project applies core Spring concepts to the development of AMQP-based messaging solutions. -Spring Boot offers several conveniences for working with AMQP through RabbitMQ, including the `spring-boot-starter-amqp` "`Starter`". +Spring Boot offers several conveniences for working with AMQP through RabbitMQ, including the `spring-boot-starter-amqp` starter. [[messaging.amqp.rabbitmq]] -=== RabbitMQ Support +== RabbitMQ Support + https://www.rabbitmq.com/[RabbitMQ] is a lightweight, reliable, scalable, and portable message broker based on the AMQP protocol. Spring uses RabbitMQ to communicate through the AMQP protocol. RabbitMQ configuration is controlled by external configuration properties in `+spring.rabbitmq.*+`. For example, you might declare the following section in `application.properties`: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - rabbitmq: - host: "localhost" - port: 5672 - username: "admin" - password: "secret" +spring: + rabbitmq: + host: "localhost" + port: 5672 + username: "admin" + password: "secret" ---- Alternatively, you could configure the same connection using the `addresses` attribute: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - rabbitmq: - addresses: "amqp://admin:secret@localhost" +spring: + rabbitmq: + addresses: "amqp://admin:secret@localhost" ---- NOTE: When specifying addresses that way, the `host` and `port` properties are ignored. If the address uses the `amqps` protocol, SSL support is enabled automatically. -See {spring-boot-autoconfigure-module-code}/amqp/RabbitProperties.java[`RabbitProperties`] for more of the supported property-based configuration options. +See xref:api:java/org/springframework/boot/autoconfigure/amqp/RabbitProperties.html[`RabbitProperties`] for more of the supported property-based configuration options. To configure lower-level details of the RabbitMQ `ConnectionFactory` that is used by Spring AMQP, define a `ConnectionFactoryCustomizer` bean. If a `ConnectionNameStrategy` bean exists in the context, it will be automatically used to name connections created by the auto-configured `CachingConnectionFactory`. @@ -48,26 +50,27 @@ TIP: See https://spring.io/blog/2010/06/14/understanding-amqp-the-protocol-used- [[messaging.amqp.sending]] -=== Sending a Message +== Sending a Message + Spring's `AmqpTemplate` and `AmqpAdmin` are auto-configured, and you can autowire them directly into your own beans, as shown in the following example: -include::code:MyBean[] +include-code::MyBean[] -NOTE: {spring-amqp-api}/rabbit/core/RabbitMessagingTemplate.html[`RabbitMessagingTemplate`] can be injected in a similar manner. +NOTE: {url-spring-amqp-javadoc}/org/springframework/amqp/rabbit/core/RabbitMessagingTemplate.html[`RabbitMessagingTemplate`] can be injected in a similar manner. If a `MessageConverter` bean is defined, it is associated automatically to the auto-configured `AmqpTemplate`. If necessary, any `org.springframework.amqp.core.Queue` that is defined as a bean is automatically used to declare a corresponding queue on the RabbitMQ instance. To retry operations, you can enable retries on the `AmqpTemplate` (for example, in the event that the broker connection is lost): -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - rabbitmq: - template: - retry: - enabled: true - initial-interval: "2s" +spring: + rabbitmq: + template: + retry: + enabled: true + initial-interval: "2s" ---- Retries are disabled by default. @@ -78,15 +81,16 @@ If you need to create more `RabbitTemplate` instances or if you want to override [[messaging.amqp.sending-stream]] -=== Sending a Message To A Stream +== Sending a Message To A Stream + To send a message to a particular stream, specify the name of the stream, as shown in the following example: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - rabbitmq: - stream: - name: "my-stream" +spring: + rabbitmq: + stream: + name: "my-stream" ---- If a `MessageConverter`, `StreamMessageConverter`, or `ProducerCustomizer` bean is defined, it is associated automatically to the auto-configured `RabbitStreamTemplate`. @@ -96,16 +100,17 @@ If you need to create more `RabbitStreamTemplate` instances or if you want to ov [[messaging.amqp.receiving]] -=== Receiving a Message +== Receiving a Message + When the Rabbit infrastructure is present, any bean can be annotated with `@RabbitListener` to create a listener endpoint. If no `RabbitListenerContainerFactory` has been defined, a default `SimpleRabbitListenerContainerFactory` is automatically configured and you can switch to a direct container using the configprop:spring.rabbitmq.listener.type[] property. If a `MessageConverter` or a `MessageRecoverer` bean is defined, it is automatically associated with the default factory. The following sample component creates a listener endpoint on the `someQueue` queue: -include::code:MyBean[] +include-code::MyBean[] -TIP: See {spring-amqp-api}/rabbit/annotation/EnableRabbit.html[the Javadoc of `@EnableRabbit`] for more details. +TIP: See {url-spring-amqp-javadoc}/org/springframework/amqp/rabbit/annotation/EnableRabbit.html[the Javadoc of `@EnableRabbit`] for more details. If you need to create more `RabbitListenerContainerFactory` instances or if you want to override the default, Spring Boot provides a `SimpleRabbitListenerContainerFactoryConfigurer` and a `DirectRabbitListenerContainerFactoryConfigurer` that you can use to initialize a `SimpleRabbitListenerContainerFactory` and a `DirectRabbitListenerContainerFactory` with the same settings as the factories used by the auto-configuration. @@ -114,11 +119,11 @@ Those two beans are exposed by the auto-configuration. For instance, the following configuration class exposes another factory that uses a specific `MessageConverter`: -include::code:custom/MyRabbitConfiguration[] +include-code::custom/MyRabbitConfiguration[] Then you can use the factory in any `@RabbitListener`-annotated method, as follows: -include::code:custom/MyBean[] +include-code::custom/MyBean[] You can enable retries to handle situations where your listener throws an exception. By default, `RejectAndDontRequeueRecoverer` is used, but you can define a `MessageRecoverer` of your own. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/index.adoc new file mode 100644 index 000000000000..88957a1d7cb7 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/index.adoc @@ -0,0 +1,8 @@ +[[messaging]] += Messaging + +The Spring Framework provides extensive support for integrating with messaging systems, from simplified use of the JMS API using `JmsTemplate` to a complete infrastructure to receive messages asynchronously. +Spring AMQP provides a similar feature set for the Advanced Message Queuing Protocol. +Spring Boot also provides auto-configuration options for `RabbitTemplate` and RabbitMQ. +Spring WebSocket natively includes support for STOMP messaging, and Spring Boot has support for that through starters and a small amount of auto-configuration. +Spring Boot also has support for Apache Kafka and Apache Pulsar. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/jms.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/jms.adoc new file mode 100644 index 000000000000..e5f8cb39c415 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/jms.adoc @@ -0,0 +1,174 @@ +[[messaging.jms]] += JMS + +The `jakarta.jms.ConnectionFactory` interface provides a standard method of creating a `jakarta.jms.Connection` for interacting with a JMS broker. +Although Spring needs a `ConnectionFactory` to work with JMS, you generally need not use it directly yourself and can instead rely on higher level messaging abstractions. +(See the {url-spring-framework-docs}/integration/jms.html[relevant section] of the Spring Framework reference documentation for details.) +Spring Boot also auto-configures the necessary infrastructure to send and receive messages. + + + +[[messaging.jms.activemq]] +== ActiveMQ "Classic" Support + +When https://activemq.apache.org/components/classic[ActiveMQ "Classic"] is available on the classpath, Spring Boot can configure a `ConnectionFactory`. + +NOTE: If you use `spring-boot-starter-activemq`, the necessary dependencies to connect to an ActiveMQ "Classic" instance are provided, as is the Spring infrastructure to integrate with JMS. + +ActiveMQ "Classic" configuration is controlled by external configuration properties in `+spring.activemq.*+`. +By default, ActiveMQ "Classic" is auto-configured to use the https://activemq.apache.org/tcp-transport-reference[TCP transport], connecting by default to `tcp://localhost:61616`. The following example shows how to change the default broker URL: + +[configprops,yaml] +---- +spring: + activemq: + broker-url: "tcp://192.168.1.210:9876" + user: "admin" + password: "secret" +---- + +By default, a `CachingConnectionFactory` wraps the native `ConnectionFactory` with sensible settings that you can control by external configuration properties in `+spring.jms.*+`: + +[configprops,yaml] +---- +spring: + jms: + cache: + session-cache-size: 5 +---- + +If you'd rather use native pooling, you can do so by adding a dependency to `org.messaginghub:pooled-jms` and configuring the `JmsPoolConnectionFactory` accordingly, as shown in the following example: + +[configprops,yaml] +---- +spring: + activemq: + pool: + enabled: true + max-connections: 50 +---- + +TIP: See xref:api:java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.html[`ActiveMQProperties`] for more of the supported options. +You can also register an arbitrary number of beans that implement `ActiveMQConnectionFactoryCustomizer` for more advanced customizations. + +By default, ActiveMQ "Classic" creates a destination if it does not yet exist so that destinations are resolved against their provided names. + + + +[[messaging.jms.artemis]] +== ActiveMQ Artemis Support + +Spring Boot can auto-configure a `ConnectionFactory` when it detects that https://activemq.apache.org/components/artemis/[ActiveMQ Artemis] is available on the classpath. +If the broker is present, an embedded broker is automatically started and configured (unless the mode property has been explicitly set). +The supported modes are `embedded` (to make explicit that an embedded broker is required and that an error should occur if the broker is not available on the classpath) and `native` (to connect to a broker using the `netty` transport protocol). +When the latter is configured, Spring Boot configures a `ConnectionFactory` that connects to a broker running on the local machine with the default settings. + +NOTE: If you use `spring-boot-starter-artemis`, the necessary dependencies to connect to an existing ActiveMQ Artemis instance are provided, as well as the Spring infrastructure to integrate with JMS. +Adding `org.apache.activemq:artemis-jakarta-server` to your application lets you use embedded mode. + +ActiveMQ Artemis configuration is controlled by external configuration properties in `+spring.artemis.*+`. +For example, you might declare the following section in `application.properties`: + +[configprops,yaml] +---- +spring: + artemis: + mode: native + broker-url: "tcp://192.168.1.210:9876" + user: "admin" + password: "secret" +---- + +When embedding the broker, you can choose if you want to enable persistence and list the destinations that should be made available. +These can be specified as a comma-separated list to create them with the default options, or you can define bean(s) of type `org.apache.activemq.artemis.jms.server.config.JMSQueueConfiguration` or `org.apache.activemq.artemis.jms.server.config.TopicConfiguration`, for advanced queue and topic configurations, respectively. + +By default, a `CachingConnectionFactory` wraps the native `ConnectionFactory` with sensible settings that you can control by external configuration properties in `+spring.jms.*+`: + +[configprops,yaml] +---- +spring: + jms: + cache: + session-cache-size: 5 +---- + +If you'd rather use native pooling, you can do so by adding a dependency on `org.messaginghub:pooled-jms` and configuring the `JmsPoolConnectionFactory` accordingly, as shown in the following example: + +[configprops,yaml] +---- +spring: + artemis: + pool: + enabled: true + max-connections: 50 +---- + +See xref:api:java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisProperties.html[`ArtemisProperties`] for more supported options. + +No JNDI lookup is involved, and destinations are resolved against their names, using either the `name` attribute in the ActiveMQ Artemis configuration or the names provided through configuration. + + + +[[messaging.jms.jndi]] +== Using a JNDI ConnectionFactory + +If you are running your application in an application server, Spring Boot tries to locate a JMS `ConnectionFactory` by using JNDI. +By default, the `java:/JmsXA` and `java:/XAConnectionFactory` location are checked. +You can use the configprop:spring.jms.jndi-name[] property if you need to specify an alternative location, as shown in the following example: + +[configprops,yaml] +---- +spring: + jms: + jndi-name: "java:/MyConnectionFactory" +---- + + + +[[messaging.jms.sending]] +== Sending a Message + +Spring's `JmsTemplate` is auto-configured, and you can autowire it directly into your own beans, as shown in the following example: + +include-code::MyBean[] + +NOTE: {url-spring-framework-javadoc}/org/springframework/jms/core/JmsMessagingTemplate.html[`JmsMessagingTemplate`] can be injected in a similar manner. +If a `DestinationResolver` or a `MessageConverter` bean is defined, it is associated automatically to the auto-configured `JmsTemplate`. + + + +[[messaging.jms.receiving]] +== Receiving a Message + +When the JMS infrastructure is present, any bean can be annotated with `@JmsListener` to create a listener endpoint. +If no `JmsListenerContainerFactory` has been defined, a default one is configured automatically. +If a `DestinationResolver`, a `MessageConverter`, or a `jakarta.jms.ExceptionListener` beans are defined, they are associated automatically with the default factory. + +In most scenarios, message listener containers should be configured against the native `ConnectionFactory`. +This way each listener container has its own connection and this gives full responsibility to it in terms of local recovery. +The auto-configuration uses `ConnectionFactoryUnwrapper` to unwrap the native connection factory from the auto-configured one. + +By default, the default factory is transactional. +If you run in an infrastructure where a `JtaTransactionManager` is present, it is associated to the listener container by default. +If not, the `sessionTransacted` flag is enabled. +In that latter scenario, you can associate your local data store transaction to the processing of an incoming message by adding `@Transactional` on your listener method (or a delegate thereof). +This ensures that the incoming message is acknowledged, once the local transaction has completed. +This also includes sending response messages that have been performed on the same JMS session. + +The following component creates a listener endpoint on the `someQueue` destination: + +include-code::MyBean[] + +TIP: See the {url-spring-framework-javadoc}/org/springframework/jms/annotation/EnableJms.html[`@EnableJms`] API documentation for more details. + +If you need to create more `JmsListenerContainerFactory` instances or if you want to override the default, Spring Boot provides a `DefaultJmsListenerContainerFactoryConfigurer` that you can use to initialize a `DefaultJmsListenerContainerFactory` with the same settings as the one that is auto-configured. + +For instance, the following example exposes another factory that uses a specific `MessageConverter`: + +include-code::custom/MyJmsConfiguration[] + +NOTE: In the example above, the customization uses `ConnectionFactoryUnwrapper` to associate the native connection factory to the message listener container the same way the auto-configured factory does. + +Then you can use the factory in any `@JmsListener`-annotated method as follows: + +include-code::custom/MyBean[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/kafka.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/kafka.adoc new file mode 100644 index 000000000000..ba422e8164a3 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/kafka.adoc @@ -0,0 +1,171 @@ +[[messaging.kafka]] += Apache Kafka Support + +https://kafka.apache.org/[Apache Kafka] is supported by providing auto-configuration of the `spring-kafka` project. + +Kafka configuration is controlled by external configuration properties in `spring.kafka.*`. +For example, you might declare the following section in `application.properties`: + +[configprops,yaml] +---- +spring: + kafka: + bootstrap-servers: "localhost:9092" + consumer: + group-id: "myGroup" +---- + +TIP: To create a topic on startup, add a bean of type `NewTopic`. +If the topic already exists, the bean is ignored. + +See xref:api:java/org/springframework/boot/autoconfigure/kafka/KafkaProperties.html[`KafkaProperties`] for more supported options. + + + +[[messaging.kafka.sending]] +== Sending a Message + +Spring's `KafkaTemplate` is auto-configured, and you can autowire it directly in your own beans, as shown in the following example: + +include-code::MyBean[] + +NOTE: If the property configprop:spring.kafka.producer.transaction-id-prefix[] is defined, a `KafkaTransactionManager` is automatically configured. +Also, if a `RecordMessageConverter` bean is defined, it is automatically associated to the auto-configured `KafkaTemplate`. + + + +[[messaging.kafka.receiving]] +== Receiving a Message + +When the Apache Kafka infrastructure is present, any bean can be annotated with `@KafkaListener` to create a listener endpoint. +If no `KafkaListenerContainerFactory` has been defined, a default one is automatically configured with keys defined in `spring.kafka.listener.*`. + +The following component creates a listener endpoint on the `someTopic` topic: + +include-code::MyBean[] + +If a `KafkaTransactionManager` bean is defined, it is automatically associated to the container factory. +Similarly, if a `RecordFilterStrategy`, `CommonErrorHandler`, `AfterRollbackProcessor` or `ConsumerAwareRebalanceListener` bean is defined, it is automatically associated to the default factory. + +Depending on the listener type, a `RecordMessageConverter` or `BatchMessageConverter` bean is associated to the default factory. +If only a `RecordMessageConverter` bean is present for a batch listener, it is wrapped in a `BatchMessageConverter`. + +TIP: A custom `ChainedKafkaTransactionManager` must be marked `@Primary` as it usually references the auto-configured `KafkaTransactionManager` bean. + + + +[[messaging.kafka.streams]] +== Kafka Streams + +Spring for Apache Kafka provides a factory bean to create a `StreamsBuilder` object and manage the lifecycle of its streams. +Spring Boot auto-configures the required `KafkaStreamsConfiguration` bean as long as `kafka-streams` is on the classpath and Kafka Streams is enabled by the `@EnableKafkaStreams` annotation. + +Enabling Kafka Streams means that the application id and bootstrap servers must be set. +The former can be configured using `spring.kafka.streams.application-id`, defaulting to `spring.application.name` if not set. +The latter can be set globally or specifically overridden only for streams. + +Several additional properties are available using dedicated properties; other arbitrary Kafka properties can be set using the `spring.kafka.streams.properties` namespace. +See also xref:messaging/kafka.adoc#messaging.kafka.additional-properties[] for more information. + +To use the factory bean, wire `StreamsBuilder` into your `@Bean` as shown in the following example: + +include-code::MyKafkaStreamsConfiguration[] + +By default, the streams managed by the `StreamBuilder` object are started automatically. +You can customize this behavior using the configprop:spring.kafka.streams.auto-startup[] property. + + + +[[messaging.kafka.additional-properties]] +== Additional Kafka Properties + +The properties supported by auto configuration are shown in the xref:appendix:application-properties/index.adoc#appendix.application-properties.integration[Integration Properties] section of the Appendix. +Note that, for the most part, these properties (hyphenated or camelCase) map directly to the Apache Kafka dotted properties. +See the Apache Kafka documentation for details. + +Properties that don't include a client type (`producer`, `consumer`, `admin`, or `streams`) in their name are considered to be common and apply to all clients. +Most of these common properties can be overridden for one or more of the client types, if needed. + +Apache Kafka designates properties with an importance of HIGH, MEDIUM, or LOW. +Spring Boot auto-configuration supports all HIGH importance properties, some selected MEDIUM and LOW properties, and any properties that do not have a default value. + +Only a subset of the properties supported by Kafka are available directly through the `KafkaProperties` class. +If you wish to configure the individual client types with additional properties that are not directly supported, use the following properties: + +[configprops,yaml] +---- +spring: + kafka: + properties: + "[prop.one]": "first" + admin: + properties: + "[prop.two]": "second" + consumer: + properties: + "[prop.three]": "third" + producer: + properties: + "[prop.four]": "fourth" + streams: + properties: + "[prop.five]": "fifth" +---- + +This sets the common `prop.one` Kafka property to `first` (applies to producers, consumers, admins, and streams), the `prop.two` admin property to `second`, the `prop.three` consumer property to `third`, the `prop.four` producer property to `fourth` and the `prop.five` streams property to `fifth`. + +You can also configure the Spring Kafka `JsonDeserializer` as follows: + +[configprops,yaml] +---- +spring: + kafka: + consumer: + value-deserializer: "org.springframework.kafka.support.serializer.JsonDeserializer" + properties: + "[spring.json.value.default.type]": "com.example.Invoice" + "[spring.json.trusted.packages]": "com.example.main,com.example.another" +---- + +Similarly, you can disable the `JsonSerializer` default behavior of sending type information in headers: + +[configprops,yaml] +---- +spring: + kafka: + producer: + value-serializer: "org.springframework.kafka.support.serializer.JsonSerializer" + properties: + "[spring.json.add.type.headers]": false +---- + +IMPORTANT: Properties set in this way override any configuration item that Spring Boot explicitly supports. + + + +[[messaging.kafka.embedded]] +== Testing with Embedded Kafka + +Spring for Apache Kafka provides a convenient way to test projects with an embedded Apache Kafka broker. +To use this feature, annotate a test class with `@EmbeddedKafka` from the `spring-kafka-test` module. +For more information, please see the Spring for Apache Kafka {url-spring-kafka-docs}/testing.html#ekb[reference manual]. + +To make Spring Boot auto-configuration work with the aforementioned embedded Apache Kafka broker, you need to remap a system property for embedded broker addresses (populated by the `EmbeddedKafkaBroker`) into the Spring Boot configuration property for Apache Kafka. +There are several ways to do that: + +* Provide a system property to map embedded broker addresses into configprop:spring.kafka.bootstrap-servers[] in the test class: + +include-code::property/MyTest[tag=*] + +* Configure a property name on the `@EmbeddedKafka` annotation: + +include-code::annotation/MyTest[] + +* Use a placeholder in configuration properties: + +[configprops,yaml] +---- +spring: + kafka: + bootstrap-servers: "${spring.embedded.kafka.brokers}" +---- diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/pulsar.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/pulsar.adoc new file mode 100644 index 000000000000..558456f1ecad --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/pulsar.adoc @@ -0,0 +1,244 @@ +[[messaging.pulsar]] += Apache Pulsar Support + +https://pulsar.apache.org/[Apache Pulsar] is supported by providing auto-configuration of the {url-spring-pulsar-site}[Spring for Apache Pulsar] project. + +Spring Boot will auto-configure and register the classic (imperative) Spring for Apache Pulsar components when `org.springframework.pulsar:spring-pulsar` is on the classpath. +It will do the same for the reactive components when `org.springframework.pulsar:spring-pulsar-reactive` is on the classpath. + +There are `spring-boot-starter-pulsar` and `spring-boot-starter-pulsar-reactive` starters for conveniently collecting the dependencies for imperative and reactive use, respectively. + + + +[[messaging.pulsar.connecting]] +== Connecting to Pulsar + +When you use the Pulsar starter, Spring Boot will auto-configure and register a `PulsarClient` bean. + +By default, the application tries to connect to a local Pulsar instance at `pulsar://localhost:6650`. +This can be adjusted by setting the configprop:spring.pulsar.client.service-url[] property to a different value. + +NOTE: The value must be a valid https://pulsar.apache.org/docs/client-libraries-java/#connection-urls[Pulsar Protocol] URL + +You can configure the client by specifying any of the `spring.pulsar.client.*` prefixed application properties. + +If you need more control over the configuration, consider registering one or more `PulsarClientBuilderCustomizer` beans. + + + +[[messaging.pulsar.connecting.auth]] +=== Authentication + +To connect to a Pulsar cluster that requires authentication, you need to specify which authentication plugin to use by setting the `pluginClassName` and any parameters required by the plugin. +You can set the parameters as a map of parameter names to parameter values. +The following example shows how to configure the `AuthenticationOAuth2` plugin. + +[configprops,yaml] +---- +spring: + pulsar: + client: + authentication: + plugin-class-name: org.apache.pulsar.client.impl.auth.oauth2.AuthenticationOAuth2 + param: + issuerUrl: https://auth.server.cloud/ + privateKey: file:///Users/some-key.json + audience: urn:sn:acme:dev:my-instance +---- + +[NOTE] +==== +You need to ensure that names defined under `+spring.pulsar.client.authentication.param.*+` exactly match those expected by your auth plugin (which is typically camel cased). +Spring Boot will not attempt any kind of relaxed binding for these entries. + +For example, if you want to configure the issuer url for the `AuthenticationOAuth2` auth plugin you must use `+spring.pulsar.client.authentication.param.issuerUrl+`. +If you use other forms, such as `issuerurl` or `issuer-url`, the setting will not be applied to the plugin. + +This lack of relaxed binding also makes using environment variables for authentication parameters problematic because the case sensitivity is lost during translation. +If you use environment variables for the parameters then you will need to follow {url-spring-pulsar-docs}/reference/pulsar/pulsar-client.html#client-authentication-env-vars[these steps] in the Spring for Apache Pulsar reference documentation for it to work properly. +==== + + + +[[messaging.pulsar.connecting.ssl]] +=== SSL + +By default, Pulsar clients communicate with Pulsar services in plain text. +You can follow {url-spring-pulsar-docs}/reference/pulsar/pulsar-client.html#tls-encryption[these steps] in the Spring for Apache Pulsar reference documentation to enable TLS encryption. + +For complete details on the client and authentication see the Spring for Apache Pulsar {url-spring-pulsar-docs}/reference/pulsar/pulsar-client.html[reference documentation]. + +[[messaging.pulsar.connecting-reactive]] +== Connecting to Pulsar Reactively + +When the Reactive auto-configuration is activated, Spring Boot will auto-configure and register a `ReactivePulsarClient` bean. + +The `ReactivePulsarClient` adapts an instance of the previously described `PulsarClient`. +Therefore, follow the previous section to configure the `PulsarClient` used by the `ReactivePulsarClient`. + + + +[[messaging.pulsar.admin]] +== Connecting to Pulsar Administration + +Spring for Apache Pulsar's `PulsarAdministration` client is also auto-configured. + +By default, the application tries to connect to a local Pulsar instance at `\http://localhost:8080`. +This can be adjusted by setting the configprop:spring.pulsar.admin.service-url[] property to a different value in the form `(http|https)://:`. + +If you need more control over the configuration, consider registering one or more `PulsarAdminBuilderCustomizer` beans. + + + +[[messaging.pulsar.admin.auth]] +=== Authentication + +When accessing a Pulsar cluster that requires authentication, the admin client requires the same security configuration as the regular Pulsar client. +You can use the aforementioned xref:messaging/pulsar.adoc#messaging.pulsar.connecting.auth[authentication configuration] by replacing `spring.pulsar.client.authentication` with `spring.pulsar.admin.authentication`. + +TIP: To create a topic on startup, add a bean of type `PulsarTopic`. +If the topic already exists, the bean is ignored. + + + +[[messaging.pulsar.sending]] +== Sending a Message + +Spring's `PulsarTemplate` is auto-configured, and you can use it to send messages, as shown in the following example: + +include-code::MyBean[] + +The `PulsarTemplate` relies on a `PulsarProducerFactory` to create the underlying Pulsar producer. +Spring Boot auto-configuration also provides this producer factory, which by default, caches the producers that it creates. +You can configure the producer factory and cache settings by specifying any of the `spring.pulsar.producer.\*` and `spring.pulsar.producer.cache.*` prefixed application properties. + +If you need more control over the producer factory configuration, consider registering one or more `ProducerBuilderCustomizer` beans. +These customizers are applied to all created producers. +You can also pass in a `ProducerBuilderCustomizer` when sending a message to only affect the current producer. + +If you need more control over the message being sent, you can pass in a `TypedMessageBuilderCustomizer` when sending a message. + + + +[[messaging.pulsar.sending-reactive]] +== Sending a Message Reactively + +When the Reactive auto-configuration is activated, Spring's `ReactivePulsarTemplate` is auto-configured, and you can use it to send messages, as shown in the following example: + +include-code::MyBean[] + +The `ReactivePulsarTemplate` relies on a `ReactivePulsarSenderFactory` to actually create the underlying sender. +Spring Boot auto-configuration also provides this sender factory, which by default, caches the producers that it creates. +You can configure the sender factory and cache settings by specifying any of the `spring.pulsar.producer.\*` and `spring.pulsar.producer.cache.*` prefixed application properties. + +If you need more control over the sender factory configuration, consider registering one or more `ReactiveMessageSenderBuilderCustomizer` beans. +These customizers are applied to all created senders. +You can also pass in a `ReactiveMessageSenderBuilderCustomizer` when sending a message to only affect the current sender. + +If you need more control over the message being sent, you can pass in a `MessageSpecBuilderCustomizer` when sending a message. + + + +[[messaging.pulsar.receiving]] +== Receiving a Message + +When the Apache Pulsar infrastructure is present, any bean can be annotated with `@PulsarListener` to create a listener endpoint. +The following component creates a listener endpoint on the `someTopic` topic: + +include-code::MyBean[] + +Spring Boot auto-configuration provides all the components necessary for `PulsarListener`, such as the `PulsarListenerContainerFactory` and the consumer factory it uses to construct the underlying Pulsar consumers. +You can configure these components by specifying any of the `spring.pulsar.listener.\*` and `spring.pulsar.consumer.*` prefixed application properties. + +If you need more control over the consumer factory configuration, consider registering one or more `ConsumerBuilderCustomizer` beans. +These customizers are applied to all consumers created by the factory, and therefore all `@PulsarListener` instances. +You can also customize a single listener by setting the `consumerCustomizer` attribute of the `@PulsarListener` annotation. + + + +[[messaging.pulsar.receiving-reactive]] +== Receiving a Message Reactively + +When the Apache Pulsar infrastructure is present and the Reactive auto-configuration is activated, any bean can be annotated with `@ReactivePulsarListener` to create a reactive listener endpoint. +The following component creates a reactive listener endpoint on the `someTopic` topic: + +include-code::MyBean[] + +Spring Boot auto-configuration provides all the components necessary for `ReactivePulsarListener`, such as the `ReactivePulsarListenerContainerFactory` and the consumer factory it uses to construct the underlying reactive Pulsar consumers. +You can configure these components by specifying any of the `spring.pulsar.listener.*` and `spring.pulsar.consumer.*` prefixed application properties. + +If you need more control over the consumer factory configuration, consider registering one or more `ReactiveMessageConsumerBuilderCustomizer` beans. +These customizers are applied to all consumers created by the factory, and therefore all `@ReactivePulsarListener` instances. +You can also customize a single listener by setting the `consumerCustomizer` attribute of the `@ReactivePulsarListener` annotation. + + + +[[messaging.pulsar.reading]] +== Reading a Message + +The Pulsar reader interface enables applications to manually manage cursors. +When you use a reader to connect to a topic you need to specify which message the reader begins reading from when it connects to a topic. + +When the Apache Pulsar infrastructure is present, any bean can be annotated with `@PulsarReader` to consume messages using a reader. +The following component creates a reader endpoint that starts reading messages from the beginning of the `someTopic` topic: + +include-code::MyBean[] + +The `@PulsarReader` relies on a `PulsarReaderFactory` to create the underlying Pulsar reader. +Spring Boot auto-configuration provides this reader factory which can be customized by setting any of the `spring.pulsar.reader.*` prefixed application properties. + +If you need more control over the reader factory configuration, consider registering one or more `ReaderBuilderCustomizer` beans. +These customizers are applied to all readers created by the factory, and therefore all `@PulsarReader` instances. +You can also customize a single listener by setting the `readerCustomizer` attribute of the `@PulsarReader` annotation. + + + +[[messaging.pulsar.reading-reactive]] +== Reading a Message Reactively + +When the Apache Pulsar infrastructure is present and the Reactive auto-configuration is activated, Spring's `ReactivePulsarReaderFactory` is provided, and you can use it to create a reader in order to read messages in a reactive fashion. +The following component creates a reader using the provided factory and reads a single message from 5 minutes ago from the `someTopic` topic: + +include-code::MyBean[] + +Spring Boot auto-configuration provides this reader factory which can be customized by setting any of the `spring.pulsar.reader.*` prefixed application properties. + +If you need more control over the reader factory configuration, consider passing in one or more `ReactiveMessageReaderBuilderCustomizer` instances when using the factory to create a reader. + +If you need more control over the reader factory configuration, consider registering one or more `ReactiveMessageReaderBuilderCustomizer` beans. +These customizers are applied to all created readers. +You can also pass one or more `ReactiveMessageReaderBuilderCustomizer` when creating a reader to only apply the customizations to the created reader. + +TIP: For more details on any of the above components and to discover other available features, see the Spring for Apache Pulsar {url-spring-pulsar-docs}[reference documentation]. + + + +[[messaging.pulsar.transactions]] +== Transaction Support + +Spring for Apache Pulsar supports transactions when using `PulsarTemplate` and `@PulsarListener`. + +NOTE: Transactions are not currently supported when using the reactive variants. + +Setting the configprop:spring.pulsar.transaction.enabled[] property to `true` will: + +* Configure a `PulsarTransactionManager` bean +* Enable transaction support for `PulsarTemplate` +* Enable transaction support for `@PulsarListener` methods + +The `transactional` attribute of `@PulsarListener` can be used to fine-tune when transactions should be used with listeners. + +For more control of the Spring for Apache Pulsar transaction features you should define your own `PulsarTemplate` and/or `ConcurrentPulsarListenerContainerFactory` beans. +You can also define a `PulsarAwareTransactionManager` bean if the default auto-configured `PulsarTransactionManager` is not suitable. + + + +[[messaging.pulsar.additional-properties]] +== Additional Pulsar Properties + +The properties supported by auto-configuration are shown in the xref:appendix:application-properties/index.adoc#appendix.application-properties.integration[Integration Properties] section of the Appendix. +Note that, for the most part, these properties (hyphenated or camelCase) map directly to the Apache Pulsar configuration properties. +See the Apache Pulsar documentation for details. + +Only a subset of the properties supported by Pulsar are available directly through the `PulsarProperties` class. +If you wish to tune the auto-configured components with additional properties that are not directly supported, you can use the customizer supported by each aforementioned component. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/rsocket.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/rsocket.adoc similarity index 79% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/rsocket.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/rsocket.adoc index a3a9c76c6062..fa07f585ab7c 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/rsocket.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/rsocket.adoc @@ -1,16 +1,18 @@ [[messaging.rsocket]] -== RSocket += RSocket + https://rsocket.io[RSocket] is a binary protocol for use on byte stream transports. It enables symmetric interaction models through async message passing over a single connection. The `spring-messaging` module of the Spring Framework provides support for RSocket requesters and responders, both on the client and on the server side. -See the {spring-framework-docs}/rsocket.html#rsocket-spring[RSocket section] of the Spring Framework reference for more details, including an overview of the RSocket protocol. +See the {url-spring-framework-docs}/rsocket.html#rsocket-spring[RSocket section] of the Spring Framework reference for more details, including an overview of the RSocket protocol. [[messaging.rsocket.strategies-auto-configuration]] -=== RSocket Strategies Auto-configuration +== RSocket Strategies Auto-configuration + Spring Boot auto-configures an `RSocketStrategies` bean that provides all the required infrastructure for encoding and decoding RSocket payloads. By default, the auto-configuration will try to configure the following (in order): @@ -18,7 +20,7 @@ By default, the auto-configuration will try to configure the following (in order . JSON codecs with Jackson The `spring-boot-starter-rsocket` starter provides both dependencies. -See the <> to know more about customization possibilities. +See the xref:features/json.adoc#features.json.jackson[Jackson support section] to know more about customization possibilities. Developers can customize the `RSocketStrategies` component by creating beans that implement the `RSocketStrategiesCustomizer` interface. Note that their `@Order` is important, as it determines the order of codecs. @@ -26,7 +28,8 @@ Note that their `@Order` is important, as it determines the order of codecs. [[messaging.rsocket.server-auto-configuration]] -=== RSocket server Auto-configuration +== RSocket Server Auto-configuration + Spring Boot provides RSocket server auto-configuration. The required dependencies are provided by the `spring-boot-starter-rsocket`. @@ -35,13 +38,13 @@ This depends on the type of application and its configuration. For WebFlux application (that is of type `WebApplicationType.REACTIVE`), the RSocket server will be plugged into the Web Server only if the following properties match: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - rsocket: - server: - mapping-path: "/rsocket" - transport: "websocket" +spring: + rsocket: + server: + mapping-path: "/rsocket" + transport: "websocket" ---- WARNING: Plugging RSocket into a web server is only supported with Reactor Netty, as RSocket itself is built with that library. @@ -49,18 +52,19 @@ WARNING: Plugging RSocket into a web server is only supported with Reactor Netty Alternatively, an RSocket TCP or websocket server is started as an independent, embedded server. Besides the dependency requirements, the only required configuration is to define a port for that server: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - rsocket: - server: - port: 9898 +spring: + rsocket: + server: + port: 9898 ---- [[messaging.rsocket.messaging]] -=== Spring Messaging RSocket support +== Spring Messaging RSocket Support + Spring Boot will auto-configure the Spring Messaging infrastructure for RSocket. This means that Spring Boot will create a `RSocketMessageHandler` bean that will handle RSocket requests to your application. @@ -68,7 +72,8 @@ This means that Spring Boot will create a `RSocketMessageHandler` bean that will [[messaging.rsocket.requester]] -=== Calling RSocket Services with RSocketRequester +== Calling RSocket Services with RSocketRequester + Once the `RSocket` channel is established between server and client, any party can send or receive requests to the other. As a server, you can get injected with an `RSocketRequester` instance on any handler method of an RSocket `@Controller`. @@ -80,4 +85,4 @@ This is done on purpose since this builder is stateful and you should not create The following code shows a typical example: -include::code:MyService[] +include-code::MyService[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/spring-integration.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/spring-integration.adoc new file mode 100644 index 000000000000..7f377259c30f --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/spring-integration.adoc @@ -0,0 +1,49 @@ +[[messaging.spring-integration]] += Spring Integration + +Spring Boot offers several conveniences for working with {url-spring-integration-site}[Spring Integration], including the `spring-boot-starter-integration` starter. +Spring Integration provides abstractions over messaging and also other transports such as HTTP, TCP, and others. +If Spring Integration is available on your classpath, it is initialized through the `@EnableIntegration` annotation. + +Spring Integration polling logic relies xref:features/task-execution-and-scheduling.adoc[on the auto-configured `TaskScheduler`]. +The default `PollerMetadata` (poll unbounded number of messages every second) can be customized with `spring.integration.poller.*` configuration properties. + +Spring Boot also configures some features that are triggered by the presence of additional Spring Integration modules. +If `spring-integration-jmx` is also on the classpath, message processing statistics are published over JMX. +If `spring-integration-jdbc` is available, the default database schema can be created on startup, as shown in the following line: + +[configprops,yaml] +---- +spring: + integration: + jdbc: + initialize-schema: "always" +---- + +If `spring-integration-rsocket` is available, developers can configure an RSocket server using `spring.rsocket.server.*` properties and let it use `IntegrationRSocketEndpoint` or `RSocketOutboundGateway` components to handle incoming RSocket messages. +This infrastructure can handle Spring Integration RSocket channel adapters and `@MessageMapping` handlers (given `spring.integration.rsocket.server.message-mapping-enabled` is configured). + +Spring Boot can also auto-configure an `ClientRSocketConnector` using configuration properties: + +[configprops,yaml] +---- +# Connecting to a RSocket server over TCP +spring: + integration: + rsocket: + client: + host: "example.org" + port: 9898 +---- + +[configprops,yaml] +---- +# Connecting to a RSocket Server over WebSocket +spring: + integration: + rsocket: + client: + uri: "ws://example.org" +---- + +See the {code-spring-boot-autoconfigure-src}/integration/IntegrationAutoConfiguration.java[`IntegrationAutoConfiguration`] and xref:api:java/org/springframework/boot/autoconfigure/integration/IntegrationProperties.html[`IntegrationProperties`] classes for more details. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/websockets.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/websockets.adoc new file mode 100644 index 000000000000..a6e7dd9af6af --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/websockets.adoc @@ -0,0 +1,17 @@ +[[messaging.websockets]] += WebSockets + +Spring Boot provides WebSockets auto-configuration for embedded Tomcat, Jetty, and Undertow. +If you deploy a war file to a standalone container, Spring Boot assumes that the container is responsible for the configuration of its WebSocket support. + +Spring Framework provides {url-spring-framework-docs}/web/websocket.html[rich WebSocket support] for MVC web applications that can be easily accessed through the `spring-boot-starter-websocket` module. + +WebSocket support is also available for {url-spring-framework-docs}/web/webflux-websocket.html[reactive web applications] and requires to include the WebSocket API alongside `spring-boot-starter-webflux`: + +[source,xml] +---- + + jakarta.websocket + jakarta.websocket-api + +---- diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/aot.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/aot.adoc new file mode 100644 index 000000000000..0372c305a4b2 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/aot.adoc @@ -0,0 +1,35 @@ +[[packaging.aot]] += Ahead-of-Time Processing With the JVM + +It's beneficial for the startup time to run your application using the AOT generated initialization code. +First, you need to ensure that the jar you are building includes AOT generated code. + +NOTE: CDS and AOT can be combined to further improve startup time. + +For Maven, this means that you should build with `-Pnative` to activate the `native` profile: + +[source,shell] +---- +$ mvn -Pnative package +---- + +For Gradle, you need to ensure that your build includes the `org.springframework.boot.aot` plugin. + +When the JAR has been built, run it with `spring.aot.enabled` system property set to `true`. For example: + +[source,shell] +---- +$ java -Dspring.aot.enabled=true -jar myapplication.jar + +........ Starting AOT-processed MyApplication ... +---- + +Beware that using the ahead-of-time processing has drawbacks. +It implies the following restrictions: + +* The classpath is fixed and fully defined at build time +* The beans defined in your application cannot change at runtime, meaning: +- The Spring `@Profile` annotation and profile-specific configuration xref:how-to:aot.adoc#howto.aot.conditions[have limitations]. +- Properties that change if a bean is created are not supported (for example, `@ConditionalOnProperty` and `.enable` properties). + +To learn more about ahead-of-time processing, please see the xref:packaging/native-image/introducing-graalvm-native-images.adoc#packaging.native-image.introducing-graalvm-native-images.understanding-aot-processing[] section. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/checkpoint-restore.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/checkpoint-restore.adoc new file mode 100644 index 000000000000..8ffcc62a83a4 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/checkpoint-restore.adoc @@ -0,0 +1,16 @@ +[[packaging.checkpoint-restore]] += Checkpoint and Restore With the JVM + +https://wiki.openjdk.org/display/crac/Main[Coordinated Restore at Checkpoint] (CRaC) is an OpenJDK project that defines a new Java API to allow you to checkpoint and restore an application on the HotSpot JVM. +It is based on https://github.com/checkpoint-restore/criu[CRIU], a project that implements checkpoint/restore functionality on Linux. + +The principle is the following: you start your application almost as usual but with a CRaC enabled version of the JDK like https://bell-sw.com/pages/downloads/?package=jdk-crac[BellSoft Liberica JDK with CRaC] or https://www.azul.com/downloads/?package=jdk-crac#zulu[Azul Zulu JDK with CRaC]. +Then at some point, potentially after some workloads that will warm up your JVM by executing all common code paths, you trigger a checkpoint using an API call, a `jcmd` command, an HTTP endpoint, or a different mechanism. + +A memory representation of the running JVM, including its warmness, is then serialized to disk, allowing a fast restoration at a later point, potentially on another machine with a similar operating system and CPU architecture. +The restored process retains all the capabilities of the HotSpot JVM, including further JIT optimizations at runtime. + +Based on the foundations provided by Spring Framework, Spring Boot provides support for checkpointing and restoring your application, and manages out-of-the-box the lifecycle of resources such as socket, files and thread pools https://github.com/spring-projects/spring-lifecycle-smoke-tests/blob/main/STATUS.adoc[on a limited scope]. +Additional lifecycle management is expected for other dependencies and potentially for the application code dealing with such resources. + +You can find more details about the two modes supported ("on demand checkpoint/restore of a running application" and "automatic checkpoint/restore at startup"), how to enable checkpoint and restore support and some guidelines in {url-spring-framework-docs}/integration/checkpoint-restore.html[the Spring Framework JVM Checkpoint Restore support documentation]. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/class-data-sharing.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/class-data-sharing.adoc new file mode 100644 index 000000000000..12e9cf6901e4 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/class-data-sharing.adoc @@ -0,0 +1,24 @@ +[[packaging.class-data-sharing]] += Class Data Sharing + +Class Data Sharing (CDS) is a https://docs.oracle.com/en/java/javase/17/vm/class-data-sharing.html[JVM feature] that can help reduce the startup time and memory footprint of Java applications. + +To use it, you should first perform a training run on your application in extracted form: + +[source,shell] +---- +$ java -Djarmode=tools -jar my-app.jar extract --destination application +$ cd application +$ java -XX:ArchiveClassesAtExit=application.jsa -Dspring.context.exit=onRefresh -jar my-app.jar +---- + +This creates an `application.jsa` file that can be reused as long as the application is not updated. + +To use the cache, you need to add an extra parameter when starting the application: + +[source,shell] +---- +$ java -XX:SharedArchiveFile=application.jsa -jar my-app.jar +---- + +NOTE: For more details about CDS, refer to the xref:how-to:class-data-sharing.adoc[CDS how-to guide] and the {url-spring-framework-docs}/integration/cds.html[Spring Framework reference documentation]. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/container-images/cloud-native-buildpacks.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/container-images/cloud-native-buildpacks.adoc new file mode 100644 index 000000000000..6b625ba2f21e --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/container-images/cloud-native-buildpacks.adoc @@ -0,0 +1,19 @@ +[[packaging.container-images.buildpacks]] += Cloud Native Buildpacks + +Docker images can be built directly from your Maven or Gradle plugin using https://buildpacks.io[Cloud Native Buildpacks]. +If you’ve ever used an application platform such as Cloud Foundry or Heroku then you’ve probably used a buildpack. +Buildpacks are the part of the platform that takes your application and converts it into something that the platform can actually run. +For example, Cloud Foundry’s Java buildpack will notice that you’re pushing a `.jar` file and automatically add a relevant JRE. + +With Cloud Native Buildpacks, you can create Docker compatible images that you can run anywhere. +Spring Boot includes buildpack support directly for both Maven and Gradle. +This means you can just type a single command and quickly get a sensible image into your locally running Docker daemon. + +See the individual plugin documentation on how to use buildpacks with xref:maven-plugin:build-image.adoc#build-image[Maven] and xref:gradle-plugin:packaging-oci-image.adoc[Gradle]. + +NOTE: The https://github.com/paketo-buildpacks/spring-boot[Paketo Spring Boot buildpack] supports the `layers.idx` file, so any xref:packaging/container-images/efficient-images.adoc#packaging.container-images.efficient-images.layering[layer customization] that is applied to it will be reflected in the image created by the buildpacks. + +NOTE: In order to achieve reproducible builds and container image caching, buildpacks can manipulate the application resources metadata (such as the file "last modified" information). +You should ensure that your application does not rely on that metadata at runtime. +Spring Boot can use that information when serving static resources, but this can be disabled with configprop:spring.web.resources.cache.use-last-modified[]. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/container-images/dockerfiles.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/container-images/dockerfiles.adoc new file mode 100644 index 000000000000..335f0d3845de --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/container-images/dockerfiles.adoc @@ -0,0 +1,64 @@ +[[packaging.container-images.dockerfiles]] += Dockerfiles + +While it is possible to convert a Spring Boot uber jar into a Docker image with just a few lines in the Dockerfile, using the xref:packaging/container-images/efficient-images.adoc#packaging.container-images.efficient-images.layering[layering feature] will result in an optimized image. +When you create a jar containing the layers index file, the `spring-boot-jarmode-tools` jar will be added as a dependency to your jar. +With this jar on the classpath, you can launch your application in a special mode which allows the bootstrap code to run something entirely different from your application, for example, something that extracts the layers. + +CAUTION: The `tools` mode can not be used with a xref:how-to:deployment/installing.adoc[fully executable Spring Boot archive] that includes a launch script. +Disable launch script configuration when building a jar file that is intended to be used with `layertools`. + +Here’s how you can launch your jar with a `tools` jar mode: + +[source,shell] +---- +$ java -Djarmode=tools -jar my-app.jar +---- + +This will provide the following output: + +[subs="verbatim"] +---- +Usage: + java -Djarmode=tools -jar my-app.jar + +Available commands: + extract Extract the contents from the jar + list-layers List layers from the jar that can be extracted + help Help about any command +---- + +The `extract` command can be used to easily split the application into layers to be added to the Dockerfile. +Here is an example of a Dockerfile using `jarmode`. + +[source,dockerfile] +---- +FROM bellsoft/liberica-runtime-container:jre-17-cds-slim-glibc as builder +WORKDIR /builder +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} application.jar +RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted + +FROM bellsoft/liberica-runtime-container:jre-17-cds-slim-glibc +WORKDIR /application +COPY --from=builder /builder/extracted/dependencies/ ./ +COPY --from=builder /builder/extracted/spring-boot-loader/ ./ +COPY --from=builder /builder/extracted/snapshot-dependencies/ ./ +COPY --from=builder /builder/extracted/application/ ./ +ENTRYPOINT ["java", "-jar", "application.jar"] +---- + +Assuming the above `Dockerfile` is in the current directory, your Docker image can be built with `docker build .`, or optionally specifying the path to your application jar, as shown in the following example: + +[source,shell] +---- +$ docker build --build-arg JAR_FILE=path/to/myapp.jar . +---- + +This is a multi-stage Dockerfile. +The builder stage extracts the directories that are needed later. +Each of the `COPY` commands relates to the layers extracted by the jarmode. + +Of course, a Dockerfile can be written without using the `jarmode`. +You can use some combination of `unzip` and `mv` to move things to the right layer but `jarmode` simplifies that. +Additionally, the layout created by the `jarmode` is CDS friendly out of the box. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/container-images/efficient-images.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/container-images/efficient-images.adoc new file mode 100644 index 000000000000..42e94c140b23 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/container-images/efficient-images.adoc @@ -0,0 +1,50 @@ +[[packaging.container-images.efficient-images]] += Efficient Container Images + +It is easily possible to package a Spring Boot uber jar as a Docker image. +However, there are various downsides to copying and running the uber jar as-is in the Docker image. +There’s always a certain amount of overhead when running an uber jar without unpacking it, and in a containerized environment this can be noticeable. +The other issue is that putting your application's code and all its dependencies in one layer in the Docker image is not optimal. +Since you probably recompile your code more often than you upgrade the version of Spring Boot you use, it’s often better to separate things a bit more. +If you put jar files in the layer before your application classes, Docker often only needs to change the very bottom layer and can pick others up from its cache. + + + +[[packaging.container-images.efficient-images.layering]] +== Layering Docker Images + +To make it easier to create optimized Docker images, Spring Boot supports adding a layer index file to the jar. +It provides a list of layers and the parts of the jar that should be contained within them. +The list of layers in the index is ordered based on the order in which the layers should be added to the Docker/OCI image. +Out-of-the-box, the following layers are supported: + +* `dependencies` (for regular released dependencies) +* `spring-boot-loader` (for everything under `org/springframework/boot/loader`) +* `snapshot-dependencies` (for snapshot dependencies) +* `application` (for application classes and resources) + +The following shows an example of a `layers.idx` file: + +[source,yaml] +---- +- "dependencies": + - BOOT-INF/lib/library1.jar + - BOOT-INF/lib/library2.jar +- "spring-boot-loader": + - org/springframework/boot/loader/launch/JarLauncher.class + - ... +- "snapshot-dependencies": + - BOOT-INF/lib/library3-SNAPSHOT.jar +- "application": + - META-INF/MANIFEST.MF + - BOOT-INF/classes/a/b/C.class +---- + +This layering is designed to separate code based on how likely it is to change between application builds. +Library code is less likely to change between builds, so it is placed in its own layers to allow tooling to re-use the layers from cache. +Application code is more likely to change between builds so it is isolated in a separate layer. + +Spring Boot also supports layering for war files with the help of a `layers.idx`. + +For Maven, see the xref:maven-plugin:packaging.adoc#packaging.layers[packaging layered jar or war section] for more details on adding a layer index to the archive. +For Gradle, see the xref:gradle-plugin:packaging.adoc#packaging-executable.configuring.layered-archives[packaging layered jar or war section] of the Gradle plugin documentation. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/container-images/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/container-images/index.adoc new file mode 100644 index 000000000000..606154e2279d --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/container-images/index.adoc @@ -0,0 +1,4 @@ +[[packaging.container-images]] += Container Images + +Spring Boot applications can be containerized xref:packaging/container-images/dockerfiles.adoc[using Dockerfiles], or by xref:packaging/container-images/cloud-native-buildpacks.adoc[using Cloud Native Buildpacks] to create optimized docker compatible container images that you can run anywhere. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/efficient.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/efficient.adoc new file mode 100644 index 000000000000..7cf034b685d1 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/efficient.adoc @@ -0,0 +1,38 @@ +[[packaging.efficient]] += Efficient Deployments + + + +[[packaging.efficient.unpacking]] +== Unpacking the Executable jar + +You can run your application using the executable jar, but loading the classes from nested jars has a small startup cost. +Depending on the size of the jar, running the application from an exploded structure is faster and recommended in production. +Certain PaaS implementations may also choose to extract archives before they run. +For example, Cloud Foundry operates this way. + +Spring Boot supports extracting your application to a directory using different layouts. +The default layout is the most efficient, and is xref:reference:packaging/class-data-sharing.adoc[CDS friendly]. + +In this layout, the libraries are extracted to a `lib/` folder, and the application jar +contains the application classes and a manifest which references the libraries in the `lib/` folder. + +To unpack the executable jar, run this command: + +[source,shell] +---- +$ java -Djarmode=tools -jar my-app.jar extract +---- + +And then in production, you can run the extracted jar: + +[source,shell] +---- +$ java -jar my-app/my-app.jar +---- + +After startup, you should not expect any differences in execution time between running an executable jar and running an extracted jar. + +TIP: Run `java -Djarmode=tools -jar my-app.jar help extract` to see all possible options. + + diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/index.adoc new file mode 100644 index 000000000000..260f87ca0fb6 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/index.adoc @@ -0,0 +1,7 @@ +[[packaging]] += Packaging Spring Boot Applications + +Spring Boot supports several technologies for optimizing applications for deployment, including xref:packaging/native-image/index.adoc[GraalVM native images], xref:packaging/class-data-sharing.adoc[Class Data Sharing], and xref:packaging/checkpoint-restore.adoc[Checkpoint and Restore]. + +Spring Boot applications can be packaged in Docker containers using techniques described in xref:packaging/container-images/index.adoc[]. + diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/native-image/advanced-topics.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/native-image/advanced-topics.adoc new file mode 100644 index 000000000000..d89da49cddd4 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/native-image/advanced-topics.adoc @@ -0,0 +1,196 @@ +[[packaging.native-image.advanced]] += Advanced Native Images Topics + + + +[[packaging.native-image.advanced.nested-configuration-properties]] +== Nested Configuration Properties + +Reflection hints are automatically created for configuration properties by the Spring ahead-of-time engine. +Nested configuration properties which are not inner classes, however, *must* be annotated with `@NestedConfigurationProperty`, otherwise they won't be detected and will not be bindable. + +include-code::MyProperties[] + +where `Nested` is: + +include-code::Nested[] + +The example above produces configuration properties for `my.properties.name` and `my.properties.nested.number`. +Without the `@NestedConfigurationProperty` annotation on the `nested` field, the `my.properties.nested.number` property would not be bindable in a native image. +You can also annotate the getter method. + +When using constructor binding, you have to annotate the field with `@NestedConfigurationProperty`: + +include-code::MyPropertiesCtor[] + +When using records, you have to annotate the parameter with `@NestedConfigurationProperty`: + +include-code::MyPropertiesRecord[] + +When using Kotlin, you need to annotate the parameter of a data class with `@NestedConfigurationProperty`: + +include-code::MyPropertiesKotlin[] + +NOTE: Please use public getters and setters in all cases, otherwise the properties will not be bindable. + + + +[[packaging.native-image.advanced.converting-executable-jars]] +== Converting a Spring Boot Executable Jar + +It is possible to convert a Spring Boot xref:specification:executable-jar/index.adoc[executable jar] into a native image as long as the jar contains the AOT generated assets. +This can be useful for a number of reasons, including: + +* You can keep your regular JVM pipeline and turn the JVM application into a native image on your CI/CD platform. +* As `native-image` https://github.com/oracle/graal/issues/407[does not support cross-compilation], you can keep an OS neutral deployment artifact which you convert later to different OS architectures. + +You can convert a Spring Boot executable jar into a native image using Cloud Native Buildpacks, or using the `native-image` tool that is shipped with GraalVM. + +NOTE: Your executable jar must include AOT generated assets such as generated classes and JSON hint files. + + + +[[packaging.native-image.advanced.converting-executable-jars.buildpacks]] +=== Using Buildpacks + +Spring Boot applications usually use Cloud Native Buildpacks through the Maven (`mvn spring-boot:build-image`) or Gradle (`gradle bootBuildImage`) integrations. +You can, however, also use https://buildpacks.io//docs/tools/pack/[`pack`] to turn an AOT processed Spring Boot executable jar into a native container image. + + +First, make sure that a Docker daemon is available (see https://docs.docker.com/installation/#installation[Get Docker] for more details). +https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user[Configure it to allow non-root user] if you are on Linux. + +You also need to install `pack` by following https://buildpacks.io//docs/tools/pack/#install[the installation guide on buildpacks.io]. + +Assuming an AOT processed Spring Boot executable jar built as `myproject-0.0.1-SNAPSHOT.jar` is in the `target` directory, run: + +[source,shell] +---- +$ pack build --builder paketobuildpacks/builder-jammy-tiny \ + --path target/myproject-0.0.1-SNAPSHOT.jar \ + --env 'BP_NATIVE_IMAGE=true' \ + my-application:0.0.1-SNAPSHOT +---- + +NOTE: You do not need to have a local GraalVM installation to generate an image in this way. + +Once `pack` has finished, you can launch the application using `docker run`: + +[source,shell] +---- +$ docker run --rm -p 8080:8080 docker.io/library/myproject:0.0.1-SNAPSHOT +---- + + + +[[packaging.native-image.advanced.converting-executable-jars.native-image]] +=== Using GraalVM native-image + +Another option to turn an AOT processed Spring Boot executable jar into a native executable is to use the GraalVM `native-image` tool. +For this to work, you'll need a GraalVM distribution on your machine. +You can either download it manually on the {url-download-liberica-nik}[Liberica Native Image Kit page] or you can use a download manager like SDKMAN!. + +Assuming an AOT processed Spring Boot executable jar built as `myproject-0.0.1-SNAPSHOT.jar` is in the `target` directory, run: + +[source,shell] +---- +$ rm -rf target/native +$ mkdir -p target/native +$ cd target/native +$ jar -xvf ../myproject-0.0.1-SNAPSHOT.jar +$ native-image -H:Name=myproject @META-INF/native-image/argfile -cp .:BOOT-INF/classes:`find BOOT-INF/lib | tr '\n' ':'` +$ mv myproject ../ +---- + +NOTE: These commands work on Linux or macOS machines, but you will need to adapt them for Windows. + +TIP: The `@META-INF/native-image/argfile` might not be packaged in your jar. +It is only included when reachability metadata overrides are needed. + +WARNING: The `native-image` `-cp` flag does not accept wildcards. +You need to ensure that all jars are listed (the command above uses `find` and `tr` to do this). + + + +[[packaging.native-image.advanced.using-the-tracing-agent]] +== Using the Tracing Agent + +The GraalVM native image {url-graal-docs-native-image}/metadata/AutomaticMetadataCollection[tracing agent] allows you to intercept reflection, resources or proxy usage on the JVM in order to generate the related hints. +Spring should generate most of these hints automatically, but the tracing agent can be used to quickly identify the missing entries. + +When using the agent to generate hints for a native image, there are a couple of approaches: + +* Launch the application directly and exercise it. +* Run application tests to exercise the application. + +The first option is interesting for identifying the missing hints when a library or a pattern is not recognized by Spring. + +The second option sounds more appealing for a repeatable setup, but by default the generated hints will include anything required by the test infrastructure. +Some of these will be unnecessary when the application runs for real. +To address this problem the agent supports an access-filter file that will cause certain data to be excluded from the generated output. + + + +[[packaging.native-image.advanced.using-the-tracing-agent.launch]] +=== Launch the Application Directly + +Use the following command to launch the application with the native image tracing agent attached: + +[source,shell,subs="verbatim,attributes"] +---- +$ java -Dspring.aot.enabled=true \ + -agentlib:native-image-agent=config-output-dir=/path/to/config-dir/ \ + -jar target/myproject-0.0.1-SNAPSHOT.jar +---- + +Now you can exercise the code paths you want to have hints for and then stop the application with `ctrl-c`. + +On application shutdown the native image tracing agent will write the hint files to the given config output directory. +You can either manually inspect these files, or use them as input to the native image build process. +To use them as input, copy them into the `src/main/resources/META-INF/native-image/` directory. +The next time you build the native image, GraalVM will take these files into consideration. + +There are more advanced options which can be set on the native image tracing agent, for example filtering the recorded hints by caller classes, etc. +For further reading, please see {url-graal-docs-native-image}/metadata/AutomaticMetadataCollection[the official documentation]. + + + +[[packaging.native-image.advanced.custom-hints]] +== Custom Hints + +If you need to provide your own hints for reflection, resources, serialization, proxy usage etc. you can use the `RuntimeHintsRegistrar` API. +Create a class that implements the `RuntimeHintsRegistrar` interface, and then make appropriate calls to the provided `RuntimeHints` instance: + +include-code::MyRuntimeHints[] + +You can then use `@ImportRuntimeHints` on any `@Configuration` class (for example your `@SpringBootApplication` annotated application class) to activate those hints. + +If you have classes which need binding (mostly needed when serializing or deserializing JSON), you can use {url-spring-framework-docs}/core/aot.html#aot.hints.register-reflection-for-binding[`@RegisterReflectionForBinding`] on any bean. +Most of the hints are automatically inferred, for example when accepting or returning data from a `@RestController` method. +But when you work with `WebClient`, `RestClient` or `RestTemplate` directly, you might need to use `@RegisterReflectionForBinding`. + + + +[[packaging.native-image.advanced.custom-hints.testing]] +=== Testing Custom Hints + +The `RuntimeHintsPredicates` API can be used to test your hints. +The API provides methods that build a `Predicate` that can be used to test a `RuntimeHints` instance. + +If you're using AssertJ, your test would look like this: + +include-code::MyRuntimeHintsTests[] + + + +[[packaging.native-image.advanced.known-limitations]] +== Known Limitations + +GraalVM native images are an evolving technology and not all libraries provide support. +The GraalVM community is helping by providing https://github.com/oracle/graalvm-reachability-metadata[reachability metadata] for projects that don't yet ship their own. +Spring itself doesn't contain hints for 3rd party libraries and instead relies on the reachability metadata project. + +If you encounter problems when generating native images for Spring Boot applications, please check the {url-github-wiki}/Spring-Boot-with-GraalVM[Spring Boot with GraalVM] page of the Spring Boot wiki. +You can also contribute issues to the https://github.com/spring-projects/spring-aot-smoke-tests[spring-aot-smoke-tests] project on GitHub which is used to confirm that common application types are working as expected. + +If you find a library which doesn't work with GraalVM, please raise an issue on the https://github.com/oracle/graalvm-reachability-metadata[reachability metadata project]. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/native-image/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/native-image/index.adoc new file mode 100644 index 000000000000..975a60bc5605 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/native-image/index.adoc @@ -0,0 +1,6 @@ +[[packaging.native-image]] += GraalVM Native Images + +https://www.graalvm.org/native-image/[GraalVM Native Images] are standalone executables that can be generated by processing compiled Java applications ahead-of-time. +Native Images generally have a smaller memory footprint and start faster than their JVM counterparts. + diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/native-image/introducing-graalvm-native-images.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/native-image/introducing-graalvm-native-images.adoc similarity index 78% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/native-image/introducing-graalvm-native-images.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/native-image/introducing-graalvm-native-images.adoc index bacc73c71301..f3d7d818b0d8 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/native-image/introducing-graalvm-native-images.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/native-image/introducing-graalvm-native-images.adoc @@ -1,5 +1,6 @@ -[[native-image.introducing-graalvm-native-images]] -== Introducing GraalVM Native Images +[[packaging.native-image.introducing-graalvm-native-images]] += Introducing GraalVM Native Images + GraalVM Native Images provide a new way to deploy and run Java applications. Compared to the Java Virtual Machine, native images can run with a smaller memory footprint and with much faster startup times. @@ -11,12 +12,13 @@ This ahead-of-time processing involves statically analyzing your application cod A GraalVM Native Image is a complete, platform-specific executable. You do not need to ship a Java Virtual Machine in order to run a native image. -TIP: If you just want to get started and experiment with GraalVM you can skip ahead to the "`<>`" section and return to this section later. +TIP: If you just want to get started and experiment with GraalVM you can jump to the xref:how-to:native-image/developing-your-first-application.adoc[] section and return to this section later. + +[[packaging.native-image.introducing-graalvm-native-images.key-differences-with-jvm-deployments]] +== Key Differences with JVM Deployments -[[native-image.introducing-graalvm-native-images.key-differences-with-jvm-deployments]] -=== Key Differences with JVM Deployments The fact that GraalVM Native Images are produced ahead-of-time means that there are some key differences between native and JVM based applications. The main differences are: @@ -27,25 +29,26 @@ The main differences are: * There is no lazy class loading, everything shipped in the executables will be loaded in memory on startup. * There are some limitations around some aspects of Java applications that are not fully supported. -On top of those differences, Spring uses a process called <>, which imposes further limitations. +On top of those differences, Spring uses a process called xref:packaging/native-image/introducing-graalvm-native-images.adoc#packaging.native-image.introducing-graalvm-native-images.understanding-aot-processing[Spring Ahead-of-Time processing], which imposes further limitations. Please make sure to read at least the beginning of the next section to learn about those. -TIP: The {graal-native-image-docs}/metadata/Compatibility/[Native Image Compatibility Guide] section of the GraalVM reference documentation provides more details about GraalVM limitations. +TIP: The {url-graal-docs-native-image}/metadata/Compatibility/[Native Image Compatibility Guide] section of the GraalVM reference documentation provides more details about GraalVM limitations. + +[[packaging.native-image.introducing-graalvm-native-images.understanding-aot-processing]] +== Understanding Spring Ahead-of-Time Processing -[[native-image.introducing-graalvm-native-images.understanding-aot-processing]] -=== Understanding Spring Ahead-of-Time Processing Typical Spring Boot applications are quite dynamic and configuration is performed at runtime. In fact, the concept of Spring Boot auto-configuration depends heavily on reacting to the state of the runtime in order to configure things correctly. Although it would be possible to tell GraalVM about these dynamic aspects of the application, doing so would undo most of the benefit of static analysis. So instead, when using Spring Boot to create native images, a closed-world is assumed and the dynamic aspects of the application are restricted. -A closed-world assumption implies, besides <>, the following restrictions: +A closed-world assumption implies, besides xref:packaging/native-image/introducing-graalvm-native-images.adoc#packaging.native-image.introducing-graalvm-native-images.key-differences-with-jvm-deployments[the limitations created by GraalVM itself], the following restrictions: * The beans defined in your application cannot change at runtime, meaning: -- The Spring `@Profile` annotation and profile-specific configuration <>. +- The Spring `@Profile` annotation and profile-specific configuration xref:how-to:aot.adoc#howto.aot.conditions[have limitations]. - Properties that change if a bean is created are not supported (for example, `@ConditionalOnProperty` and `.enable` properties). When these restrictions are in place, it becomes possible for Spring to perform ahead-of-time processing during build-time and generate additional assets that GraalVM can use. @@ -62,8 +65,9 @@ A Spring AOT processed application will typically generate: -[[native-image.introducing-graalvm-native-images.understanding-aot-processing.source-code-generation]] -==== Source Code Generation +[[packaging.native-image.introducing-graalvm-native-images.understanding-aot-processing.source-code-generation]] +=== Source Code Generation + Spring applications are composed of Spring Beans. Internally, Spring Framework uses two distinct concepts to manage beans. There are bean instances, which are the actual instances that have been created and can be injected into other beans. @@ -71,7 +75,7 @@ There are also bean definitions which are used to define attributes of a bean an If we take a typical `@Configuration` class: -include::code:MyConfiguration[] +include-code::MyConfiguration[] The bean definition is created by parsing the `@Configuration` class and finding the `@Bean` methods. In the above example, we're defining a `BeanDefinition` for a singleton bean named `myBean`. @@ -86,7 +90,7 @@ Once the bean definitions have been discovered, they are processed and converted The Spring AOT process would convert the configuration class above to code like this: -include::code:MyConfiguration__BeanDefinitions[] +include-code::MyConfiguration__BeanDefinitions[] NOTE: The exact code generated may differ depending on the nature of your bean definitions. @@ -96,7 +100,7 @@ There is a bean definition for the `myConfiguration` bean, and one for `myBean`. When a `myBean` instance is required, a `BeanInstanceSupplier` is called. This supplier will invoke the `myBean()` method on the `myConfiguration` bean. -NOTE: During Spring AOT processing your application is started up to the point that bean definitions are available. +NOTE: During Spring AOT processing, your application is started up to the point that bean definitions are available. Bean instances are not created during the AOT processing phase. Spring AOT will generate code like this for all your bean definitions. @@ -108,8 +112,9 @@ Generated source files can be found in `target/spring-aot/main/sources` when usi -[[native-image.introducing-graalvm-native-images.understanding-aot-processing.hint-file-generation]] -==== Hint File Generation +[[packaging.native-image.introducing-graalvm-native-images.understanding-aot-processing.hint-file-generation]] +=== Hint File Generation + In addition to generating source files, the Spring AOT engine will also generate hint files that are used by GraalVM. Hint files contain JSON data that describes how GraalVM should deal with things that it can't understand by directly inspecting the code. @@ -123,8 +128,9 @@ TIP: Generated hint files can be found in `target/spring-aot/main/resources` whe -[[native-image.introducing-graalvm-native-images.understanding-aot-processing.proxy-class-generation]] -==== Proxy Class Generation +[[packaging.native-image.introducing-graalvm-native-images.understanding-aot-processing.proxy-class-generation]] +=== Proxy Class Generation + Spring sometimes needs to generate proxy classes to enhance the code you've written with additional features. To do this, it uses the cglib library which directly generates bytecode. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/index.adoc new file mode 100644 index 000000000000..1ae13e3d1f5a --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/index.adoc @@ -0,0 +1,30 @@ +[[testing]] += Testing + +Spring Boot provides a number of utilities and annotations to help when testing your application. +Test support is provided by two modules: `spring-boot-test` contains core items, and `spring-boot-test-autoconfigure` supports auto-configuration for tests. + +Most developers use the `spring-boot-starter-test` starter, which imports both Spring Boot test modules as well as JUnit Jupiter, AssertJ, Hamcrest, and a number of other useful libraries. + +[TIP] +==== +If you have tests that use JUnit 4, JUnit 5's vintage engine can be used to run them. +To use the vintage engine, add a dependency on `junit-vintage-engine`, as shown in the following example: + +[source,xml] +---- + + org.junit.vintage + junit-vintage-engine + test + + + org.hamcrest + hamcrest-core + + + +---- +==== + +`hamcrest-core` is excluded in favor of `org.hamcrest:hamcrest` that is part of `spring-boot-starter-test`. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-applications.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-applications.adoc new file mode 100644 index 000000000000..c475cb816468 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-applications.adoc @@ -0,0 +1,14 @@ +[[testing.spring-applications]] += Testing Spring Applications + +One of the major advantages of dependency injection is that it should make your code easier to unit test. +You can instantiate objects by using the `new` operator without even involving Spring. +You can also use _mock objects_ instead of real dependencies. + +Often, you need to move beyond unit testing and start integration testing (with a Spring `ApplicationContext`). +It is useful to be able to perform integration testing without requiring deployment of your application or needing to connect to other infrastructure. + +The Spring Framework includes a dedicated test module for such integration testing. +You can declare a dependency directly to `org.springframework:spring-test` or use the `spring-boot-starter-test` starter to pull it in transitively. + +If you have not used the `spring-test` module before, you should start by reading the {url-spring-framework-docs}/testing.html[relevant section] of the Spring Framework reference documentation. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-boot-applications.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-boot-applications.adoc new file mode 100644 index 000000000000..a64acb95440c --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-boot-applications.adoc @@ -0,0 +1,902 @@ +[[testing.spring-boot-applications]] += Testing Spring Boot Applications + +A Spring Boot application is a Spring `ApplicationContext`, so nothing very special has to be done to test it beyond what you would normally do with a vanilla Spring context. + +NOTE: External properties, logging, and other features of Spring Boot are installed in the context by default only if you use `SpringApplication` to create it. + +Spring Boot provides a `@SpringBootTest` annotation, which can be used as an alternative to the standard `spring-test` `@ContextConfiguration` annotation when you need Spring Boot features. +The annotation works by xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.detecting-configuration[creating the `ApplicationContext` used in your tests through `SpringApplication`]. +In addition to `@SpringBootTest` a number of other annotations are also provided for xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-tests[testing more specific slices] of an application. + +TIP: If you are using JUnit 4, do not forget to also add `@RunWith(SpringRunner.class)` to your test, otherwise the annotations will be ignored. +If you are using JUnit 5, there is no need to add the equivalent `@ExtendWith(SpringExtension.class)` as `@SpringBootTest` and the other `@...Test` annotations are already annotated with it. + +By default, `@SpringBootTest` will not start a server. +You can use the `webEnvironment` attribute of `@SpringBootTest` to further refine how your tests run: + +* `MOCK`(Default) : Loads a web `ApplicationContext` and provides a mock web environment. +Embedded servers are not started when using this annotation. +If a web environment is not available on your classpath, this mode transparently falls back to creating a regular non-web `ApplicationContext`. +It can be used in conjunction with xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.with-mock-environment[`@AutoConfigureMockMvc` or `@AutoConfigureWebTestClient`] for mock-based testing of your web application. +* `RANDOM_PORT`: Loads a `WebServerApplicationContext` and provides a real web environment. +Embedded servers are started and listen on a random port. +* `DEFINED_PORT`: Loads a `WebServerApplicationContext` and provides a real web environment. +Embedded servers are started and listen on a defined port (from your `application.properties`) or on the default port of `8080`. +* `NONE`: Loads an `ApplicationContext` by using `SpringApplication` but does not provide _any_ web environment (mock or otherwise). + +NOTE: If your test is `@Transactional`, it rolls back the transaction at the end of each test method by default. +However, as using this arrangement with either `RANDOM_PORT` or `DEFINED_PORT` implicitly provides a real servlet environment, the HTTP client and server run in separate threads and, thus, in separate transactions. +Any transaction initiated on the server does not roll back in this case. + +NOTE: `@SpringBootTest` with `webEnvironment = WebEnvironment.RANDOM_PORT` will also start the management server on a separate random port if your application uses a different port for the management server. + + + +[[testing.spring-boot-applications.detecting-web-app-type]] +== Detecting Web Application Type + +If Spring MVC is available, a regular MVC-based application context is configured. +If you have only Spring WebFlux, we will detect that and configure a WebFlux-based application context instead. + +If both are present, Spring MVC takes precedence. +If you want to test a reactive web application in this scenario, you must set the configprop:spring.main.web-application-type[] property: + +include-code::MyWebFluxTests[] + + + +[[testing.spring-boot-applications.detecting-configuration]] +== Detecting Test Configuration + +If you are familiar with the Spring Test Framework, you may be used to using `@ContextConfiguration(classes=...)` in order to specify which Spring `@Configuration` to load. +Alternatively, you might have often used nested `@Configuration` classes within your test. + +When testing Spring Boot applications, this is often not required. +Spring Boot's `@*Test` annotations search for your primary configuration automatically whenever you do not explicitly define one. + +The search algorithm works up from the package that contains the test until it finds a class annotated with `@SpringBootApplication` or `@SpringBootConfiguration`. +As long as you xref:using/structuring-your-code.adoc[structured your code] in a sensible way, your main configuration is usually found. + +[NOTE] +==== +If you use a xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-tests[test annotation to test a more specific slice of your application], you should avoid adding configuration settings that are specific to a particular area on the xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.user-configuration-and-slicing[main method's application class]. + +The underlying component scan configuration of `@SpringBootApplication` defines exclude filters that are used to make sure slicing works as expected. +If you are using an explicit `@ComponentScan` directive on your `@SpringBootApplication`-annotated class, be aware that those filters will be disabled. +If you are using slicing, you should define them again. +==== + +If you want to customize the primary configuration, you can use a nested `@TestConfiguration` class. +Unlike a nested `@Configuration` class, which would be used instead of your application's primary configuration, a nested `@TestConfiguration` class is used in addition to your application's primary configuration. + +NOTE: Spring's test framework caches application contexts between tests. +Therefore, as long as your tests share the same configuration (no matter how it is discovered), the potentially time-consuming process of loading the context happens only once. + + + +[[testing.spring-boot-applications.using-main]] +== Using the Test Configuration Main Method + +Typically the test configuration discovered by `@SpringBootTest` will be your main `@SpringBootApplication`. +In most well structured applications, this configuration class will also include the `main` method used to launch the application. + +For example, the following is a very common code pattern for a typical Spring Boot application: + +include-code::typical/MyApplication[] + +In the example above, the `main` method doesn't do anything other than delegate to `SpringApplication.run`. +It is, however, possible to have a more complex `main` method that applies customizations before calling `SpringApplication.run`. + +For example, here is an application that changes the banner mode and sets additional profiles: + +include-code::custom/MyApplication[] + +Since customizations in the `main` method can affect the resulting `ApplicationContext`, it's possible that you might also want to use the `main` method to create the `ApplicationContext` used in your tests. +By default, `@SpringBootTest` will not call your `main` method, and instead the class itself is used directly to create the `ApplicationContext` + +If you want to change this behavior, you can change the `useMainMethod` attribute of `@SpringBootTest` to `UseMainMethod.ALWAYS` or `UseMainMethod.WHEN_AVAILABLE`. +When set to `ALWAYS`, the test will fail if no `main` method can be found. +When set to `WHEN_AVAILABLE` the `main` method will be used if it is available, otherwise the standard loading mechanism will be used. + +For example, the following test will invoke the `main` method of `MyApplication` in order to create the `ApplicationContext`. +If the main method sets additional profiles then those will be active when the `ApplicationContext` starts. + +include-code::always/MyApplicationTests[] + + + +[[testing.spring-boot-applications.excluding-configuration]] +== Excluding Test Configuration + +If your application uses component scanning (for example, if you use `@SpringBootApplication` or `@ComponentScan`), you may find top-level configuration classes that you created only for specific tests accidentally get picked up everywhere. + +As we xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.detecting-configuration[have seen earlier], `@TestConfiguration` can be used on an inner class of a test to customize the primary configuration. +`@TestConfiguration` can also be used on a top-level class. Doing so indicates that the class should not be picked up by scanning. +You can then import the class explicitly where it is required, as shown in the following example: + +include-code::MyTests[] + +NOTE: If you directly use `@ComponentScan` (that is, not through `@SpringBootApplication`) you need to register the `TypeExcludeFilter` with it. +See the xref:api:java/org/springframework/boot/context/TypeExcludeFilter.html[`TypeExcludeFilter`] API documentation for details. + +NOTE: An imported `@TestConfiguration` is processed earlier than an inner-class `@TestConfiguration` and an imported `@TestConfiguration` will be processed before any configuration found through component scanning. +Generally speaking, this difference in ordering has no noticeable effect but it is something to be aware of if you're relying on bean overriding. + + + +[[testing.spring-boot-applications.using-application-arguments]] +== Using Application Arguments + +If your application expects xref:features/spring-application.adoc#features.spring-application.application-arguments[arguments], you can +have `@SpringBootTest` inject them using the `args` attribute. + +include-code::MyApplicationArgumentTests[] + + + +[[testing.spring-boot-applications.with-mock-environment]] +== Testing With a Mock Environment + +By default, `@SpringBootTest` does not start the server but instead sets up a mock environment for testing web endpoints. + +With Spring MVC, we can query our web endpoints using {url-spring-framework-docs}/testing/mockmvc.html[`MockMvc`]. +Three integrations are available: + +* The regular {url-spring-framework-docs}/testing/mockmvc/hamcrest.html[`MockMvc`] that uses Hamcrest. +* {url-spring-framework-docs}/testing/mockmvc/assertj.html[`MockMvcTester`] that wraps `MockMvc` and uses AssertJ. +* {url-spring-framework-docs}/testing/webtestclient.html[`WebTestClient`] where `MockMvc` is plugged in as the server to handle requests with. + +The following example showcases the available integrations: + +include-code::MyMockMvcTests[] + +TIP: If you want to focus only on the web layer and not start a complete `ApplicationContext`, consider xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.spring-mvc-tests[using `@WebMvcTest` instead]. + +With Spring WebFlux endpoints, you can use {url-spring-framework-docs}/testing/webtestclient.html[`WebTestClient`] as shown in the following example: + +include-code::MyMockWebTestClientTests[] + +[TIP] +==== +Testing within a mocked environment is usually faster than running with a full servlet container. +However, since mocking occurs at the Spring MVC layer, code that relies on lower-level servlet container behavior cannot be directly tested with MockMvc. + +For example, Spring Boot's error handling is based on the "`error page`" support provided by the servlet container. +This means that, whilst you can test your MVC layer throws and handles exceptions as expected, you cannot directly test that a specific xref:web/servlet.adoc#web.servlet.spring-mvc.error-handling.error-pages[custom error page] is rendered. +If you need to test these lower-level concerns, you can start a fully running server as described in the next section. +==== + + + +[[testing.spring-boot-applications.with-running-server]] +== Testing With a Running Server + +If you need to start a full running server, we recommend that you use random ports. +If you use `@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)`, an available port is picked at random each time your test runs. + +The `@LocalServerPort` annotation can be used to xref:how-to:webserver.adoc#howto.webserver.discover-port[inject the actual port used] into your test. +For convenience, tests that need to make REST calls to the started server can additionally `@Autowire` a {url-spring-framework-docs}/testing/webtestclient.html[`WebTestClient`], which resolves relative links to the running server and comes with a dedicated API for verifying responses, as shown in the following example: + +include-code::MyRandomPortWebTestClientTests[] + +TIP: `WebTestClient` can also used with a xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.with-mock-environment[mock environment], removing the need for a running server, by annotating your test class with `@AutoConfigureWebTestClient`. + +This setup requires `spring-webflux` on the classpath. +If you can not or will not add webflux, Spring Boot also provides a `TestRestTemplate` facility: + +include-code::MyRandomPortTestRestTemplateTests[] + + + +[[testing.spring-boot-applications.customizing-web-test-client]] +== Customizing WebTestClient + +To customize the `WebTestClient` bean, configure a `WebTestClientBuilderCustomizer` bean. +Any such beans are called with the `WebTestClient.Builder` that is used to create the `WebTestClient`. + + + +[[testing.spring-boot-applications.jmx]] +== Using JMX + +As the test context framework caches context, JMX is disabled by default to prevent identical components to register on the same domain. +If such test needs access to an `MBeanServer`, consider marking it dirty as well: + +include-code::MyJmxTests[] + + + +[[testing.spring-boot-applications.observations]] +== Using Observations + +If you annotate xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-tests[a sliced test] with `@AutoConfigureObservability`, it auto-configures an `ObservationRegistry`. + + + +[[testing.spring-boot-applications.metrics]] +== Using Metrics + +Regardless of your classpath, meter registries, except the in-memory backed, are not auto-configured when using `@SpringBootTest`. + +If you need to export metrics to a different backend as part of an integration test, annotate it with `@AutoConfigureObservability`. + +If you annotate xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-tests[a sliced test] with `@AutoConfigureObservability`, it auto-configures an in-memory `MeterRegistry`. +Data exporting in sliced tests is not supported with the `@AutoConfigureObservability` annotation. + + + +[[testing.spring-boot-applications.tracing]] +== Using Tracing + +Regardless of your classpath, tracing components which are reporting data are not auto-configured when using `@SpringBootTest`. + +If you need those components as part of an integration test, annotate the test with `@AutoConfigureObservability`. + +If you have created your own reporting components (e.g. a custom `SpanExporter` or `SpanHandler`) and you don't want them to be active in tests, you can use the `@ConditionalOnEnabledTracing` annotation to disable them. + +If you annotate xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-tests[a sliced test] with `@AutoConfigureObservability`, it auto-configures a no-op `Tracer`. +Data exporting in sliced tests is not supported with the `@AutoConfigureObservability` annotation. + + + +[[testing.spring-boot-applications.mocking-beans]] +== Mocking and Spying Beans + +When running tests, it is sometimes necessary to mock certain components within your application context. +For example, you may have a facade over some remote service that is unavailable during development. +Mocking can also be useful when you want to simulate failures that might be hard to trigger in a real environment. + +Spring Framework includes a `@MockitoBean` annotation that can be used to define a Mockito mock for a bean inside your `ApplicationContext`. +Additionally, `@MockitoSpyBean` can be used to define a Mockito spy. +Learn more about these features in the {url-spring-framework-docs}/testing/annotations/integration-spring/annotation-mockitobean.html[Spring Framework documentation]. + + + +[[testing.spring-boot-applications.autoconfigured-tests]] +== Auto-configured Tests + +Spring Boot's auto-configuration system works well for applications but can sometimes be a little too much for tests. +It often helps to load only the parts of the configuration that are required to test a "`slice`" of your application. +For example, you might want to test that Spring MVC controllers are mapping URLs correctly, and you do not want to involve database calls in those tests, or you might want to test JPA entities, and you are not interested in the web layer when those tests run. + +The `spring-boot-test-autoconfigure` module includes a number of annotations that can be used to automatically configure such "`slices`". +Each of them works in a similar way, providing a `@...Test` annotation that loads the `ApplicationContext` and one or more `@AutoConfigure...` annotations that can be used to customize auto-configuration settings. + +NOTE: Each slice restricts component scan to appropriate components and loads a very restricted set of auto-configuration classes. +If you need to exclude one of them, most `@...Test` annotations provide an `excludeAutoConfiguration` attribute. +Alternatively, you can use `@ImportAutoConfiguration#exclude`. + +NOTE: Including multiple "`slices`" by using several `@...Test` annotations in one test is not supported. +If you need multiple "`slices`", pick one of the `@...Test` annotations and include the `@AutoConfigure...` annotations of the other "`slices`" by hand. + +TIP: It is also possible to use the `@AutoConfigure...` annotations with the standard `@SpringBootTest` annotation. +You can use this combination if you are not interested in "`slicing`" your application but you want some of the auto-configured test beans. + + + +[[testing.spring-boot-applications.json-tests]] +== Auto-configured JSON Tests + +To test that object JSON serialization and deserialization is working as expected, you can use the `@JsonTest` annotation. +`@JsonTest` auto-configures the available supported JSON mapper, which can be one of the following libraries: + +* Jackson `ObjectMapper`, any `@JsonComponent` beans and any Jackson ``Module``s +* `Gson` +* `Jsonb` + +TIP: A list of the auto-configurations that are enabled by `@JsonTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +If you need to configure elements of the auto-configuration, you can use the `@AutoConfigureJsonTesters` annotation. + +Spring Boot includes AssertJ-based helpers that work with the JSONAssert and JsonPath libraries to check that JSON appears as expected. +The `JacksonTester`, `GsonTester`, `JsonbTester`, and `BasicJsonTester` classes can be used for Jackson, Gson, Jsonb, and Strings respectively. +Any helper fields on the test class can be `@Autowired` when using `@JsonTest`. +The following example shows a test class for Jackson: + +include-code::MyJsonTests[] + +NOTE: JSON helper classes can also be used directly in standard unit tests. +To do so, call the `initFields` method of the helper in your `@Before` method if you do not use `@JsonTest`. + +If you use Spring Boot's AssertJ-based helpers to assert on a number value at a given JSON path, you might not be able to use `isEqualTo` depending on the type. +Instead, you can use AssertJ's `satisfies` to assert that the value matches the given condition. +For instance, the following example asserts that the actual number is a float value close to `0.15` within an offset of `0.01`. + +include-code::MyJsonAssertJTests[tag=*] + + + +[[testing.spring-boot-applications.spring-mvc-tests]] +== Auto-configured Spring MVC Tests + +To test whether Spring MVC controllers are working as expected, use the `@WebMvcTest` annotation. +`@WebMvcTest` auto-configures the Spring MVC infrastructure and limits scanned beans to `@Controller`, `@ControllerAdvice`, `@JsonComponent`, `Converter`, `GenericConverter`, `Filter`, `HandlerInterceptor`, `WebMvcConfigurer`, `WebMvcRegistrations`, and `HandlerMethodArgumentResolver`. +Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@WebMvcTest` annotation is used. +`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. + +TIP: A list of the auto-configuration settings that are enabled by `@WebMvcTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +TIP: If you need to register extra components, such as the Jackson `Module`, you can import additional configuration classes by using `@Import` on your test. + +Often, `@WebMvcTest` is limited to a single controller and is used in combination with `@MockBean` to provide mock implementations for required collaborators. + +`@WebMvcTest` also auto-configures `MockMvc`. +Mock MVC offers a powerful way to quickly test MVC controllers without needing to start a full HTTP server. +If AssertJ is available, the AssertJ support provided by `MockMvcTester` is auto-configured as well. + +TIP: You can also auto-configure `MockMvc` and `MockMvcTester` in a non-`@WebMvcTest` (such as `@SpringBootTest`) by annotating it with `@AutoConfigureMockMvc`. +The following example uses `MockMvcTester`: + +include-code::MyControllerTests[] + +TIP: If you need to configure elements of the auto-configuration (for example, when servlet filters should be applied) you can use attributes in the `@AutoConfigureMockMvc` annotation. + +If you use HtmlUnit and Selenium, auto-configuration also provides an HtmlUnit `WebClient` bean and/or a Selenium `WebDriver` bean. +The following example uses HtmlUnit: + +include-code::MyHtmlUnitTests[] + +NOTE: By default, Spring Boot puts `WebDriver` beans in a special "`scope`" to ensure that the driver exits after each test and that a new instance is injected. +If you do not want this behavior, you can add `@Scope("singleton")` to your `WebDriver` `@Bean` definition. + +WARNING: The `webDriver` scope created by Spring Boot will replace any user defined scope of the same name. +If you define your own `webDriver` scope you may find it stops working when you use `@WebMvcTest`. + +If you have Spring Security on the classpath, `@WebMvcTest` will also scan `WebSecurityConfigurer` beans. +Instead of disabling security completely for such tests, you can use Spring Security's test support. +More details on how to use Spring Security's `MockMvc` support can be found in this xref:how-to:testing.adoc#howto.testing.with-spring-security[] "`How-to Guides`" section. + +TIP: Sometimes writing Spring MVC tests is not enough; Spring Boot can help you run xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.with-running-server[full end-to-end tests with an actual server]. + + + +[[testing.spring-boot-applications.spring-webflux-tests]] +== Auto-configured Spring WebFlux Tests + +To test that {url-spring-framework-docs}/web-reactive.html[Spring WebFlux] controllers are working as expected, you can use the `@WebFluxTest` annotation. +`@WebFluxTest` auto-configures the Spring WebFlux infrastructure and limits scanned beans to `@Controller`, `@ControllerAdvice`, `@JsonComponent`, `Converter`, `GenericConverter`, `WebFilter`, and `WebFluxConfigurer`. +Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@WebFluxTest` annotation is used. +`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. + +TIP: A list of the auto-configurations that are enabled by `@WebFluxTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +TIP: If you need to register extra components, such as Jackson `Module`, you can import additional configuration classes using `@Import` on your test. + +Often, `@WebFluxTest` is limited to a single controller and used in combination with the `@MockBean` annotation to provide mock implementations for required collaborators. + +`@WebFluxTest` also auto-configures {url-spring-framework-docs}/testing/webtestclient.html[`WebTestClient`], which offers a powerful way to quickly test WebFlux controllers without needing to start a full HTTP server. + +TIP: You can also auto-configure `WebTestClient` in a non-`@WebFluxTest` (such as `@SpringBootTest`) by annotating it with `@AutoConfigureWebTestClient`. +The following example shows a class that uses both `@WebFluxTest` and a `WebTestClient`: + +include-code::MyControllerTests[] + +TIP: This setup is only supported by WebFlux applications as using `WebTestClient` in a mocked web application only works with WebFlux at the moment. + +NOTE: `@WebFluxTest` cannot detect routes registered through the functional web framework. +For testing `RouterFunction` beans in the context, consider importing your `RouterFunction` yourself by using `@Import` or by using `@SpringBootTest`. + +NOTE: `@WebFluxTest` cannot detect custom security configuration registered as a `@Bean` of type `SecurityWebFilterChain`. +To include that in your test, you will need to import the configuration that registers the bean by using `@Import` or by using `@SpringBootTest`. + +TIP: Sometimes writing Spring WebFlux tests is not enough; Spring Boot can help you run xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.with-running-server[full end-to-end tests with an actual server]. + + + +[[testing.spring-boot-applications.spring-graphql-tests]] +== Auto-configured Spring GraphQL Tests + +Spring GraphQL offers a dedicated testing support module; you'll need to add it to your project: + +.Maven +[source,xml] +---- + + + org.springframework.graphql + spring-graphql-test + test + + + + org.springframework.boot + spring-boot-starter-webflux + test + + +---- + +.Gradle +[source,gradle] +---- +dependencies { + testImplementation("org.springframework.graphql:spring-graphql-test") + // Unless already present in the implementation configuration + testImplementation("org.springframework.boot:spring-boot-starter-webflux") +} +---- + +This testing module ships the {url-spring-graphql-docs}/#testing-graphqltester[GraphQlTester]. +The tester is heavily used in test, so be sure to become familiar with using it. +There are `GraphQlTester` variants and Spring Boot will auto-configure them depending on the type of tests: + +* the `ExecutionGraphQlServiceTester` performs tests on the server side, without a client nor a transport +* the `HttpGraphQlTester` performs tests with a client that connects to a server, with or without a live server + +Spring Boot helps you to test your {url-spring-graphql-docs}/#controllers[Spring GraphQL Controllers] with the `@GraphQlTest` annotation. +`@GraphQlTest` auto-configures the Spring GraphQL infrastructure, without any transport nor server being involved. +This limits scanned beans to `@Controller`, `RuntimeWiringConfigurer`, `JsonComponent`, `Converter`, `GenericConverter`, `DataFetcherExceptionResolver`, `Instrumentation` and `GraphQlSourceBuilderCustomizer`. +Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@GraphQlTest` annotation is used. +`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. + +TIP: A list of the auto-configurations that are enabled by `@GraphQlTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +Often, `@GraphQlTest` is limited to a set of controllers and used in combination with the `@MockBean` annotation to provide mock implementations for required collaborators. + +include-code::GreetingControllerTests[] + +`@SpringBootTest` tests are full integration tests and involve the entire application. +When using a random or defined port, a live server is configured and an `HttpGraphQlTester` bean is contributed automatically so you can use it to test your server. +When a MOCK environment is configured, you can also request an `HttpGraphQlTester` bean by annotating your test class with `@AutoConfigureHttpGraphQlTester`: + +include-code::GraphQlIntegrationTests[] + + + +[[testing.spring-boot-applications.autoconfigured-spring-data-cassandra]] +== Auto-configured Data Cassandra Tests + +You can use `@DataCassandraTest` to test Cassandra applications. +By default, it configures a `CassandraTemplate`, scans for `@Table` classes, and configures Spring Data Cassandra repositories. +Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@DataCassandraTest` annotation is used. +`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. +(For more about using Cassandra with Spring Boot, see xref:data/nosql.adoc#data.nosql.cassandra[].) + +TIP: A list of the auto-configuration settings that are enabled by `@DataCassandraTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +The following example shows a typical setup for using Cassandra tests in Spring Boot: + +include-code::MyDataCassandraTests[] + + + +[[testing.spring-boot-applications.autoconfigured-spring-data-couchbase]] +== Auto-configured Data Couchbase Tests + +You can use `@DataCouchbaseTest` to test Couchbase applications. +By default, it configures a `CouchbaseTemplate` or `ReactiveCouchbaseTemplate`, scans for `@Document` classes, and configures Spring Data Couchbase repositories. +Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@DataCouchbaseTest` annotation is used. +`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. +(For more about using Couchbase with Spring Boot, see xref:data/nosql.adoc#data.nosql.couchbase[], earlier in this chapter.) + +TIP: A list of the auto-configuration settings that are enabled by `@DataCouchbaseTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +The following example shows a typical setup for using Couchbase tests in Spring Boot: + +include-code::MyDataCouchbaseTests[] + + + +[[testing.spring-boot-applications.autoconfigured-spring-data-elasticsearch]] +== Auto-configured Data Elasticsearch Tests + +You can use `@DataElasticsearchTest` to test Elasticsearch applications. +By default, it configures an `ElasticsearchRestTemplate`, scans for `@Document` classes, and configures Spring Data Elasticsearch repositories. +Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@DataElasticsearchTest` annotation is used. +`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. +(For more about using Elasticsearch with Spring Boot, see xref:data/nosql.adoc#data.nosql.elasticsearch[], earlier in this chapter.) + +TIP: A list of the auto-configuration settings that are enabled by `@DataElasticsearchTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +The following example shows a typical setup for using Elasticsearch tests in Spring Boot: + +include-code::MyDataElasticsearchTests[] + + + +[[testing.spring-boot-applications.autoconfigured-spring-data-jpa]] +== Auto-configured Data JPA Tests + +You can use the `@DataJpaTest` annotation to test JPA applications. +By default, it scans for `@Entity` classes and configures Spring Data JPA repositories. +If an embedded database is available on the classpath, it configures one as well. +SQL queries are logged by default by setting the `spring.jpa.show-sql` property to `true`. +This can be disabled using the `showSql` attribute of the annotation. + +Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@DataJpaTest` annotation is used. +`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. + +TIP: A list of the auto-configuration settings that are enabled by `@DataJpaTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +By default, data JPA tests are transactional and roll back at the end of each test. +See the {url-spring-framework-docs}/testing/testcontext-framework/tx.html#testcontext-tx-enabling-transactions[relevant section] in the Spring Framework Reference Documentation for more details. +If that is not what you want, you can disable transaction management for a test or for the whole class as follows: + +include-code::MyNonTransactionalTests[] + +Data JPA tests may also inject a xref:api:java/org/springframework/boot/test/autoconfigure/orm/jpa/TestEntityManager.html[`TestEntityManager`] bean, which provides an alternative to the standard JPA `EntityManager` that is specifically designed for tests. + +TIP: `TestEntityManager` can also be auto-configured to any of your Spring-based test class by adding `@AutoConfigureTestEntityManager`. +When doing so, make sure that your test is running in a transaction, for instance by adding `@Transactional` on your test class or method. + +A `JdbcTemplate` is also available if you need that. +The following example shows the `@DataJpaTest` annotation in use: + +include-code::withoutdb/MyRepositoryTests[] + +In-memory embedded databases generally work well for tests, since they are fast and do not require any installation. +If, however, you prefer to run tests against a real database you can use the `@AutoConfigureTestDatabase` annotation, as shown in the following example: + +include-code::withdb/MyRepositoryTests[] + + + +[[testing.spring-boot-applications.autoconfigured-jdbc]] +== Auto-configured JDBC Tests + +`@JdbcTest` is similar to `@DataJpaTest` but is for tests that only require a `DataSource` and do not use Spring Data JDBC. +By default, it configures an in-memory embedded database and a `JdbcTemplate`. +Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@JdbcTest` annotation is used. +`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. + +TIP: A list of the auto-configurations that are enabled by `@JdbcTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +By default, JDBC tests are transactional and roll back at the end of each test. +See the {url-spring-framework-docs}/testing/testcontext-framework/tx.html#testcontext-tx-enabling-transactions[relevant section] in the Spring Framework Reference Documentation for more details. +If that is not what you want, you can disable transaction management for a test or for the whole class, as follows: + +include-code::MyTransactionalTests[] + +If you prefer your test to run against a real database, you can use the `@AutoConfigureTestDatabase` annotation in the same way as for `@DataJpaTest`. +(See xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-spring-data-jpa[].) + + + +[[testing.spring-boot-applications.autoconfigured-spring-data-jdbc]] +== Auto-configured Data JDBC Tests + +`@DataJdbcTest` is similar to `@JdbcTest` but is for tests that use Spring Data JDBC repositories. +By default, it configures an in-memory embedded database, a `JdbcTemplate`, and Spring Data JDBC repositories. +Only `AbstractJdbcConfiguration` subclasses are scanned when the `@DataJdbcTest` annotation is used, regular `@Component` and `@ConfigurationProperties` beans are not scanned. +`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. + +TIP: A list of the auto-configurations that are enabled by `@DataJdbcTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +By default, Data JDBC tests are transactional and roll back at the end of each test. +See the {url-spring-framework-docs}/testing/testcontext-framework/tx.html#testcontext-tx-enabling-transactions[relevant section] in the Spring Framework Reference Documentation for more details. +If that is not what you want, you can disable transaction management for a test or for the whole test class as xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-jdbc[shown in the JDBC example]. + +If you prefer your test to run against a real database, you can use the `@AutoConfigureTestDatabase` annotation in the same way as for `@DataJpaTest`. +(See xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-spring-data-jpa[].) + + + +[[testing.spring-boot-applications.autoconfigured-spring-data-r2dbc]] +== Auto-configured Data R2DBC Tests + +`@DataR2dbcTest` is similar to `@DataJdbcTest` but is for tests that use Spring Data R2DBC repositories. +By default, it configures an in-memory embedded database, an `R2dbcEntityTemplate`, and Spring Data R2DBC repositories. +Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@DataR2dbcTest` annotation is used. +`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. + +TIP: A list of the auto-configurations that are enabled by `@DataR2dbcTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +By default, Data R2DBC tests are not transactional. + +If you prefer your test to run against a real database, you can use the `@AutoConfigureTestDatabase` annotation in the same way as for `@DataJpaTest`. +(See xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-spring-data-jpa[].) + + + +[[testing.spring-boot-applications.autoconfigured-jooq]] +== Auto-configured jOOQ Tests + +You can use `@JooqTest` in a similar fashion as `@JdbcTest` but for jOOQ-related tests. +As jOOQ relies heavily on a Java-based schema that corresponds with the database schema, the existing `DataSource` is used. +If you want to replace it with an in-memory database, you can use `@AutoConfigureTestDatabase` to override those settings. +(For more about using jOOQ with Spring Boot, see xref:data/sql.adoc#data.sql.jooq[].) +Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@JooqTest` annotation is used. +`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. + +TIP: A list of the auto-configurations that are enabled by `@JooqTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +`@JooqTest` configures a `DSLContext`. +The following example shows the `@JooqTest` annotation in use: + +include-code::MyJooqTests[] + +JOOQ tests are transactional and roll back at the end of each test by default. +If that is not what you want, you can disable transaction management for a test or for the whole test class as xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-jdbc[shown in the JDBC example]. + + + +[[testing.spring-boot-applications.autoconfigured-spring-data-mongodb]] +== Auto-configured Data MongoDB Tests + +You can use `@DataMongoTest` to test MongoDB applications. +By default, it configures a `MongoTemplate`, scans for `@Document` classes, and configures Spring Data MongoDB repositories. +Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@DataMongoTest` annotation is used. +`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. +(For more about using MongoDB with Spring Boot, see xref:data/nosql.adoc#data.nosql.mongodb[].) + +TIP: A list of the auto-configuration settings that are enabled by `@DataMongoTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +The following class shows the `@DataMongoTest` annotation in use: + +include-code::MyDataMongoDbTests[] + + + +[[testing.spring-boot-applications.autoconfigured-spring-data-neo4j]] +== Auto-configured Data Neo4j Tests + +You can use `@DataNeo4jTest` to test Neo4j applications. +By default, it scans for `@Node` classes, and configures Spring Data Neo4j repositories. +Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@DataNeo4jTest` annotation is used. +`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. +(For more about using Neo4J with Spring Boot, see xref:data/nosql.adoc#data.nosql.neo4j[].) + +TIP: A list of the auto-configuration settings that are enabled by `@DataNeo4jTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +The following example shows a typical setup for using Neo4J tests in Spring Boot: + +include-code::propagation/MyDataNeo4jTests[] + +By default, Data Neo4j tests are transactional and roll back at the end of each test. +See the {url-spring-framework-docs}/testing/testcontext-framework/tx.html#testcontext-tx-enabling-transactions[relevant section] in the Spring Framework Reference Documentation for more details. +If that is not what you want, you can disable transaction management for a test or for the whole class, as follows: + +include-code::nopropagation/MyDataNeo4jTests[] + +NOTE: Transactional tests are not supported with reactive access. +If you are using this style, you must configure `@DataNeo4jTest` tests as described above. + + + +[[testing.spring-boot-applications.autoconfigured-spring-data-redis]] +== Auto-configured Data Redis Tests + +You can use `@DataRedisTest` to test Redis applications. +By default, it scans for `@RedisHash` classes and configures Spring Data Redis repositories. +Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@DataRedisTest` annotation is used. +`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. +(For more about using Redis with Spring Boot, see xref:data/nosql.adoc#data.nosql.redis[].) + +TIP: A list of the auto-configuration settings that are enabled by `@DataRedisTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +The following example shows the `@DataRedisTest` annotation in use: + +include-code::MyDataRedisTests[] + + + +[[testing.spring-boot-applications.autoconfigured-spring-data-ldap]] +== Auto-configured Data LDAP Tests + +You can use `@DataLdapTest` to test LDAP applications. +By default, it configures an in-memory embedded LDAP (if available), configures an `LdapTemplate`, scans for `@Entry` classes, and configures Spring Data LDAP repositories. +Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@DataLdapTest` annotation is used. +`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. +(For more about using LDAP with Spring Boot, see xref:data/nosql.adoc#data.nosql.ldap[].) + +TIP: A list of the auto-configuration settings that are enabled by `@DataLdapTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +The following example shows the `@DataLdapTest` annotation in use: + +include-code::inmemory/MyDataLdapTests[] + +In-memory embedded LDAP generally works well for tests, since it is fast and does not require any developer installation. +If, however, you prefer to run tests against a real LDAP server, you should exclude the embedded LDAP auto-configuration, as shown in the following example: + +include-code::server/MyDataLdapTests[] + + + +[[testing.spring-boot-applications.autoconfigured-rest-client]] +== Auto-configured REST Clients + +You can use the `@RestClientTest` annotation to test REST clients. +By default, it auto-configures Jackson, GSON, and Jsonb support, configures a `RestTemplateBuilder` and a `RestClient.Builder`, and adds support for `MockRestServiceServer`. +Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@RestClientTest` annotation is used. +`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. + +TIP: A list of the auto-configuration settings that are enabled by `@RestClientTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +The specific beans that you want to test should be specified by using the `value` or `components` attribute of `@RestClientTest`. + +When using a `RestTemplateBuilder` in the beans under test and `RestTemplateBuilder.rootUri(String rootUri)` has been called when building the `RestTemplate`, then the root URI should be omitted from the `MockRestServiceServer` expectations as shown in the following example: + +include-code::MyRestTemplateServiceTests[] + +When using a `RestClient.Builder` in the beans under test, or when using a `RestTemplateBuilder` without calling `rootUri(String rootURI)`, the full URI must be used in the `MockRestServiceServer` expectations as shown in the following example: + +include-code::MyRestClientServiceTests[] + + + +[[testing.spring-boot-applications.autoconfigured-spring-restdocs]] +== Auto-configured Spring REST Docs Tests + +You can use the `@AutoConfigureRestDocs` annotation to use {url-spring-restdocs-site}[Spring REST Docs] in your tests with Mock MVC, REST Assured, or WebTestClient. +It removes the need for the JUnit extension in Spring REST Docs. + +`@AutoConfigureRestDocs` can be used to override the default output directory (`target/generated-snippets` if you are using Maven or `build/generated-snippets` if you are using Gradle). +It can also be used to configure the host, scheme, and port that appears in any documented URIs. + + + +[[testing.spring-boot-applications.autoconfigured-spring-restdocs.with-mock-mvc]] +=== Auto-configured Spring REST Docs Tests With Mock MVC + +`@AutoConfigureRestDocs` customizes the `MockMvc` bean to use Spring REST Docs when testing servlet-based web applications. +You can inject it by using `@Autowired` and use it in your tests as you normally would when using Mock MVC and Spring REST Docs, as shown in the following example: + +include-code::hamcrest/MyUserDocumentationTests[] + +If you prefer to use the AssertJ integration, `MockMvcTester` is available as well, as shown in the following example: + +include-code::assertj/MyUserDocumentationTests[] + +Both reuses the same `MockMvc` instance behind the scenes so any configuration to it applies to both. + +If you require more control over Spring REST Docs configuration than offered by the attributes of `@AutoConfigureRestDocs`, you can use a `RestDocsMockMvcConfigurationCustomizer` bean, as shown in the following example: + +include-code::MyRestDocsConfiguration[] + +If you want to make use of Spring REST Docs support for a parameterized output directory, you can create a `RestDocumentationResultHandler` bean. +The auto-configuration calls `alwaysDo` with this result handler, thereby causing each `MockMvc` call to automatically generate the default snippets. +The following example shows a `RestDocumentationResultHandler` being defined: + +include-code::MyResultHandlerConfiguration[] + + + +[[testing.spring-boot-applications.autoconfigured-spring-restdocs.with-web-test-client]] +=== Auto-configured Spring REST Docs Tests With WebTestClient + +`@AutoConfigureRestDocs` can also be used with `WebTestClient` when testing reactive web applications. +You can inject it by using `@Autowired` and use it in your tests as you normally would when using `@WebFluxTest` and Spring REST Docs, as shown in the following example: + +include-code::MyUsersDocumentationTests[] + +If you require more control over Spring REST Docs configuration than offered by the attributes of `@AutoConfigureRestDocs`, you can use a `RestDocsWebTestClientConfigurationCustomizer` bean, as shown in the following example: + +include-code::MyRestDocsConfiguration[] + +If you want to make use of Spring REST Docs support for a parameterized output directory, you can use a `WebTestClientBuilderCustomizer` to configure a consumer for every entity exchange result. +The following example shows such a `WebTestClientBuilderCustomizer` being defined: + +include-code::MyWebTestClientBuilderCustomizerConfiguration[] + + + +[[testing.spring-boot-applications.autoconfigured-spring-restdocs.with-rest-assured]] +=== Auto-configured Spring REST Docs Tests With REST Assured + +`@AutoConfigureRestDocs` makes a `RequestSpecification` bean, preconfigured to use Spring REST Docs, available to your tests. +You can inject it by using `@Autowired` and use it in your tests as you normally would when using REST Assured and Spring REST Docs, as shown in the following example: + +include-code::MyUserDocumentationTests[] + +If you require more control over Spring REST Docs configuration than offered by the attributes of `@AutoConfigureRestDocs`, a `RestDocsRestAssuredConfigurationCustomizer` bean can be used, as shown in the following example: + +include-code::MyRestDocsConfiguration[] + + + +[[testing.spring-boot-applications.autoconfigured-webservices]] +== Auto-configured Spring Web Services Tests + + + +[[testing.spring-boot-applications.autoconfigured-webservices.client]] +=== Auto-configured Spring Web Services Client Tests + +You can use `@WebServiceClientTest` to test applications that call web services using the Spring Web Services project. +By default, it configures a mock `WebServiceServer` bean and automatically customizes your `WebServiceTemplateBuilder`. +(For more about using Web Services with Spring Boot, see xref:io/webservices.adoc[].) + + +TIP: A list of the auto-configuration settings that are enabled by `@WebServiceClientTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +The following example shows the `@WebServiceClientTest` annotation in use: + +include-code::MyWebServiceClientTests[] + + + +[[testing.spring-boot-applications.autoconfigured-webservices.server]] +=== Auto-configured Spring Web Services Server Tests + +You can use `@WebServiceServerTest` to test applications that implement web services using the Spring Web Services project. +By default, it configures a `MockWebServiceClient` bean that can be used to call your web service endpoints. +(For more about using Web Services with Spring Boot, see xref:io/webservices.adoc[].) + + +TIP: A list of the auto-configuration settings that are enabled by `@WebServiceServerTest` can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +The following example shows the `@WebServiceServerTest` annotation in use: + +include-code::MyWebServiceServerTests[] + + + +[[testing.spring-boot-applications.additional-autoconfiguration-and-slicing]] +== Additional Auto-configuration and Slicing + +Each slice provides one or more `@AutoConfigure...` annotations that namely defines the auto-configurations that should be included as part of a slice. +Additional auto-configurations can be added on a test-by-test basis by creating a custom `@AutoConfigure...` annotation or by adding `@ImportAutoConfiguration` to the test as shown in the following example: + +include-code::MyJdbcTests[] + +NOTE: Make sure to not use the regular `@Import` annotation to import auto-configurations as they are handled in a specific way by Spring Boot. + +Alternatively, additional auto-configurations can be added for any use of a slice annotation by registering them in a file stored in `META-INF/spring` as shown in the following example: + +.META-INF/spring/org.springframework.boot.test.autoconfigure.jdbc.JdbcTest.imports +[source] +---- +com.example.IntegrationAutoConfiguration +---- + +In this example, the `com.example.IntegrationAutoConfiguration` is enabled on every test annotated with `@JdbcTest`. + +TIP: You can use comments with `#` in this file. + +TIP: A slice or `@AutoConfigure...` annotation can be customized this way as long as it is meta-annotated with `@ImportAutoConfiguration`. + + + +[[testing.spring-boot-applications.user-configuration-and-slicing]] +== User Configuration and Slicing + +If you xref:using/structuring-your-code.adoc[structure your code] in a sensible way, your `@SpringBootApplication` class is xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.detecting-configuration[used by default] as the configuration of your tests. + +It then becomes important not to litter the application's main class with configuration settings that are specific to a particular area of its functionality. + +Assume that you are using Spring Data MongoDB, you rely on the auto-configuration for it, and you have enabled auditing. +You could define your `@SpringBootApplication` as follows: + +include-code::MyApplication[] + +Because this class is the source configuration for the test, any slice test actually tries to enable Mongo auditing, which is definitely not what you want to do. +A recommended approach is to move that area-specific configuration to a separate `@Configuration` class at the same level as your application, as shown in the following example: + +include-code::MyMongoConfiguration[] + +NOTE: Depending on the complexity of your application, you may either have a single `@Configuration` class for your customizations or one class per domain area. +The latter approach lets you enable it in one of your tests, if necessary, with the `@Import` annotation. +See xref:how-to:testing.adoc#howto.testing.slice-tests[this how-to section] for more details on when you might want to enable specific `@Configuration` classes for slice tests. + +Test slices exclude `@Configuration` classes from scanning. +For example, for a `@WebMvcTest`, the following configuration will not include the given `WebMvcConfigurer` bean in the application context loaded by the test slice: + +include-code::MyWebConfiguration[] + +The configuration below will, however, cause the custom `WebMvcConfigurer` to be loaded by the test slice. + +include-code::MyWebMvcConfigurer[] + +Another source of confusion is classpath scanning. +Assume that, while you structured your code in a sensible way, you need to scan an additional package. +Your application may resemble the following code: + +include-code::scan/MyApplication[] + +Doing so effectively overrides the default component scan directive with the side effect of scanning those two packages regardless of the slice that you chose. +For instance, a `@DataJpaTest` seems to suddenly scan components and user configurations of your application. +Again, moving the custom directive to a separate class is a good way to fix this issue. + +TIP: If this is not an option for you, you can create a `@SpringBootConfiguration` somewhere in the hierarchy of your test so that it is used instead. +Alternatively, you can specify a source for your test, which disables the behavior of finding a default one. + + + +[[testing.spring-boot-applications.spock]] +== Using Spock to Test Spring Boot Applications + +Spock 2.2 or later can be used to test a Spring Boot application. +To do so, add a dependency on a `-groovy-4.0` version of Spock's `spock-spring` module to your application's build. +`spock-spring` integrates Spring's test framework into Spock. +See https://spockframework.org/spock/docs/2.2-M1/modules.html#_spring_module[the documentation for Spock's Spring module] for further details. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-scope-dependencies.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-scope-dependencies.adoc new file mode 100644 index 000000000000..02d3e2424d1d --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-scope-dependencies.adoc @@ -0,0 +1,16 @@ +[[testing.test-scope-dependencies]] += Test Scope Dependencies + +The `spring-boot-starter-test` starter (in the `test` `scope`) contains the following provided libraries: + +* https://junit.org/junit5/[JUnit 5]: The de-facto standard for unit testing Java applications. +* {url-spring-framework-docs}/testing/integration.html[Spring Test] & Spring Boot Test: Utilities and integration test support for Spring Boot applications. +* https://assertj.github.io/doc/[AssertJ]: A fluent assertion library. +* https://github.com/hamcrest/JavaHamcrest[Hamcrest]: A library of matcher objects (also known as constraints or predicates). +* https://site.mockito.org/[Mockito]: A Java mocking framework. +* https://github.com/skyscreamer/JSONassert[JSONassert]: An assertion library for JSON. +* https://github.com/jayway/JsonPath[JsonPath]: XPath for JSON. +* https://github.com/awaitility/awaitility[Awaitility]: A library for testing asynchronous systems. + +We generally find these common libraries to be useful when writing tests. +If these libraries do not suit your needs, you can add additional test dependencies of your own. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-utilities.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-utilities.adoc new file mode 100644 index 000000000000..9b82a7567eec --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-utilities.adoc @@ -0,0 +1,69 @@ +[[testing.utilities]] += Test Utilities + +A few test utility classes that are generally useful when testing your application are packaged as part of `spring-boot`. + + + +[[testing.utilities.config-data-application-context-initializer]] +== ConfigDataApplicationContextInitializer + +`ConfigDataApplicationContextInitializer` is an `ApplicationContextInitializer` that you can apply to your tests to load Spring Boot `application.properties` files. +You can use it when you do not need the full set of features provided by `@SpringBootTest`, as shown in the following example: + +include-code::MyConfigFileTests[] + +NOTE: Using `ConfigDataApplicationContextInitializer` alone does not provide support for `@Value("${...}")` injection. +Its only job is to ensure that `application.properties` files are loaded into Spring's `Environment`. +For `@Value` support, you need to either additionally configure a `PropertySourcesPlaceholderConfigurer` or use `@SpringBootTest`, which auto-configures one for you. + + + +[[testing.utilities.test-property-values]] +== TestPropertyValues + +`TestPropertyValues` lets you quickly add properties to a `ConfigurableEnvironment` or `ConfigurableApplicationContext`. +You can call it with `key=value` strings, as follows: + +include-code::MyEnvironmentTests[] + + + +[[testing.utilities.output-capture]] +== OutputCapture + +`OutputCapture` is a JUnit `Extension` that you can use to capture `System.out` and `System.err` output. +To use it, add `@ExtendWith(OutputCaptureExtension.class)` and inject `CapturedOutput` as an argument to your test class constructor or test method as follows: + +include-code::MyOutputCaptureTests[] + + + +[[testing.utilities.test-rest-template]] +== TestRestTemplate + +`TestRestTemplate` is a convenience alternative to Spring's `RestTemplate` that is useful in integration tests. +You can get a vanilla template or one that sends Basic HTTP authentication (with a username and password). +In either case, the template is fault tolerant. +This means that it behaves in a test-friendly way by not throwing exceptions on 4xx and 5xx errors. +Instead, such errors can be detected through the returned `ResponseEntity` and its status code. + +TIP: Spring Framework 5.0 provides a new `WebTestClient` that works for xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.spring-webflux-tests[WebFlux integration tests] and both xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.with-running-server[WebFlux and MVC end-to-end testing]. +It provides a fluent API for assertions, unlike `TestRestTemplate`. + +It is recommended, but not mandatory, to use the Apache HTTP Client (version 5.1 or better). +If you have that on your classpath, the `TestRestTemplate` responds by configuring the client appropriately. +If you do use Apache's HTTP client, some additional test-friendly features are enabled: + +* Redirects are not followed (so you can assert the response location). +* Cookies are ignored (so the template is stateless). + +`TestRestTemplate` can be instantiated directly in your integration tests, as shown in the following example: + +include-code::MyTests[] + +Alternatively, if you use the `@SpringBootTest` annotation with `WebEnvironment.RANDOM_PORT` or `WebEnvironment.DEFINED_PORT`, you can inject a fully configured `TestRestTemplate` and start using it. +If necessary, additional customizations can be applied through the `RestTemplateBuilder` bean. +Any URLs that do not specify a host and port automatically connect to the embedded server, as shown in the following example: + +include-code::MySpringBootTests[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc new file mode 100644 index 000000000000..0ee1dcab5aaf --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc @@ -0,0 +1,130 @@ +[[testing.testcontainers]] += Testcontainers + +The https://www.testcontainers.org/[Testcontainers] library provides a way to manage services running inside Docker containers. +It integrates with JUnit, allowing you to write a test class that can start up a container before any of the tests run. +Testcontainers is especially useful for writing integration tests that talk to a real backend service such as MySQL, MongoDB, Cassandra and others. + +Testcontainers can be used in a Spring Boot test as follows: + +include-code::vanilla/MyIntegrationTests[] + +This will start up a docker container running Neo4j (if Docker is running locally) before any of the tests are run. +In most cases, you will need to configure the application to connect to the service running in the container. + + + +[[testing.testcontainers.service-connections]] +== Service Connections + +A service connection is a connection to any remote service. +Spring Boot's auto-configuration can consume the details of a service connection and use them to establish a connection to a remote service. +When doing so, the connection details take precedence over any connection-related configuration properties. + +When using Testcontainers, connection details can be automatically created for a service running in a container by annotating the container field in the test class. + +include-code::MyIntegrationTests[] + +Thanks to `@ServiceConnection`, the above configuration allows Neo4j-related beans in the application to communicate with Neo4j running inside the Testcontainers-managed Docker container. +This is done by automatically defining a `Neo4jConnectionDetails` bean which is then used by the Neo4j auto-configuration, overriding any connection-related configuration properties. + +NOTE: You'll need to add the `spring-boot-testcontainers` module as a test dependency in order to use service connections with Testcontainers. + +Service connection annotations are processed by `ContainerConnectionDetailsFactory` classes registered with `spring.factories`. +A `ContainerConnectionDetailsFactory` can create a `ConnectionDetails` bean based on a specific `Container` subclass, or the Docker image name. + +The following service connection factories are provided in the `spring-boot-testcontainers` jar: + +|=== +| Connection Details | Matched on + +| `ActiveMQConnectionDetails` +| Containers named "symptoma/activemq" or `ActiveMQContainer` + +| `ArtemisConnectionDetails` +| Containers of type `ArtemisContainer` + +| `CassandraConnectionDetails` +| Containers of type `CassandraContainer` + +| `CouchbaseConnectionDetails` +| Containers of type `CouchbaseContainer` + +| `ElasticsearchConnectionDetails` +| Containers of type `ElasticsearchContainer` + +| `FlywayConnectionDetails` +| Containers of type `JdbcDatabaseContainer` + +| `JdbcConnectionDetails` +| Containers of type `JdbcDatabaseContainer` + +| `KafkaConnectionDetails` +| Containers of type `org.testcontainers.containers.KafkaContainer`, `org.testcontainers.kafka.KafkaContainer` or `RedpandaContainer` + +| `LiquibaseConnectionDetails` +| Containers of type `JdbcDatabaseContainer` + +| `MongoConnectionDetails` +| Containers of type `MongoDBContainer` + +| `Neo4jConnectionDetails` +| Containers of type `Neo4jContainer` + +| `OtlpMetricsConnectionDetails` +| Containers named "otel/opentelemetry-collector-contrib" + +| `OtlpTracingConnectionDetails` +| Containers named "otel/opentelemetry-collector-contrib" + +| `PulsarConnectionDetails` +| Containers of type `PulsarContainer` + +| `R2dbcConnectionDetails` +| Containers of type `MariaDBContainer`, `MSSQLServerContainer`, `MySQLContainer`, `OracleContainer`, or `PostgreSQLContainer` + +| `RabbitConnectionDetails` +| Containers of type `RabbitMQContainer` + +| `RedisConnectionDetails` +| Containers named "redis", "redis/redis-stack" or "redis/redis-stack-server" + +| `ZipkinConnectionDetails` +| Containers named "openzipkin/zipkin" +|=== + +[TIP] +==== +By default all applicable connection details beans will be created for a given `Container`. +For example, a `PostgreSQLContainer` will create both `JdbcConnectionDetails` and `R2dbcConnectionDetails`. + +If you want to create only a subset of the applicable types, you can use the `type` attribute of `@ServiceConnection`. +==== + +By default `Container.getDockerImageName().getRepository()` is used to obtain the name used to find connection details. +The repository portion of the Docker image name ignores any registry and the version. +This works as long as Spring Boot is able to get the instance of the `Container`, which is the case when using a `static` field like in the example above. + +If you're using a `@Bean` method, Spring Boot won't call the bean method to get the Docker image name, because this would cause eager initialization issues. +Instead, the return type of the bean method is used to find out which connection detail should be used. +This works as long as you're using typed containers, e.g. `Neo4jContainer` or `RabbitMQContainer`. +This stops working if you're using `GenericContainer`, e.g. with Redis, as shown in the following example: + +include-code::MyRedisConfiguration[] + +Spring Boot can't tell from `GenericContainer` which container image is used, so the `name` attribute from `@ServiceConnection` must be used to provide that hint. + +You can also use the `name` attribute of `@ServiceConnection` to override which connection detail will be used, for example when using custom images. +If you are using the Docker image `registry.mycompany.com/mirror/myredis`, you'd use `@ServiceConnection(name="redis")` to ensure `RedisConnectionDetails` are created. + + + +[[testing.testcontainers.dynamic-properties]] +== Dynamic Properties + +A slightly more verbose but also more flexible alternative to service connections is `@DynamicPropertySource`. +A static `@DynamicPropertySource` method allows adding dynamic property values to the Spring Environment. + +include-code::MyIntegrationTests[] + +The above configuration allows Neo4j-related beans in the application to communicate with Neo4j running inside the Testcontainers-managed Docker container. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/auto-configuration.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/auto-configuration.adoc similarity index 94% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/auto-configuration.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/auto-configuration.adoc index b3c7375f2364..98e9ad6e230a 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/auto-configuration.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/auto-configuration.adoc @@ -1,5 +1,6 @@ [[using.auto-configuration]] -== Auto-configuration += Auto-configuration + Spring Boot auto-configuration attempts to automatically configure your Spring application based on the jar dependencies that you have added. For example, if `HSQLDB` is on your classpath, and you have not manually configured any database connection beans, then Spring Boot auto-configures an in-memory database. @@ -11,7 +12,8 @@ We generally recommend that you add one or the other to your primary `@Configura [[using.auto-configuration.replacing]] -=== Gradually Replacing Auto-configuration +== Gradually Replacing Auto-configuration + Auto-configuration is non-invasive. At any point, you can start to define your own configuration to replace specific parts of the auto-configuration. For example, if you add your own `DataSource` bean, the default embedded database support backs away. @@ -22,10 +24,11 @@ Doing so enables debug logs for a selection of core loggers and logs a condition [[using.auto-configuration.disabling-specific]] -=== Disabling Specific Auto-configuration Classes +== Disabling Specific Auto-configuration Classes + If you find that specific auto-configuration classes that you do not want are being applied, you can use the exclude attribute of `@SpringBootApplication` to disable them, as shown in the following example: -include::code:MyApplication[] +include-code::MyApplication[] If the class is not on the classpath, you can use the `excludeName` attribute of the annotation and specify the fully qualified name instead. If you prefer to use `@EnableAutoConfiguration` rather than `@SpringBootApplication`, `exclude` and `excludeName` are also available. @@ -37,8 +40,10 @@ NOTE: Even though auto-configuration classes are `public`, the only aspect of th The actual contents of those classes, such as nested configuration classes or bean methods are for internal use only and we do not recommend using those directly. + [[using.auto-configuration.packages]] -=== Auto-configuration Packages +== Auto-configuration Packages + Auto-configuration packages are the packages that various auto-configured features look in by default when scanning for things such as entities and Spring Data repositories. The `@EnableAutoConfiguration` annotation (either directly or through its presence on `@SpringBootApplication`) determines the default auto-configuration package. Additional packages can be configured using the `@AutoConfigurationPackage` annotation. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/build-systems.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/build-systems.adoc new file mode 100644 index 000000000000..f1cc5ee8b4ca --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/build-systems.adoc @@ -0,0 +1,151 @@ +[[using.build-systems]] += Build Systems + +It is strongly recommended that you choose a build system that supports xref:using/build-systems.adoc#using.build-systems.dependency-management[dependency management] and that can consume artifacts published to the Maven Central repository. +We would recommend that you choose Maven or Gradle. +It is possible to get Spring Boot to work with other build systems (Ant, for example), but they are not particularly well supported. + + + +[[using.build-systems.dependency-management]] +== Dependency Management + +Each release of Spring Boot provides a curated list of dependencies that it supports. +In practice, you do not need to provide a version for any of these dependencies in your build configuration, as Spring Boot manages that for you. +When you upgrade Spring Boot itself, these dependencies are upgraded as well in a consistent way. + +NOTE: You can still specify a version and override Spring Boot's recommendations if you need to do so. + +The curated list contains all the Spring modules that you can use with Spring Boot as well as a refined list of third party libraries. +The list is available as a standard Bills of Materials (`spring-boot-dependencies`) that can be used with both xref:using/build-systems.adoc#using.build-systems.maven[Maven] and xref:using/build-systems.adoc#using.build-systems.gradle[Gradle]. + +WARNING: Each release of Spring Boot is associated with a base version of the Spring Framework. +We **highly** recommend that you do not specify its version. + + + +[[using.build-systems.maven]] +== Maven + +To learn about using Spring Boot with Maven, see the documentation for Spring Boot's Maven plugin: + +* xref:maven-plugin:index.adoc[Reference] +* xref:maven-plugin:api/java/index.html[API] + + + +[[using.build-systems.gradle]] +== Gradle + +To learn about using Spring Boot with Gradle, see the documentation for Spring Boot's Gradle plugin: + +* xref:gradle-plugin:index.adoc[Reference] +* xref:gradle-plugin:api/java/index.html[API] + + + +[[using.build-systems.ant]] +== Ant + +It is possible to build a Spring Boot project using Apache Ant+Ivy. +The `spring-boot-antlib` "`AntLib`" module is also available to help Ant create executable jars. + +To declare dependencies, a typical `ivy.xml` file looks something like the following example: + +[source,xml] +---- + + + + + + + + + + +---- + +A typical `build.xml` looks like the following example: + +[source,xml,subs="verbatim,attributes"] +---- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +---- + +TIP: If you do not want to use the `spring-boot-antlib` module, see the xref:how-to:build.adoc#howto.build.build-an-executable-archive-with-ant-without-using-spring-boot-antlib[] section of "`How-to Guides`". + + + +[[using.build-systems.starters]] +== Starters + +Starters are a set of convenient dependency descriptors that you can include in your application. +You get a one-stop shop for all the Spring and related technologies that you need without having to hunt through sample code and copy-paste loads of dependency descriptors. +For example, if you want to get started using Spring and JPA for database access, include the `spring-boot-starter-data-jpa` dependency in your project. + +The starters contain a lot of the dependencies that you need to get a project up and running quickly and with a consistent, supported set of managed transitive dependencies. + +.What is in a name +**** +All **official** starters follow a similar naming pattern; `+spring-boot-starter-*+`, where `+*+` is a particular type of application. +This naming structure is intended to help when you need to find a starter. +The Maven integration in many IDEs lets you search dependencies by name. +For example, with the appropriate Eclipse or Spring Tools plugin installed, you can press `ctrl-space` in the POM editor and type "`spring-boot-starter`" for a complete list. + +As explained in the xref:features/developing-auto-configuration.adoc#features.developing-auto-configuration.custom-starter[] section, third party starters should not start with `spring-boot`, as it is reserved for official Spring Boot artifacts. +Rather, a third-party starter typically starts with the name of the project. +For example, a third-party starter project called `thirdpartyproject` would typically be named `thirdpartyproject-spring-boot-starter`. +**** + +The following application starters are provided by Spring Boot under the `org.springframework.boot` group: + +.Spring Boot application starters +include::ROOT:partial$starters/application-starters.adoc[] + +In addition to the application starters, the following starters can be used to add xref:how-to:actuator.adoc[production ready] features: + +.Spring Boot production starters +include::ROOT:partial$starters/production-starters.adoc[] + +Finally, Spring Boot also includes the following starters that can be used if you want to exclude or swap specific technical facets: + +.Spring Boot technical starters +include::ROOT:partial$starters/technical-starters.adoc[] + +To learn how to swap technical facets, please see the how-to documentation for xref:how-to:webserver.adoc#howto.webserver.use-another[swapping web server] and xref:how-to:logging.adoc#howto.logging.log4j[logging system]. + +TIP: For a list of additional community contributed starters, see the {code-spring-boot-latest}/spring-boot-project/spring-boot-starters/README.adoc[README file] in the `spring-boot-starters` module on GitHub. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/configuration-classes.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/configuration-classes.adoc similarity index 91% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/configuration-classes.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/configuration-classes.adoc index b73af3b0fb32..f99fcfae101e 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/configuration-classes.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/configuration-classes.adoc @@ -1,5 +1,6 @@ [[using.configuration-classes]] -== Configuration Classes += Configuration Classes + Spring Boot favors Java-based configuration. Although it is possible to use `SpringApplication` with XML sources, we generally recommend that your primary source be a single `@Configuration` class. Usually the class that defines the `main` method is a good candidate as the primary `@Configuration`. @@ -11,7 +12,8 @@ Searching for `+Enable*+` annotations can be a good starting point. [[using.configuration-classes.importing-additional-configuration]] -=== Importing Additional Configuration Classes +== Importing Additional Configuration Classes + You need not put all your `@Configuration` into a single class. The `@Import` annotation can be used to import additional configuration classes. Alternatively, you can use `@ComponentScan` to automatically pick up all Spring components, including `@Configuration` classes. @@ -19,6 +21,7 @@ Alternatively, you can use `@ComponentScan` to automatically pick up all Spring [[using.configuration-classes.importing-xml-configuration]] -=== Importing XML Configuration +== Importing XML Configuration + If you absolutely must use XML based configuration, we recommend that you still start with a `@Configuration` class. You can then use an `@ImportResource` annotation to load XML configuration files. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/devtools.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/devtools.adoc similarity index 80% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/devtools.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/devtools.adoc index ad4356ae0040..bfb548e25032 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/devtools.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/devtools.adoc @@ -1,31 +1,32 @@ [[using.devtools]] -== Developer Tools += Developer Tools + Spring Boot includes an additional set of tools that can make the application development experience a little more pleasant. The `spring-boot-devtools` module can be included in any project to provide additional development-time features. To include devtools support, add the module dependency to your build, as shown in the following listings for Maven and Gradle: .Maven -[source,xml,indent=0,subs="verbatim"] +[source,xml] ---- - - - org.springframework.boot - spring-boot-devtools - true - - + + + org.springframework.boot + spring-boot-devtools + true + + ---- .Gradle -[source,gradle,indent=0,subs="verbatim"] +[source,gradle] ---- - dependencies { - developmentOnly("org.springframework.boot:spring-boot-devtools") - } +dependencies { + developmentOnly("org.springframework.boot:spring-boot-devtools") +} ---- CAUTION: Devtools might cause classloading issues, in particular in multi-module projects. -<> explains how to diagnose and solve them. +xref:using/devtools.adoc#using.devtools.diagnosing-classloading-issues[] explains how to diagnose and solve them. NOTE: Developer tools are automatically disabled when running a fully packaged application. If your application is launched from `java -jar` or if it is started from a special classloader, then it is considered a "`production application`". @@ -37,27 +38,29 @@ To disable devtools, exclude the dependency or set the `-Dspring.devtools.restar TIP: Flagging the dependency as optional in Maven or using the `developmentOnly` configuration in Gradle (as shown above) prevents devtools from being transitively applied to other modules that use your project. TIP: Repackaged archives do not contain devtools by default. -If you want to use a <>, you need to include it. +If you want to use a xref:using/devtools.adoc#using.devtools.remote-applications[certain remote devtools feature], you need to include it. When using the Maven plugin, set the `excludeDevtools` property to `false`. -When using the Gradle plugin, {spring-boot-gradle-plugin-docs}#packaging-executable-configuring-including-development-only-dependencies[configure the task's classpath to include the `developmentOnly` configuration]. +When using the Gradle plugin, xref:gradle-plugin:packaging.adoc#packaging-executable.configuring.including-development-only-dependencies[configure the task's classpath to include the `developmentOnly` configuration]. [[using.devtools.diagnosing-classloading-issues]] -=== Diagnosing Classloading Issues -As described in the <> section, restart functionality is implemented by using two classloaders. +== Diagnosing Classloading Issues + +As described in the xref:#using.devtools.restart.restart-vs-reload[] section, restart functionality is implemented by using two classloaders. For most applications, this approach works well. However, it can sometimes cause classloading issues, in particular in multi-module projects. -To diagnose whether the classloading issues are indeed caused by devtools and its two classloaders, <>. -If this solves your problems, <> to include your entire project. +To diagnose whether the classloading issues are indeed caused by devtools and its two classloaders, xref:using/devtools.adoc#using.devtools.restart.disable[try disabling restart]. +If this solves your problems, xref:using/devtools.adoc#using.devtools.restart.customizing-the-classload[customize the restart classloader] to include your entire project. [[using.devtools.property-defaults]] -=== Property Defaults +== Property Defaults + Several of the libraries supported by Spring Boot use caches to improve performance. -For example, <> cache compiled templates to avoid repeatedly parsing template files. +For example, xref:web/servlet.adoc#web.servlet.spring-mvc.template-engines[template engines] cache compiled templates to avoid repeatedly parsing template files. Also, Spring MVC can add HTTP caching headers to responses when serving static resources. While caching is very beneficial in production, it can be counter-productive during development, preventing you from seeing the changes you just made in your application. @@ -69,7 +72,7 @@ Rather than needing to set these properties manually, the `spring-boot-devtools` The following table lists all the properties that are applied: -include::devtools-property-defaults.adoc[] +include::ROOT:partial$propertydefaults/devtools-property-defaults.adoc[] NOTE: If you do not want property defaults to be applied you can set configprop:spring.devtools.add-properties[] to `false` in your `application.properties`. @@ -80,11 +83,12 @@ If you wish to log all request details (including potentially sensitive informat [[using.devtools.restart]] -=== Automatic Restart +== Automatic Restart + Applications that use `spring-boot-devtools` automatically restart whenever files on the classpath change. This can be a useful feature when working in an IDE, as it gives a very fast feedback loop for code changes. By default, any entry on the classpath that points to a directory is monitored for changes. -Note that certain resources, such as static assets and view templates, <>. +Note that certain resources, such as static assets and view templates, xref:using/devtools.adoc#using.devtools.restart.excluding-resources[do not need to restart the application]. .Triggering a restart **** @@ -101,7 +105,7 @@ NOTE: If you are restarting with Maven or Gradle using the build plugin you must If you disable forking, the isolated application classloader used by devtools will not be created and restarts will not operate properly. TIP: Automatic restart works very well when used with LiveReload. -<> for details. +See the xref:using/devtools.adoc#using.devtools.livereload[] section for details. If you use JRebel, automatic restarts are disabled in favor of dynamic class reloading. Other devtools features (such as LiveReload and property overrides) can still be used. @@ -130,36 +134,38 @@ These work by rewriting classes as they are loaded to make them more amenable to [[using.devtools.restart.logging-condition-delta]] -==== Logging Changes in Condition Evaluation +=== Logging Changes in Condition Evaluation + By default, each time your application restarts, a report showing the condition evaluation delta is logged. The report shows the changes to your application's auto-configuration as you make changes such as adding or removing beans and setting configuration properties. To disable the logging of the report, set the following property: -[source,yaml,indent=0,subs="verbatim",configblocks] +[configprops,yaml] ---- - spring: - devtools: - restart: - log-condition-evaluation-delta: false +spring: + devtools: + restart: + log-condition-evaluation-delta: false ---- [[using.devtools.restart.excluding-resources]] -==== Excluding Resources +=== Excluding Resources + Certain resources do not necessarily need to trigger a restart when they are changed. For example, Thymeleaf templates can be edited in-place. -By default, changing resources in `/META-INF/maven`, `/META-INF/resources`, `/resources`, `/static`, `/public`, or `/templates` does not trigger a restart but does trigger a <>. +By default, changing resources in `/META-INF/maven`, `/META-INF/resources`, `/resources`, `/static`, `/public`, or `/templates` does not trigger a restart but does trigger a xref:using/devtools.adoc#using.devtools.livereload[live reload]. If you want to customize these exclusions, you can use the configprop:spring.devtools.restart.exclude[] property. For example, to exclude only `/static` and `/public` you would set the following property: -[source,yaml,indent=0,subs="verbatim",configblocks] +[configprops,yaml] ---- - spring: - devtools: - restart: - exclude: "static/**,public/**" +spring: + devtools: + restart: + exclude: "static/**,public/**" ---- TIP: If you want to keep those defaults and _add_ additional exclusions, use the configprop:spring.devtools.restart.additional-exclude[] property instead. @@ -167,26 +173,29 @@ TIP: If you want to keep those defaults and _add_ additional exclusions, use the [[using.devtools.restart.watching-additional-paths]] -==== Watching Additional Paths +=== Watching Additional Paths + You may want your application to be restarted or reloaded when you make changes to files that are not on the classpath. To do so, use the configprop:spring.devtools.restart.additional-paths[] property to configure additional paths to watch for changes. -You can use the configprop:spring.devtools.restart.exclude[] property <> to control whether changes beneath the additional paths trigger a full restart or a <>. +You can use the configprop:spring.devtools.restart.exclude[] property xref:using/devtools.adoc#using.devtools.restart.excluding-resources[described earlier] to control whether changes beneath the additional paths trigger a full restart or a xref:using/devtools.adoc#using.devtools.livereload[live reload]. [[using.devtools.restart.disable]] -==== Disabling Restart +=== Disabling Restart + If you do not want to use the restart feature, you can disable it by using the configprop:spring.devtools.restart.enabled[] property. In most cases, you can set this property in your `application.properties` (doing so still initializes the restart classloader, but it does not watch for file changes). If you need to _completely_ disable restart support (for example, because it does not work with a specific library), you need to set the configprop:spring.devtools.restart.enabled[] `System` property to `false` before calling `SpringApplication.run(...)`, as shown in the following example: -include::code:MyApplication[] +include-code::MyApplication[] [[using.devtools.restart.triggerfile]] -==== Using a Trigger File +=== Using a Trigger File + If you work with an IDE that continuously compiles changed files, you might prefer to trigger restarts only at specific times. To do so, you can use a "`trigger file`", which is a special file that must be modified when you want to actually trigger a restart check. @@ -197,27 +206,27 @@ The trigger file must appear somewhere on your classpath. For example, if you have a project with the following structure: -[indent=0] +[source] ---- - src - +- main - +- resources - +- .reloadtrigger +src ++- main + +- resources + +- .reloadtrigger ---- Then your `trigger-file` property would be: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - devtools: - restart: - trigger-file: ".reloadtrigger" +spring: + devtools: + restart: + trigger-file: ".reloadtrigger" ---- Restarts will now only happen when the `src/main/resources/.reloadtrigger` is updated. -TIP: You might want to set `spring.devtools.restart.trigger-file` as a <>, so that all your projects behave in the same way. +TIP: You might want to set `spring.devtools.restart.trigger-file` as a xref:using/devtools.adoc#using.devtools.globalsettings[global setting], so that all your projects behave in the same way. Some IDEs have features that save you from needing to update your trigger file manually. https://spring.io/tools[Spring Tools for Eclipse] and https://www.jetbrains.com/idea/[IntelliJ IDEA (Ultimate Edition)] both have such support. @@ -227,8 +236,8 @@ For IntelliJ IDEA, you can follow the https://www.jetbrains.com/help/idea/spring [[using.devtools.restart.customizing-the-classload]] -==== Customizing the Restart Classloader -As described earlier in the <> section, restart functionality is implemented by using two classloaders. +=== Customizing the Restart Classloader +As described earlier in the xref:#using.devtools.restart.restart-vs-reload[] section, restart functionality is implemented by using two classloaders. If this causes issues, you can diagnose the problem by using the `spring.devtools.restart.enabled` system property, and if the app works with restart switched off, you might need to customize what gets loaded by which classloader. By default, any open project in your IDE is loaded with the "`restart`" classloader, and any regular `.jar` file is loaded with the "`base`" classloader. @@ -242,13 +251,13 @@ The `include` elements are items that should be pulled up into the "`restart`" c The value of the property is a regex pattern that is applied to the classpath passed to the JVM on startup. Here is an example where some local class files are excluded and some extra libraries are included in the restart class loader: -[source,yaml,indent=0,subs="verbatim",configblocks] +[source,properties] ---- - restart: - exclude: - companycommonlibs: "/mycorp-common-[\\w\\d-\\.]/(build|bin|out|target)/" - include: - projectcommon: "/mycorp-myproj-[\\w\\d-\\.]+\\.jar" +restart: + exclude: + companycommonlibs: "/mycorp-common-[\\w\\d-\\.]/(build|bin|out|target)/" + include: + projectcommon: "/mycorp-myproj-[\\w\\d-\\.]+\\.jar" ---- NOTE: All property keys must be unique. @@ -261,7 +270,8 @@ System properties can not be used, only the properties file. [[using.devtools.restart.limitations]] -==== Known Limitations +=== Known Limitations + Restart functionality does not work well with objects that are deserialized by using a standard `ObjectInputStream`. If you need to deserialize data, you may need to use Spring's `ConfigurableObjectInputStream` in combination with `Thread.currentThread().getContextClassLoader()`. @@ -271,7 +281,8 @@ If you find such a problem, you need to request a fix with the original authors. [[using.devtools.livereload]] -=== LiveReload +== LiveReload + The `spring-boot-devtools` module includes an embedded LiveReload server that can be used to trigger a browser refresh when a resource is changed. LiveReload browser extensions are freely available for Chrome, Firefox and Safari. You can find these extensions by searching 'LiveReload' in the marketplace or store of your chosen browser. @@ -282,12 +293,13 @@ NOTE: You can only run one LiveReload server at a time. Before starting your application, ensure that no other LiveReload servers are running. If you start multiple applications from your IDE, only the first has LiveReload support. -WARNING: To trigger LiveReload when a file changes, <> must be enabled. +WARNING: To trigger LiveReload when a file changes, xref:using/devtools.adoc#using.devtools.restart[] must be enabled. [[using.devtools.globalsettings]] -=== Global Settings +== Global Settings + You can configure global devtools settings by adding any of the following files to the `$HOME/.config/spring-boot` directory: . `spring-boot-devtools.properties` @@ -295,14 +307,14 @@ You can configure global devtools settings by adding any of the following files . `spring-boot-devtools.yml` Any properties added to these files apply to _all_ Spring Boot applications on your machine that use devtools. -For example, to configure restart to always use a <>, you would add the following property to your `spring-boot-devtools` file: +For example, to configure restart to always use a xref:using/devtools.adoc#using.devtools.restart.triggerfile[trigger file], you would add the following property to your `spring-boot-devtools` file: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - devtools: - restart: - trigger-file: ".reloadtrigger" +spring: + devtools: + restart: + trigger-file: ".reloadtrigger" ---- By default, `$HOME` is the user's home directory. @@ -315,25 +327,26 @@ This allows you to share the devtools global configuration with applications tha ==== Profiles are not supported in devtools properties/yaml files. -Any profiles activated in `.spring-boot-devtools.properties` will not affect the loading of <>. +Any profiles activated in `.spring-boot-devtools.properties` will not affect the loading of xref:features/external-config.adoc#features.external-config.files.profile-specific[profile-specific configuration files]. Profile specific filenames (of the form `spring-boot-devtools-.properties`) and `spring.config.activate.on-profile` documents in both YAML and Properties files are not supported. ==== [[using.devtools.globalsettings.configuring-file-system-watcher]] -==== Configuring File System Watcher -{spring-boot-devtools-module-code}/filewatch/FileSystemWatcher.java[FileSystemWatcher] works by polling the class changes with a certain time interval, and then waiting for a predefined quiet period to make sure there are no more changes. +=== Configuring File System Watcher + +xref:api:java/org/springframework/boot/devtools/filewatch/FileSystemWatcher.html[`FileSystemWatcher`] works by polling the class changes with a certain time interval, and then waiting for a predefined quiet period to make sure there are no more changes. Since Spring Boot relies entirely on the IDE to compile and copy files into the location from where Spring Boot can read them, you might find that there are times when certain changes are not reflected when devtools restarts the application. If you observe such problems constantly, try increasing the `spring.devtools.restart.poll-interval` and `spring.devtools.restart.quiet-period` parameters to the values that fit your development environment: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - devtools: - restart: - poll-interval: "2s" - quiet-period: "1s" +spring: + devtools: + restart: + poll-interval: "2s" + quiet-period: "1s" ---- The monitored classpath directories are now polled every 2 seconds for changes, and a 1 second quiet period is maintained to make sure there are no additional class changes. @@ -341,7 +354,8 @@ The monitored classpath directories are now polled every 2 seconds for changes, [[using.devtools.remote-applications]] -=== Remote Applications +== Remote Applications + The Spring Boot developer tools are not limited to local development. You can also use several features when running applications remotely. Remote support is opt-in as enabling it can be a security risk. @@ -351,19 +365,19 @@ You should never enable support on a production deployment. To enable it, you need to make sure that `devtools` is included in the repackaged archive, as shown in the following listing: -[source,xml,indent=0,subs="verbatim"] +[source,xml] ---- - - - - org.springframework.boot - spring-boot-maven-plugin - - false - - - - + + + + org.springframework.boot + spring-boot-maven-plugin + + false + + + + ---- Then you need to set the configprop:spring.devtools.remote.secret[] property. @@ -378,7 +392,8 @@ NOTE: Remote devtools is not supported for Spring WebFlux applications. [[using.devtools.remote-applications.client]] -==== Running the Remote Client Application +=== Running the Remote Client Application + The remote client application is designed to be run from within your IDE. You need to run `org.springframework.boot.devtools.RemoteSpringApplication` with the same classpath as the remote project that you connect to. The application's single required argument is the remote URL to which it connects. @@ -393,9 +408,9 @@ For example, if you are using Eclipse or Spring Tools and you have a project nam A running remote client might resemble the following listing: -[indent=0,subs="verbatim,attributes"] +[source,subs="verbatim,attributes"] ---- -include::{remote-spring-application-output}[] +include::ROOT:example$remote-spring-application.txt[] ---- NOTE: Because the remote client is using the same classpath as the real application it can directly read application properties. @@ -408,8 +423,9 @@ TIP: If you need to use a proxy to access the remote application, configure the [[using.devtools.remote-applications.update]] -==== Remote Update -The remote client monitors your application classpath for changes in the same way as the <>. +=== Remote Update + +The remote client monitors your application classpath for changes in the same way as the xref:using/devtools.adoc#using.devtools.restart[local restart]. Any updated resource is pushed to the remote application and (_if required_) triggers a restart. This can be helpful if you iterate on a feature that uses a cloud service that you do not have locally. Generally, remote updates and restarts are much quicker than a full rebuild and deploy cycle. @@ -421,7 +437,7 @@ The next batch can’t be sent to the application, since the server is restartin This is typically manifested by a warning in the `RemoteSpringApplication` logs about failing to upload some of the classes, and a consequent retry. But it may also lead to application code inconsistency and failure to restart after the first batch of changes is uploaded. If you observe such problems constantly, try increasing the `spring.devtools.restart.poll-interval` and `spring.devtools.restart.quiet-period` parameters to the values that fit your development environment. -See the <> section for configuring these properties. +See the xref:using/devtools.adoc#using.devtools.globalsettings.configuring-file-system-watcher[] section for configuring these properties. NOTE: Files are only monitored when the remote client is running. If you change a file before starting the remote client, it is not pushed to the remote server. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/index.adoc new file mode 100644 index 000000000000..f8611a455ddd --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/index.adoc @@ -0,0 +1,10 @@ +[[using]] += Developing with Spring Boot + +This section goes into more detail about how you should use Spring Boot. +It covers topics such as build systems, auto-configuration, and how to run your applications. +We also cover some Spring Boot best practices. +Although there is nothing particularly special about Spring Boot (it is just another library that you can consume), there are a few recommendations that, when followed, make your development process a little easier. + +If you are starting out with Spring Boot, you should probably read the xref:tutorial:first-application/index.adoc[] tutorial before diving into this section. + diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/packaging-for-production.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/packaging-for-production.adoc new file mode 100644 index 000000000000..9a85c56ed1cc --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/packaging-for-production.adoc @@ -0,0 +1,9 @@ +[[using.packaging-for-production]] += Packaging Your Application for Production + +Once your Spring Boot application is ready for production deployment, there are many options for packaging and optimizing +the application. +See the xref:packaging/index.adoc[] section of the documentation to read about these features. + +For additional "production ready" features, such as health, auditing, and metric REST or JMX end-points, consider adding `spring-boot-actuator`. +See xref:how-to:actuator.adoc[] for details. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/running-your-application.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/running-your-application.adoc similarity index 78% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/running-your-application.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/running-your-application.adoc index 51dc6ef81c08..03f867d4b8bd 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/running-your-application.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/running-your-application.adoc @@ -1,16 +1,21 @@ [[using.running-your-application]] -== Running Your Application += Running Your Application + One of the biggest advantages of packaging your application as a jar and using an embedded HTTP server is that you can run your application as you would any other. The sample applies to debugging Spring Boot applications. You do not need any special IDE plugins or extensions. +NOTE: The options below are best suited for running an application locally for development. +For production deployment, see xref:reference:using/packaging-for-production.adoc[]. + NOTE: This section only covers jar-based packaging. If you choose to package your application as a war file, see your server and IDE documentation. [[using.running-your-application.from-an-ide]] -=== Running From an IDE +== Running From an IDE + You can run a Spring Boot application from your IDE as a Java application. However, you first need to import your project. Import steps vary depending on your IDE and build system. @@ -19,7 +24,7 @@ For example, Eclipse users can select `Import...` -> `Existing Maven Projects` f If you cannot directly import your project into your IDE, you may be able to generate IDE metadata by using a build plugin. Maven includes plugins for https://maven.apache.org/plugins/maven-eclipse-plugin/[Eclipse] and https://maven.apache.org/plugins/maven-idea-plugin/[IDEA]. -Gradle offers plugins for {gradle-docs}/userguide.html[various IDEs]. +Gradle offers plugins for {url-gradle-docs}/userguide.html[various IDEs]. TIP: If you accidentally run a web application twice, you see a "`Port already in use`" error. Spring Tools users can use the `Relaunch` button rather than the `Run` button to ensure that any existing instance is closed. @@ -27,69 +32,73 @@ Spring Tools users can use the `Relaunch` button rather than the `Run` button to [[using.running-your-application.as-a-packaged-application]] -=== Running as a Packaged Application +== Running as a Packaged Application + If you use the Spring Boot Maven or Gradle plugins to create an executable jar, you can run your application using `java -jar`, as shown in the following example: -[source,shell,indent=0,subs="verbatim"] +[source,shell] ---- - $ java -jar target/myapplication-0.0.1-SNAPSHOT.jar +$ java -jar target/myapplication-0.0.1-SNAPSHOT.jar ---- It is also possible to run a packaged application with remote debugging support enabled. Doing so lets you attach a debugger to your packaged application, as shown in the following example: -[source,shell,indent=0,subs="verbatim"] +[source,shell] ---- - $ java -agentlib:jdwp=server=y,transport=dt_socket,address=8000,suspend=n \ - -jar target/myapplication-0.0.1-SNAPSHOT.jar +$ java -agentlib:jdwp=server=y,transport=dt_socket,address=8000,suspend=n \ + -jar target/myapplication-0.0.1-SNAPSHOT.jar ---- [[using.running-your-application.with-the-maven-plugin]] -=== Using the Maven Plugin +== Using the Maven Plugin + The Spring Boot Maven plugin includes a `run` goal that can be used to quickly compile and run your application. Applications run in an exploded form, as they do in your IDE. The following example shows a typical Maven command to run a Spring Boot application: -[source,shell,indent=0,subs="verbatim"] +[source,shell] ---- - $ mvn spring-boot:run +$ mvn spring-boot:run ---- You might also want to use the `MAVEN_OPTS` operating system environment variable, as shown in the following example: -[source,shell,indent=0,subs="verbatim"] +[source,shell] ---- - $ export MAVEN_OPTS=-Xmx1024m +$ export MAVEN_OPTS=-Xmx1024m ---- [[using.running-your-application.with-the-gradle-plugin]] -=== Using the Gradle Plugin +== Using the Gradle Plugin + The Spring Boot Gradle plugin also includes a `bootRun` task that can be used to run your application in an exploded form. The `bootRun` task is added whenever you apply the `org.springframework.boot` and `java` plugins and is shown in the following example: -[indent=0,subs="verbatim"] +[source,shell] ---- - $ gradle bootRun +$ gradle bootRun ---- You might also want to use the `JAVA_OPTS` operating system environment variable, as shown in the following example: -[indent=0,subs="verbatim"] +[source,shell] ---- - $ export JAVA_OPTS=-Xmx1024m +$ export JAVA_OPTS=-Xmx1024m ---- [[using.running-your-application.hot-swapping]] -=== Hot Swapping +== Hot Swapping + Since Spring Boot applications are plain Java applications, JVM hot-swapping should work out of the box. JVM hot swapping is somewhat limited with the bytecode that it can replace. For a more complete solution, https://www.jrebel.com/products/jrebel[JRebel] can be used. The `spring-boot-devtools` module also includes support for quick application restarts. -See the <> for details. +See the xref:how-to:hotswapping.adoc[] section in "`How-to Guides`" for details. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/spring-beans-and-dependency-injection.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/spring-beans-and-dependency-injection.adoc similarity index 87% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/spring-beans-and-dependency-injection.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/spring-beans-and-dependency-injection.adoc index d96903bee377..cea04af8dd15 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/spring-beans-and-dependency-injection.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/spring-beans-and-dependency-injection.adoc @@ -1,5 +1,6 @@ [[using.spring-beans-and-dependency-injection]] -== Spring Beans and Dependency Injection += Spring Beans and Dependency Injection + You are free to use any of the standard Spring Framework techniques to define your beans and their injected dependencies. We generally recommend using constructor injection to wire up dependencies and `@ComponentScan` to find beans. @@ -8,10 +9,10 @@ All of your application components (`@Component`, `@Service`, `@Repository`, `@C The following example shows a `@Service` Bean that uses constructor injection to obtain a required `RiskAssessor` bean: -include::code:singleconstructor/MyAccountService[] +include-code::singleconstructor/MyAccountService[] If a bean has more than one constructor, you will need to mark the one you want Spring to use with `@Autowired`: -include::code:multipleconstructors/MyAccountService[] +include-code::multipleconstructors/MyAccountService[] TIP: Notice how using constructor injection lets the `riskAssessor` field be marked as `final`, indicating that it cannot be subsequently changed. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/structuring-your-code.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/structuring-your-code.adoc new file mode 100644 index 000000000000..6f00571b3005 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/structuring-your-code.adoc @@ -0,0 +1,56 @@ +[[using.structuring-your-code]] += Structuring Your Code + +Spring Boot does not require any specific code layout to work. +However, there are some best practices that help. + +TIP: If you wish to enforce a structure based on domains, take a look at https://spring.io/projects/spring-modulith#overview[Spring Modulith]. + + + +[[using.structuring-your-code.using-the-default-package]] +== Using the "`default`" Package + +When a class does not include a `package` declaration, it is considered to be in the "`default package`". +The use of the "`default package`" is generally discouraged and should be avoided. +It can cause particular problems for Spring Boot applications that use the `@ComponentScan`, `@ConfigurationPropertiesScan`, `@EntityScan`, or `@SpringBootApplication` annotations, since every class from every jar is read. + +TIP: We recommend that you follow Java's recommended package naming conventions and use a reversed domain name (for example, `com.example.project`). + + + +[[using.structuring-your-code.locating-the-main-class]] +== Locating the Main Application Class + +We generally recommend that you locate your main application class in a root package above other classes. +The xref:using/using-the-springbootapplication-annotation.adoc[`@SpringBootApplication` annotation] is often placed on your main class, and it implicitly defines a base "`search package`" for certain items. +For example, if you are writing a JPA application, the package of the `@SpringBootApplication` annotated class is used to search for `@Entity` items. +Using a root package also allows component scan to apply only on your project. + +TIP: If you do not want to use `@SpringBootApplication`, the `@EnableAutoConfiguration` and `@ComponentScan` annotations that it imports defines that behavior so you can also use those instead. + +The following listing shows a typical layout: + +[source] +---- +com + +- example + +- myapplication + +- MyApplication.java + | + +- customer + | +- Customer.java + | +- CustomerController.java + | +- CustomerService.java + | +- CustomerRepository.java + | + +- order + +- Order.java + +- OrderController.java + +- OrderService.java + +- OrderRepository.java +---- + +The `MyApplication.java` file would declare the `main` method, along with the basic `@SpringBootApplication`, as follows: + +include-code::MyApplication[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/using-the-springbootapplication-annotation.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/using-the-springbootapplication-annotation.adoc new file mode 100644 index 000000000000..2a4172f8f7cb --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/using-the-springbootapplication-annotation.adoc @@ -0,0 +1,24 @@ +[[using.using-the-springbootapplication-annotation]] += Using the @SpringBootApplication Annotation + +Many Spring Boot developers like their apps to use auto-configuration, component scan and be able to define extra configuration on their "application class". +A single `@SpringBootApplication` annotation can be used to enable those three features, that is: + +* `@EnableAutoConfiguration`: enable xref:using/auto-configuration.adoc[Spring Boot's auto-configuration mechanism] +* `@ComponentScan`: enable `@Component` scan on the package where the application is located (see xref:using/structuring-your-code.adoc[the best practices]) +* `@SpringBootConfiguration`: enable registration of extra beans in the context or the import of additional configuration classes. +An alternative to Spring's standard `@Configuration` that aids xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.detecting-configuration[configuration detection] in your integration tests. + +include-code::springapplication/MyApplication[] + +NOTE: `@SpringBootApplication` also provides aliases to customize the attributes of `@EnableAutoConfiguration` and `@ComponentScan`. + +[NOTE] +==== +None of these features are mandatory and you may choose to replace this single annotation by any of the features that it enables. +For instance, you may not want to use component scan or configuration properties scan in your application: + +include-code::individualannotations/MyApplication[] + +In this example, `MyApplication` is just like any other Spring Boot application except that `@Component`-annotated classes and `@ConfigurationProperties`-annotated classes are not detected automatically and the user-defined beans are imported explicitly (see `@Import`). +==== diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/graceful-shutdown.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/graceful-shutdown.adoc new file mode 100644 index 000000000000..cd3959b6851a --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/graceful-shutdown.adoc @@ -0,0 +1,37 @@ +[[web.graceful-shutdown]] += Graceful Shutdown + +Graceful shutdown is supported with all four embedded web servers (Jetty, Reactor Netty, Tomcat, and Undertow) and with both reactive and servlet-based web applications. +It occurs as part of closing the application context and is performed in the earliest phase of stopping `SmartLifecycle` beans. +This stop processing uses a timeout which provides a grace period during which existing requests will be allowed to complete but no new requests will be permitted. + +The exact way in which new requests are not permitted varies depending on the web server that is being used. +Implementations may stop accepting requests at the network layer, or they may return a response with a specific HTTP status code or HTTP header. +The use of persistent connections can also change the way that requests stop being accepted. + +TIP: To learn about more the specific method used with your web server, see the `shutDownGracefully` API documentation for xref:api:java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.html#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[`TomcatWebServer`], xref:api:java/org/springframework/boot/web/embedded/netty/NettyWebServer.html#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[`NettyWebServer`], xref:api:java/org/springframework/boot/web/embedded/jetty/JettyWebServer.html#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[`JettyWebServer`] or xref:api:java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.html#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[`UndertowWebServer`]. + +Jetty, Reactor Netty, and Tomcat will stop accepting new requests at the network layer. +Undertow will accept new connections but respond immediately with a service unavailable (503) response. + +NOTE: Graceful shutdown with Tomcat requires Tomcat 9.0.33 or later. + +To enable graceful shutdown, configure the configprop:server.shutdown[] property, as shown in the following example: + +[configprops,yaml] +---- +server: + shutdown: "graceful" +---- + +To configure the timeout period, configure the configprop:spring.lifecycle.timeout-per-shutdown-phase[] property, as shown in the following example: + +[configprops,yaml] +---- +spring: + lifecycle: + timeout-per-shutdown-phase: "20s" +---- + +IMPORTANT: Using graceful shutdown with your IDE may not work properly if it does not send a proper `SIGTERM` signal. +See the documentation of your IDE for more details. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/index.adoc new file mode 100644 index 000000000000..6e3aa514b732 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/index.adoc @@ -0,0 +1,9 @@ +[[web]] += Web + +Spring Boot is well suited for web application development. +You can create a self-contained HTTP server by using embedded Tomcat, Jetty, Undertow, or Netty. +Most web applications use the `spring-boot-starter-web` module to get up and running quickly. +You can also choose to build reactive web applications by using the `spring-boot-starter-webflux` module. + +If you have not yet developed a Spring Boot web application, you can follow the "`Hello World!`" example in the xref:tutorial:first-application/index.adoc[Getting started] section. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/reactive.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/reactive.adoc new file mode 100644 index 000000000000..36b7eb090697 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/reactive.adoc @@ -0,0 +1,347 @@ +[[web.reactive]] += Reactive Web Applications + +Spring Boot simplifies development of reactive web applications by providing auto-configuration for Spring Webflux. + + + +[[web.reactive.webflux]] +== The "`Spring WebFlux Framework`" + +Spring WebFlux is the new reactive web framework introduced in Spring Framework 5.0. +Unlike Spring MVC, it does not require the servlet API, is fully asynchronous and non-blocking, and implements the https://www.reactive-streams.org/[Reactive Streams] specification through https://projectreactor.io/[the Reactor project]. + +Spring WebFlux comes in two flavors: functional and annotation-based. +The annotation-based one is quite close to the Spring MVC model, as shown in the following example: + +include-code::MyRestController[] + +WebFlux is part of the Spring Framework and detailed information is available in its {url-spring-framework-docs}/web/webflux.html[reference documentation]. + +"`WebFlux.fn`", the functional variant, separates the routing configuration from the actual handling of the requests, as shown in the following example: + +include-code::MyRoutingConfiguration[] + +include-code::MyUserHandler[] + +"`WebFlux.fn`" is part of the Spring Framework and detailed information is available in its {url-spring-framework-docs}/web/webflux-functional.html[reference documentation]. + +TIP: You can define as many `RouterFunction` beans as you like to modularize the definition of the router. +Beans can be ordered if you need to apply a precedence. + +To get started, add the `spring-boot-starter-webflux` module to your application. + +NOTE: Adding both `spring-boot-starter-web` and `spring-boot-starter-webflux` modules in your application results in Spring Boot auto-configuring Spring MVC, not WebFlux. +This behavior has been chosen because many Spring developers add `spring-boot-starter-webflux` to their Spring MVC application to use the reactive `WebClient`. +You can still enforce your choice by setting the chosen application type to `SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE)`. + + + +[[web.reactive.webflux.auto-configuration]] +=== Spring WebFlux Auto-configuration + +Spring Boot provides auto-configuration for Spring WebFlux that works well with most applications. + +The auto-configuration adds the following features on top of Spring's defaults: + +* Configuring codecs for `HttpMessageReader` and `HttpMessageWriter` instances (described xref:web/reactive.adoc#web.reactive.webflux.httpcodecs[later in this document]). +* Support for serving static resources, including support for WebJars (described xref:web/servlet.adoc#web.servlet.spring-mvc.static-content[later in this document]). + +If you want to keep Spring Boot WebFlux features and you want to add additional {url-spring-framework-docs}/web/webflux/config.html[WebFlux configuration], you can add your own `@Configuration` class of type `WebFluxConfigurer` but *without* `@EnableWebFlux`. + +If you want to add additional customization to the auto-configured `HttpHandler`, you can define beans of type `WebHttpHandlerBuilderCustomizer` and use them to modify the `WebHttpHandlerBuilder`. + +If you want to take complete control of Spring WebFlux, you can add your own `@Configuration` annotated with `@EnableWebFlux`. + + + +[[web.reactive.webflux.conversion-service]] +=== Spring WebFlux Conversion Service + +If you want to customize the `ConversionService` used by Spring WebFlux, you can provide a `WebFluxConfigurer` bean with an `addFormatters` method. + +Conversion can also be customized using the `spring.webflux.format.*` configuration properties. +When not configured, the following defaults are used: + +|=== +|Property |`DateTimeFormatter` |Formats + +|configprop:spring.webflux.format.date[] +|`ofLocalizedDate(FormatStyle.SHORT)` +|`java.util.Date` and `java.time.LocalDate` + +|configprop:spring.webflux.format.time[] +|`ofLocalizedTime(FormatStyle.SHORT)` +|java.time's `LocalTime` and `OffsetTime` + +|configprop:spring.webflux.format.date-time[] +|`ofLocalizedDateTime(FormatStyle.SHORT)` +|java.time's `LocalDateTime`, `OffsetDateTime`, and `ZonedDateTime` +|=== + + + +[[web.reactive.webflux.httpcodecs]] +=== HTTP Codecs with HttpMessageReaders and HttpMessageWriters + +Spring WebFlux uses the `HttpMessageReader` and `HttpMessageWriter` interfaces to convert HTTP requests and responses. +They are configured with `CodecConfigurer` to have sensible defaults by looking at the libraries available in your classpath. + +Spring Boot provides dedicated configuration properties for codecs, `+spring.codec.*+`. +It also applies further customization by using `CodecCustomizer` instances. +For example, `+spring.jackson.*+` configuration keys are applied to the Jackson codec. + +If you need to add or customize codecs, you can create a custom `CodecCustomizer` component, as shown in the following example: + +include-code::MyCodecsConfiguration[] + +You can also leverage xref:features/json.adoc#features.json.jackson.custom-serializers-and-deserializers[Boot's custom JSON serializers and deserializers]. + + + +[[web.reactive.webflux.static-content]] +=== Static Content + +By default, Spring Boot serves static content from a directory called `/static` (or `/public` or `/resources` or `/META-INF/resources`) in the classpath. +It uses the `ResourceWebHandler` from Spring WebFlux so that you can modify that behavior by adding your own `WebFluxConfigurer` and overriding the `addResourceHandlers` method. + +By default, resources are mapped on `+/**+`, but you can tune that by setting the configprop:spring.webflux.static-path-pattern[] property. +For instance, relocating all resources to `/resources/**` can be achieved as follows: + +[configprops,yaml] +---- +spring: + webflux: + static-path-pattern: "/resources/**" +---- + +You can also customize the static resource locations by using `spring.web.resources.static-locations`. +Doing so replaces the default values with a list of directory locations. +If you do so, the default welcome page detection switches to your custom locations. +So, if there is an `index.html` in any of your locations on startup, it is the home page of the application. + +In addition to the "`standard`" static resource locations listed earlier, a special case is made for https://www.webjars.org/[Webjars content]. +By default, any resources with a path in `+/webjars/**+` are served from jar files if they are packaged in the Webjars format. +The path can be customized with the configprop:spring.webflux.webjars-path-pattern[] property. + +TIP: Spring WebFlux applications do not strictly depend on the servlet API, so they cannot be deployed as war files and do not use the `src/main/webapp` directory. + + + +[[web.reactive.webflux.welcome-page]] +=== Welcome Page + +Spring Boot supports both static and templated welcome pages. +It first looks for an `index.html` file in the configured static content locations. +If one is not found, it then looks for an `index` template. +If either is found, it is automatically used as the welcome page of the application. + +This only acts as a fallback for actual index routes defined by the application. +The ordering is defined by the order of `HandlerMapping` beans which is by default the following: + +[cols="1,1"] +|=== +|`RouterFunctionMapping` +|Endpoints declared with `RouterFunction` beans + +|`RequestMappingHandlerMapping` +|Endpoints declared in `@Controller` beans + +|`RouterFunctionMapping` for the Welcome Page +|The welcome page support +|=== + + + +[[web.reactive.webflux.template-engines]] +=== Template Engines + +As well as REST web services, you can also use Spring WebFlux to serve dynamic HTML content. +Spring WebFlux supports a variety of templating technologies, including Thymeleaf, FreeMarker, and Mustache. + +Spring Boot includes auto-configuration support for the following templating engines: + +* https://freemarker.apache.org/docs/[FreeMarker] +* https://www.thymeleaf.org[Thymeleaf] +* https://mustache.github.io/[Mustache] + +When you use one of these templating engines with the default configuration, your templates are picked up automatically from `src/main/resources/templates`. + + + +[[web.reactive.webflux.error-handling]] +=== Error Handling + +Spring Boot provides a `WebExceptionHandler` that handles all errors in a sensible way. +Its position in the processing order is immediately before the handlers provided by WebFlux, which are considered last. +For machine clients, it produces a JSON response with details of the error, the HTTP status, and the exception message. +For browser clients, there is a "`whitelabel`" error handler that renders the same data in HTML format. +You can also provide your own HTML templates to display errors (see the xref:web/reactive.adoc#web.reactive.webflux.error-handling.error-pages[next section]). + +Before customizing error handling in Spring Boot directly, you can leverage the {url-spring-framework-docs}/web/webflux/ann-rest-exceptions.html[RFC 7807 Problem Details] support in Spring WebFlux. +Spring WebFlux can produce custom error messages with the `application/problem+json` media type, like: + +[source,json] +---- +{ + "type": "https://example.org/problems/unknown-project", + "title": "Unknown project", + "status": 404, + "detail": "No project found for id 'spring-unknown'", + "instance": "/projects/spring-unknown" +} +---- + +This support can be enabled by setting configprop:spring.webflux.problemdetails.enabled[] to `true`. + + +The first step to customizing this feature often involves using the existing mechanism but replacing or augmenting the error contents. +For that, you can add a bean of type `ErrorAttributes`. + +To change the error handling behavior, you can implement `ErrorWebExceptionHandler` and register a bean definition of that type. +Because an `ErrorWebExceptionHandler` is quite low-level, Spring Boot also provides a convenient `AbstractErrorWebExceptionHandler` to let you handle errors in a WebFlux functional way, as shown in the following example: + +include-code::MyErrorWebExceptionHandler[] + +For a more complete picture, you can also subclass `DefaultErrorWebExceptionHandler` directly and override specific methods. + +In some cases, errors handled at the controller level are not recorded by web observations or the xref:actuator/metrics.adoc#actuator.metrics.supported.spring-webflux[metrics infrastructure]. +Applications can ensure that such exceptions are recorded with the observations by {url-spring-framework-docs}/integration/observability.html#observability.http-server.reactive[setting the handled exception on the observation context]. + + + +[[web.reactive.webflux.error-handling.error-pages]] +==== Custom Error Pages + +If you want to display a custom HTML error page for a given status code, you can add views that resolve from `error/*`, for example by adding files to a `/error` directory. +Error pages can either be static HTML (that is, added under any of the static resource directories) or built with templates. +The name of the file should be the exact status code, a status code series mask, or `error` for a default if nothing else matches. +Note that the path to the default error view is `error/error`, whereas with Spring MVC the default error view is `error`. + +For example, to map `404` to a static HTML file, your directory structure would be as follows: + +[source] +---- +src/ + +- main/ + +- java/ + | + + +- resources/ + +- public/ + +- error/ + | +- 404.html + +- +---- + +To map all `5xx` errors by using a Mustache template, your directory structure would be as follows: + +[source] +---- +src/ + +- main/ + +- java/ + | + + +- resources/ + +- templates/ + +- error/ + | +- 5xx.mustache + +- +---- + + + +[[web.reactive.webflux.web-filters]] +=== Web Filters + +Spring WebFlux provides a `WebFilter` interface that can be implemented to filter HTTP request-response exchanges. +`WebFilter` beans found in the application context will be automatically used to filter each exchange. + +Where the order of the filters is important they can implement `Ordered` or be annotated with `@Order`. +Spring Boot auto-configuration may configure web filters for you. +When it does so, the orders shown in the following table will be used: + +|=== +| Web Filter | Order + +| `WebFilterChainProxy` (Spring Security) +| `-100` + +| `HttpExchangesWebFilter` +| `Ordered.LOWEST_PRECEDENCE - 10` +|=== + + + +[[web.reactive.reactive-server]] +== Embedded Reactive Server Support + +Spring Boot includes support for the following embedded reactive web servers: Reactor Netty, Tomcat, Jetty, and Undertow. +Most developers use the appropriate starter to obtain a fully configured instance. +By default, the embedded server listens for HTTP requests on port 8080. + + + +[[web.reactive.reactive-server.customizing]] +=== Customizing Reactive Servers + +Common reactive web server settings can be configured by using Spring `Environment` properties. +Usually, you would define the properties in your `application.properties` or `application.yaml` file. + +Common server settings include: + +* Network settings: Listen port for incoming HTTP requests (`server.port`), interface address to bind to (`server.address`), and so on. +* Error management: Location of the error page (`server.error.path`) and so on. +* xref:how-to:webserver.adoc#howto.webserver.configure-ssl[SSL] +* xref:how-to:webserver.adoc#howto.webserver.enable-response-compression[HTTP compression] + +Spring Boot tries as much as possible to expose common settings, but this is not always possible. +For those cases, dedicated namespaces such as `server.netty.*` offer server-specific customizations. + +TIP: See the xref:api:java/org/springframework/boot/autoconfigure/web/ServerProperties.html[`ServerProperties`] class for a complete list. + + + +[[web.reactive.reactive-server.customizing.programmatic]] +==== Programmatic Customization + +If you need to programmatically configure your reactive web server, you can register a Spring bean that implements the `WebServerFactoryCustomizer` interface. +`WebServerFactoryCustomizer` provides access to the `ConfigurableReactiveWebServerFactory`, which includes numerous customization setter methods. +The following example shows programmatically setting the port: + +include-code::MyWebServerFactoryCustomizer[] + +`JettyReactiveWebServerFactory`, `NettyReactiveWebServerFactory`, `TomcatReactiveWebServerFactory`, and `UndertowReactiveWebServerFactory` are dedicated variants of `ConfigurableReactiveWebServerFactory` that have additional customization setter methods for Jetty, Reactor Netty, Tomcat, and Undertow respectively. +The following example shows how to customize `NettyReactiveWebServerFactory` that provides access to Reactor Netty-specific configuration options: + +include-code::MyNettyWebServerFactoryCustomizer[] + + + +[[web.reactive.reactive-server.customizing.direct]] +==== Customizing ConfigurableReactiveWebServerFactory Directly + +For more advanced use cases that require you to extend from `ReactiveWebServerFactory`, you can expose a bean of such type yourself. + +Setters are provided for many configuration options. +Several protected method "`hooks`" are also provided should you need to do something more exotic. +See the xref:api:java/org/springframework/boot/web/reactive/server/ConfigurableReactiveWebServerFactory.html[`ConfigurableReactiveWebServerFactory`] API documentation for details. + +NOTE: Auto-configured customizers are still applied on your custom factory, so use that option carefully. + + + +[[web.reactive.reactive-server-resources-configuration]] +== Reactive Server Resources Configuration + +When auto-configuring a Reactor Netty or Jetty server, Spring Boot will create specific beans that will provide HTTP resources to the server instance: `ReactorResourceFactory` or `JettyResourceFactory`. + +By default, those resources will be also shared with the Reactor Netty and Jetty clients for optimal performances, given: + +* the same technology is used for server and client +* the client instance is built using the `WebClient.Builder` bean auto-configured by Spring Boot + +Developers can override the resource configuration for Jetty and Reactor Netty by providing a custom `ReactorResourceFactory` or `JettyResourceFactory` bean - this will be applied to both clients and servers. + +You can learn more about the resource configuration on the client side in the xref:io/rest-client.adoc#io.rest-client.webclient.runtime[] section. + + diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/servlet.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/servlet.adoc similarity index 76% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/servlet.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/servlet.adoc index cfa4c42ba3aa..57611f185cca 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/servlet.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/servlet.adoc @@ -1,26 +1,28 @@ [[web.servlet]] -== Servlet Web Applications += Servlet Web Applications + If you want to build servlet-based web applications, you can take advantage of Spring Boot's auto-configuration for Spring MVC or Jersey. [[web.servlet.spring-mvc]] -=== The "`Spring Web MVC Framework`" -The {spring-framework-docs}/web/webmvc.html[Spring Web MVC framework] (often referred to as "`Spring MVC`") is a rich "`model view controller`" web framework. +== The "`Spring Web MVC Framework`" + +The {url-spring-framework-docs}/web/webmvc.html[Spring Web MVC framework] (often referred to as "`Spring MVC`") is a rich "`model view controller`" web framework. Spring MVC lets you create special `@Controller` or `@RestController` beans to handle incoming HTTP requests. Methods in your controller are mapped to HTTP by using `@RequestMapping` annotations. The following code shows a typical `@RestController` that serves JSON data: -include::code:MyRestController[] +include-code::MyRestController[] "`WebMvc.fn`", the functional variant, separates the routing configuration from the actual handling of the requests, as shown in the following example: -include::code:MyRoutingConfiguration[] +include-code::MyRoutingConfiguration[] -include::code:MyUserHandler[] +include-code::MyUserHandler[] -Spring MVC is part of the core Spring Framework, and detailed information is available in the {spring-framework-docs}/web/webmvc.html[reference documentation]. +Spring MVC is part of the core Spring Framework, and detailed information is available in the {url-spring-framework-docs}/web/webmvc.html[reference documentation]. There are also several guides that cover Spring MVC available at https://spring.io/guides. TIP: You can define as many `RouterFunction` beans as you like to modularize the definition of the router. @@ -29,32 +31,34 @@ Beans can be ordered if you need to apply a precedence. [[web.servlet.spring-mvc.auto-configuration]] -==== Spring MVC Auto-configuration +=== Spring MVC Auto-configuration + Spring Boot provides auto-configuration for Spring MVC that works well with most applications. It replaces the need for `@EnableWebMvc` and the two cannot be used together. In addition to Spring MVC's defaults, the auto-configuration provides the following features: * Inclusion of `ContentNegotiatingViewResolver` and `BeanNameViewResolver` beans. -* Support for serving static resources, including support for WebJars (covered <>). +* Support for serving static resources, including support for WebJars (covered xref:web/servlet.adoc#web.servlet.spring-mvc.static-content[later in this document]). * Automatic registration of `Converter`, `GenericConverter`, and `Formatter` beans. -* Support for `HttpMessageConverters` (covered <>). -* Automatic registration of `MessageCodesResolver` (covered <>). +* Support for `HttpMessageConverters` (covered xref:web/servlet.adoc#web.servlet.spring-mvc.message-converters[later in this document]). +* Automatic registration of `MessageCodesResolver` (covered xref:web/servlet.adoc#web.servlet.spring-mvc.message-codes[later in this document]). * Static `index.html` support. -* Automatic use of a `ConfigurableWebBindingInitializer` bean (covered <>). +* Automatic use of a `ConfigurableWebBindingInitializer` bean (covered xref:web/servlet.adoc#web.servlet.spring-mvc.binding-initializer[later in this document]). -If you want to keep those Spring Boot MVC customizations and make more {spring-framework-docs}/web/webmvc.html[MVC customizations] (interceptors, formatters, view controllers, and other features), you can add your own `@Configuration` class of type `WebMvcConfigurer` but *without* `@EnableWebMvc`. +If you want to keep those Spring Boot MVC customizations and make more {url-spring-framework-docs}/web/webmvc.html[MVC customizations] (interceptors, formatters, view controllers, and other features), you can add your own `@Configuration` class of type `WebMvcConfigurer` but *without* `@EnableWebMvc`. If you want to provide custom instances of `RequestMappingHandlerMapping`, `RequestMappingHandlerAdapter`, or `ExceptionHandlerExceptionResolver`, and still keep the Spring Boot MVC customizations, you can declare a bean of type `WebMvcRegistrations` and use it to provide custom instances of those components. The custom instances will be subject to further initialization and configuration by Spring MVC. To participate in, and if desired, override that subsequent processing, a `WebMvcConfigurer` should be used. If you do not want to use the auto-configuration and want to take complete control of Spring MVC, add your own `@Configuration` annotated with `@EnableWebMvc`. -Alternatively, add your own `@Configuration`-annotated `DelegatingWebMvcConfiguration` as described in the Javadoc of `@EnableWebMvc`. +Alternatively, add your own `@Configuration`-annotated `DelegatingWebMvcConfiguration` as described in the `@EnableWebMvc` API documentation. [[web.servlet.spring-mvc.conversion-service]] -==== Spring MVC Conversion Service +=== Spring MVC Conversion Service + Spring MVC uses a different `ConversionService` to the one used to convert values from your `application.properties` or `application.yaml` file. It means that `Period`, `Duration` and `DataSize` converters are not available and that `@DurationUnit` and `@DataSizeUnit` annotations will be ignored. @@ -65,22 +69,26 @@ Conversion can also be customized using the `spring.mvc.format.*` configuration When not configured, the following defaults are used: |=== -|Property |`DateTimeFormatter` +|Property |`DateTimeFormatter` |Formats |configprop:spring.mvc.format.date[] |`ofLocalizedDate(FormatStyle.SHORT)` +|`java.util.Date` and `java.time.LocalDate` |configprop:spring.mvc.format.time[] |`ofLocalizedTime(FormatStyle.SHORT)` +|java.time's `LocalTime` and `OffsetTime` |configprop:spring.mvc.format.date-time[] |`ofLocalizedDateTime(FormatStyle.SHORT)` +|java.time's `LocalDateTime`, `OffsetDateTime`, and `ZonedDateTime` |=== [[web.servlet.spring-mvc.message-converters]] -==== HttpMessageConverters +=== HttpMessageConverters + Spring MVC uses the `HttpMessageConverter` interface to convert HTTP requests and responses. Sensible defaults are included out of the box. For example, objects can be automatically converted to JSON (by using the Jackson library) or XML (by using the Jackson XML extension, if available, or by using JAXB if the Jackson XML extension is not available). @@ -91,7 +99,7 @@ You can also override default converters in the same way. If you need to add or customize converters, you can use Spring Boot's `HttpMessageConverters` class, as shown in the following listing: -include::code:MyHttpMessageConvertersConfiguration[] +include-code::MyHttpMessageConvertersConfiguration[] For further control, you can also sub-class `HttpMessageConverters` and override its `postProcessConverters` and/or `postProcessPartConverters` methods. This can be useful when you want to re-order or remove some of the converters that Spring MVC configures by default. @@ -99,14 +107,16 @@ This can be useful when you want to re-order or remove some of the converters th [[web.servlet.spring-mvc.message-codes]] -==== MessageCodesResolver +=== MessageCodesResolver + Spring MVC has a strategy for generating error codes for rendering error messages from binding errors: `MessageCodesResolver`. -If you set the configprop:spring.mvc.message-codes-resolver-format[] property `PREFIX_ERROR_CODE` or `POSTFIX_ERROR_CODE`, Spring Boot creates one for you (see the enumeration in {spring-framework-api}/validation/DefaultMessageCodesResolver.Format.html[`DefaultMessageCodesResolver.Format`]). +If you set the configprop:spring.mvc.message-codes-resolver-format[] property `PREFIX_ERROR_CODE` or `POSTFIX_ERROR_CODE`, Spring Boot creates one for you (see the enumeration in {url-spring-framework-javadoc}/org/springframework/validation/DefaultMessageCodesResolver.Format.html[`DefaultMessageCodesResolver.Format`]). [[web.servlet.spring-mvc.static-content]] -==== Static Content +=== Static Content + By default, Spring Boot serves static content from a directory called `/static` (or `/public` or `/resources` or `/META-INF/resources`) in the classpath or from the root of the `ServletContext`. It uses the `ResourceHttpRequestHandler` from Spring MVC so that you can modify that behavior by adding your own `WebMvcConfigurer` and overriding the `addResourceHandlers` method. @@ -119,11 +129,11 @@ Most of the time, this does not happen (unless you modify the default MVC config By default, resources are mapped on `+/**+`, but you can tune that with the configprop:spring.mvc.static-path-pattern[] property. For instance, relocating all resources to `/resources/**` can be achieved as follows: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - mvc: - static-path-pattern: "/resources/**" +spring: + mvc: + static-path-pattern: "/resources/**" ---- You can also customize the static resource locations by using the configprop:spring.web.resources.static-locations[] property (replacing the default values with a list of directory locations). @@ -138,152 +148,169 @@ Although this directory is a common standard, it works *only* with war packaging Spring Boot also supports the advanced resource handling features provided by Spring MVC, allowing use cases such as cache-busting static resources or using version agnostic URLs for Webjars. -To use version agnostic URLs for Webjars, add the `webjars-locator-core` dependency. +To use version agnostic URLs for Webjars, add the `org.webjars:webjars-locator-lite` dependency. Then declare your Webjar. Using jQuery as an example, adding `"/webjars/jquery/jquery.min.js"` results in `"/webjars/jquery/x.y.z/jquery.min.js"` where `x.y.z` is the Webjar version. -NOTE: If you use JBoss, you need to declare the `webjars-locator-jboss-vfs` dependency instead of the `webjars-locator-core`. -Otherwise, all Webjars resolve as a `404`. - To use cache busting, the following configuration configures a cache busting solution for all static resources, effectively adding a content hash, such as ``, in URLs: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - web: - resources: - chain: - strategy: - content: - enabled: true - paths: "/**" +spring: + web: + resources: + chain: + strategy: + content: + enabled: true + paths: "/**" ---- NOTE: Links to resources are rewritten in templates at runtime, thanks to a `ResourceUrlEncodingFilter` that is auto-configured for Thymeleaf and FreeMarker. You should manually declare this filter when using JSPs. -Other template engines are currently not automatically supported but can be with custom template macros/helpers and the use of the {spring-framework-api}/web/servlet/resource/ResourceUrlProvider.html[`ResourceUrlProvider`]. +Other template engines are currently not automatically supported but can be with custom template macros/helpers and the use of the {url-spring-framework-javadoc}/org/springframework/web/servlet/resource/ResourceUrlProvider.html[`ResourceUrlProvider`]. When loading resources dynamically with, for example, a JavaScript module loader, renaming files is not an option. That is why other strategies are also supported and can be combined. A "fixed" strategy adds a static version string in the URL without changing the file name, as shown in the following example: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - web: - resources: - chain: - strategy: - content: - enabled: true - paths: "/**" - fixed: - enabled: true - paths: "/js/lib/" - version: "v12" +spring: + web: + resources: + chain: + strategy: + content: + enabled: true + paths: "/**" + fixed: + enabled: true + paths: "/js/lib/" + version: "v12" ---- With this configuration, JavaScript modules located under `"/js/lib/"` use a fixed versioning strategy (`"/v12/js/lib/mymodule.js"`), while other resources still use the content one (``). -See {spring-boot-autoconfigure-module-code}/web/WebProperties.java[`WebProperties.Resources`] for more supported options. +See xref:api:java/org/springframework/boot/autoconfigure/web/WebProperties.Resources.html[`WebProperties.Resources`] for more supported options. [TIP] ==== -This feature has been thoroughly described in a dedicated https://spring.io/blog/2014/07/24/spring-framework-4-1-handling-static-web-resources[blog post] and in Spring Framework's {spring-framework-docs}/web/webmvc/mvc-config/static-resources.html[reference documentation]. +This feature has been thoroughly described in a dedicated https://spring.io/blog/2014/07/24/spring-framework-4-1-handling-static-web-resources[blog post] and in Spring Framework's {url-spring-framework-docs}/web/webmvc/mvc-config/static-resources.html[reference documentation]. ==== [[web.servlet.spring-mvc.welcome-page]] -==== Welcome Page +=== Welcome Page + Spring Boot supports both static and templated welcome pages. It first looks for an `index.html` file in the configured static content locations. If one is not found, it then looks for an `index` template. If either is found, it is automatically used as the welcome page of the application. +This only acts as a fallback for actual index routes defined by the application. +The ordering is defined by the order of `HandlerMapping` beans which is by default the following: + +[cols="1,1"] +|=== +|`RouterFunctionMapping` +|Endpoints declared with `RouterFunction` beans + +|`RequestMappingHandlerMapping` +|Endpoints declared in `@Controller` beans + +|`WelcomePageHandlerMapping` +|The welcome page support +|=== + [[web.servlet.spring-mvc.favicon]] -==== Custom Favicon +=== Custom Favicon + As with other static resources, Spring Boot checks for a `favicon.ico` in the configured static content locations. If such a file is present, it is automatically used as the favicon of the application. [[web.servlet.spring-mvc.content-negotiation]] -==== Path Matching and Content Negotiation +=== Path Matching and Content Negotiation + Spring MVC can map incoming HTTP requests to handlers by looking at the request path and matching it to the mappings defined in your application (for example, `@GetMapping` annotations on Controller methods). Spring Boot chooses to disable suffix pattern matching by default, which means that requests like `"GET /projects/spring-boot.json"` will not be matched to `@GetMapping("/projects/spring-boot")` mappings. -This is considered as a {spring-framework-docs}/web/webmvc/mvc-controller/ann-requestmapping.html#mvc-ann-requestmapping-suffix-pattern-match[best practice for Spring MVC applications]. +This is considered as a {url-spring-framework-docs}/web/webmvc/mvc-controller/ann-requestmapping.html#mvc-ann-requestmapping-suffix-pattern-match[best practice for Spring MVC applications]. This feature was mainly useful in the past for HTTP clients which did not send proper "Accept" request headers; we needed to make sure to send the correct Content Type to the client. Nowadays, Content Negotiation is much more reliable. There are other ways to deal with HTTP clients that do not consistently send proper "Accept" request headers. Instead of using suffix matching, we can use a query parameter to ensure that requests like `"GET /projects/spring-boot?format=json"` will be mapped to `@GetMapping("/projects/spring-boot")`: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - mvc: - contentnegotiation: - favor-parameter: true +spring: + mvc: + contentnegotiation: + favor-parameter: true ---- Or if you prefer to use a different parameter name: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - mvc: - contentnegotiation: - favor-parameter: true - parameter-name: "myparam" +spring: + mvc: + contentnegotiation: + favor-parameter: true + parameter-name: "myparam" ---- Most standard media types are supported out-of-the-box, but you can also define new ones: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - mvc: - contentnegotiation: - media-types: - markdown: "text/markdown" +spring: + mvc: + contentnegotiation: + media-types: + markdown: "text/markdown" ---- As of Spring Framework 5.3, Spring MVC supports two strategies for matching request paths to controllers. By default, Spring Boot uses the `PathPatternParser` strategy. `PathPatternParser` is an https://spring.io/blog/2020/06/30/url-matching-with-pathpattern-in-spring-mvc[optimized implementation] but comes with some restrictions compared to the `AntPathMatcher` strategy. -`PathPatternParser` restricts usage of {spring-framework-docs}/web/webmvc/mvc-controller/ann-requestmapping.html#mvc-ann-requestmapping-uri-templates[some path pattern variants]. +`PathPatternParser` restricts usage of {url-spring-framework-docs}/web/webmvc/mvc-controller/ann-requestmapping.html#mvc-ann-requestmapping-uri-templates[some path pattern variants]. It is also incompatible with configuring the `DispatcherServlet` with a path prefix (configprop:spring.mvc.servlet.path[]). The strategy can be configured using the configprop:spring.mvc.pathmatch.matching-strategy[] configuration property, as shown in the following example: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - mvc: - pathmatch: - matching-strategy: "ant-path-matcher" +spring: + mvc: + pathmatch: + matching-strategy: "ant-path-matcher" ---- By default, Spring MVC will send a 404 Not Found error response if a handler is not found for a request. To have a `NoHandlerFoundException` thrown instead, set configprop:spring.mvc.throw-exception-if-no-handler-found to `true`. -Note that, by default, the <> is mapped to `+/**+` and will, therefore, provide a handler for all requests. +Note that, by default, the xref:web/servlet.adoc#web.servlet.spring-mvc.static-content[serving of static content] is mapped to `+/**+` and will, therefore, provide a handler for all requests. For a `NoHandlerFoundException` to be thrown, you must also set configprop:spring.mvc.static-path-pattern[] to a more specific value such as `/resources/**` or set configprop:spring.web.resources.add-mappings[] to `false` to disable serving of static content entirely. [[web.servlet.spring-mvc.binding-initializer]] -==== ConfigurableWebBindingInitializer +=== ConfigurableWebBindingInitializer + Spring MVC uses a `WebBindingInitializer` to initialize a `WebDataBinder` for a particular request. If you create your own `ConfigurableWebBindingInitializer` `@Bean`, Spring Boot automatically configures Spring MVC to use it. [[web.servlet.spring-mvc.template-engines]] -==== Template Engines +=== Template Engines + As well as REST web services, you can also use Spring MVC to serve dynamic HTML content. Spring MVC supports a variety of templating technologies, including Thymeleaf, FreeMarker, and JSPs. Also, many other templating engines include their own Spring MVC integrations. @@ -296,7 +323,7 @@ Spring Boot includes auto-configuration support for the following templating eng * https://mustache.github.io/[Mustache] TIP: If possible, JSPs should be avoided. -There are several <> when using them with embedded servlet containers. +There are several xref:web/servlet.adoc#web.servlet.embedded-container.jsp-limitations[known limitations] when using them with embedded servlet containers. When you use one of these templating engines with the default configuration, your templates are picked up automatically from `src/main/resources/templates`. @@ -308,13 +335,14 @@ If you have this problem, you can reorder the classpath in the IDE to place the [[web.servlet.spring-mvc.error-handling]] -==== Error Handling +=== Error Handling + By default, Spring Boot provides an `/error` mapping that handles all errors in a sensible way, and it is registered as a "`global`" error page in the servlet container. For machine clients, it produces a JSON response with details of the error, the HTTP status, and the exception message. For browser clients, there is a "`whitelabel`" error view that renders the same data in HTML format (to customize it, add a `View` that resolves to `error`). There are a number of `server.error` properties that can be set if you want to customize the default error handling behavior. -See the <> section of the Appendix. +See the xref:appendix:application-properties/index.adoc#appendix.application-properties.server[Server Properties] section of the Appendix. To replace the default behavior completely, you can implement `ErrorController` and register a bean definition of that type or add a bean of type `ErrorAttributes` to use the existing mechanism but replace the contents. @@ -322,17 +350,17 @@ TIP: The `BasicErrorController` can be used as a base class for a custom `ErrorC This is particularly useful if you want to add a handler for a new content type (the default is to handle `text/html` specifically and provide a fallback for everything else). To do so, extend `BasicErrorController`, add a public method with a `@RequestMapping` that has a `produces` attribute, and create a bean of your new type. -As of Spring Framework 6.0, {spring-framework-docs}/web/webmvc/mvc-ann-rest-exceptions.html[RFC 7807 Problem Details] is supported. +As of Spring Framework 6.0, {url-spring-framework-docs}/web/webmvc/mvc-ann-rest-exceptions.html[RFC 7807 Problem Details] is supported. Spring MVC can produce custom error messages with the `application/problem+json` media type, like: -[source,json,indent=0,subs="verbatim"] +[source,json] ---- { - "type": "https://example.org/problems/unknown-project", - "title": "Unknown project", - "status": 404, - "detail": "No project found for id 'spring-unknown'", - "instance": "/projects/spring-unknown" + "type": "https://example.org/problems/unknown-project", + "title": "Unknown project", + "status": 404, + "detail": "No project found for id 'spring-unknown'", + "instance": "/projects/spring-unknown" } ---- @@ -340,79 +368,80 @@ This support can be enabled by setting configprop:spring.mvc.problemdetails.enab You can also define a class annotated with `@ControllerAdvice` to customize the JSON document to return for a particular controller and/or exception type, as shown in the following example: -include::code:MyControllerAdvice[] +include-code::MyControllerAdvice[] In the preceding example, if `MyException` is thrown by a controller defined in the same package as `SomeController`, a JSON representation of the `MyErrorBody` POJO is used instead of the `ErrorAttributes` representation. -In some cases, errors handled at the controller level are not recorded by the <>. -Applications can ensure that such exceptions are recorded with the request metrics by setting the handled exception as a request attribute: - -include::code:MyController[] +In some cases, errors handled at the controller level are not recorded by web observations or the xref:actuator/metrics.adoc#actuator.metrics.supported.spring-mvc[metrics infrastructure]. +Applications can ensure that such exceptions are recorded with the observations by {url-spring-framework-docs}/integration/observability.html#observability.http-server.servlet[setting the handled exception on the observation context]. [[web.servlet.spring-mvc.error-handling.error-pages]] -===== Custom Error Pages +==== Custom Error Pages + If you want to display a custom HTML error page for a given status code, you can add a file to an `/error` directory. Error pages can either be static HTML (that is, added under any of the static resource directories) or be built by using templates. The name of the file should be the exact status code or a series mask. For example, to map `404` to a static HTML file, your directory structure would be as follows: -[indent=0,subs="verbatim"] +[source] ---- - src/ - +- main/ - +- java/ - | + - +- resources/ - +- public/ - +- error/ - | +- 404.html - +- +src/ + +- main/ + +- java/ + | + + +- resources/ + +- public/ + +- error/ + | +- 404.html + +- ---- To map all `5xx` errors by using a FreeMarker template, your directory structure would be as follows: -[indent=0,subs="verbatim"] +[source] ---- - src/ - +- main/ - +- java/ - | + - +- resources/ - +- templates/ - +- error/ - | +- 5xx.ftlh - +- +src/ + +- main/ + +- java/ + | + + +- resources/ + +- templates/ + +- error/ + | +- 5xx.ftlh + +- ---- For more complex mappings, you can also add beans that implement the `ErrorViewResolver` interface, as shown in the following example: -include::code:MyErrorViewResolver[] +include-code::MyErrorViewResolver[] -You can also use regular Spring MVC features such as {spring-framework-docs}/web/webmvc/mvc-servlet/exceptionhandlers.html[`@ExceptionHandler` methods] and {spring-framework-docs}/web/webmvc/mvc-controller/ann-advice.html[`@ControllerAdvice`]. +You can also use regular Spring MVC features such as {url-spring-framework-docs}/web/webmvc/mvc-servlet/exceptionhandlers.html[`@ExceptionHandler` methods] and {url-spring-framework-docs}/web/webmvc/mvc-controller/ann-advice.html[`@ControllerAdvice`]. The `ErrorController` then picks up any unhandled exceptions. [[web.servlet.spring-mvc.error-handling.error-pages-without-spring-mvc]] -===== Mapping Error Pages Outside of Spring MVC +==== Mapping Error Pages Outside of Spring MVC + For applications that do not use Spring MVC, you can use the `ErrorPageRegistrar` interface to directly register `ErrorPages`. This abstraction works directly with the underlying embedded servlet container and works even if you do not have a Spring MVC `DispatcherServlet`. -include::code:MyErrorPagesConfiguration[] +include-code::MyErrorPagesConfiguration[] NOTE: If you register an `ErrorPage` with a path that ends up being handled by a `Filter` (as is common with some non-Spring web frameworks, like Jersey and Wicket), then the `Filter` has to be explicitly registered as an `ERROR` dispatcher, as shown in the following example: -include::code:MyFilterConfiguration[] +include-code::MyFilterConfiguration[] Note that the default `FilterRegistrationBean` does not include the `ERROR` dispatcher type. [[web.servlet.spring-mvc.error-handling.in-a-war-deployment]] -===== Error Handling in a WAR Deployment +==== Error Handling in a WAR Deployment + When deployed to a servlet container, Spring Boot uses its error page filter to forward a request with an error status to the appropriate error page. This is necessary as the servlet specification does not provide an API for registering error pages. Depending on the container that you are deploying your war file to and the technologies that your application uses, some additional configuration may be required. @@ -424,19 +453,21 @@ You should disable this behavior by setting `com.ibm.ws.webcontainer.invokeFlush [[web.servlet.spring-mvc.cors]] -==== CORS Support +=== CORS Support + https://en.wikipedia.org/wiki/Cross-origin_resource_sharing[Cross-origin resource sharing] (CORS) is a https://www.w3.org/TR/cors/[W3C specification] implemented by https://caniuse.com/#feat=cors[most browsers] that lets you specify in a flexible way what kind of cross-domain requests are authorized, instead of using some less secure and less powerful approaches such as IFRAME or JSONP. -As of version 4.2, Spring MVC {spring-framework-docs}/web/webmvc-cors.html[supports CORS]. -Using {spring-framework-docs}/web/webmvc-cors.html#mvc-cors-controller[controller method CORS configuration] with {spring-framework-api}/web/bind/annotation/CrossOrigin.html[`@CrossOrigin`] annotations in your Spring Boot application does not require any specific configuration. -{spring-framework-docs}/web/webmvc-cors.html#mvc-cors-global[Global CORS configuration] can be defined by registering a `WebMvcConfigurer` bean with a customized `addCorsMappings(CorsRegistry)` method, as shown in the following example: +As of version 4.2, Spring MVC {url-spring-framework-docs}/web/webmvc-cors.html[supports CORS]. +Using {url-spring-framework-docs}/web/webmvc-cors.html#mvc-cors-controller[controller method CORS configuration] with {url-spring-framework-javadoc}/org/springframework/web/bind/annotation/CrossOrigin.html[`@CrossOrigin`] annotations in your Spring Boot application does not require any specific configuration. +{url-spring-framework-docs}/web/webmvc-cors.html#mvc-cors-global[Global CORS configuration] can be defined by registering a `WebMvcConfigurer` bean with a customized `addCorsMappings(CorsRegistry)` method, as shown in the following example: -include::code:MyCorsConfiguration[] +include-code::MyCorsConfiguration[] [[web.servlet.jersey]] -=== JAX-RS and Jersey +== JAX-RS and Jersey + If you prefer the JAX-RS programming model for REST endpoints, you can use one of the available implementations instead of Spring MVC. https://jersey.github.io/[Jersey] and https://cxf.apache.org/[Apache CXF] work quite well out of the box. CXF requires you to register its `Servlet` or `Filter` as a `@Bean` in your application context. @@ -444,23 +475,17 @@ Jersey has some native Spring support, so we also provide auto-configuration sup To get started with Jersey, include the `spring-boot-starter-jersey` as a dependency and then you need one `@Bean` of type `ResourceConfig` in which you register all the endpoints, as shown in the following example: -[source,java,indent=0,subs="verbatim"] ----- -include::{docs-java}/web/servlet/jersey/MyJerseyConfig.java[] ----- +include-code::MyJerseyConfig[] WARNING: Jersey's support for scanning executable archives is rather limited. -For example, it cannot scan for endpoints in a package found in a <> or in `WEB-INF/classes` when running an executable war file. +For example, it cannot scan for endpoints in a package found in a xref:how-to:deployment/installing.adoc[fully executable jar file] or in `WEB-INF/classes` when running an executable war file. To avoid this limitation, the `packages` method should not be used, and endpoints should be registered individually by using the `register` method, as shown in the preceding example. For more advanced customizations, you can also register an arbitrary number of beans that implement `ResourceConfigCustomizer`. All the registered endpoints should be `@Components` with HTTP resource annotations (`@GET` and others), as shown in the following example: -[source,java,indent=0,subs="verbatim"] ----- -include::{docs-java}/web/servlet/jersey/MyEndpoint.java[] ----- +include-code::MyEndpoint[] Since the `Endpoint` is a Spring `@Component`, its lifecycle is managed by Spring and you can use the `@Autowired` annotation to inject dependencies and use the `@Value` annotation to inject external configuration. By default, the Jersey servlet is registered and mapped to `/*`. @@ -478,21 +503,24 @@ Both the servlet and the filter registrations can be given init parameters by us [[web.servlet.embedded-container]] -=== Embedded Servlet Container Support +== Embedded Servlet Container Support + For servlet application, Spring Boot includes support for embedded https://tomcat.apache.org/[Tomcat], https://www.eclipse.org/jetty/[Jetty], and https://github.com/undertow-io/undertow[Undertow] servers. -Most developers use the appropriate "`Starter`" to obtain a fully configured instance. +Most developers use the appropriate starter to obtain a fully configured instance. By default, the embedded server listens for HTTP requests on port `8080`. [[web.servlet.embedded-container.servlets-filters-listeners]] -==== Servlets, Filters, and Listeners +=== Servlets, Filters, and Listeners + When using an embedded servlet container, you can register servlets, filters, and all the listeners (such as `HttpSessionListener`) from the servlet spec, either by using Spring beans or by scanning for servlet components. [[web.servlet.embedded-container.servlets-filters-listeners.beans]] -===== Registering Servlets, Filters, and Listeners as Spring Beans +==== Registering Servlets, Filters, and Listeners as Spring Beans + Any `Servlet`, `Filter`, or servlet `*Listener` instance that is a Spring bean is registered with the embedded container. This can be particularly convenient if you want to refer to a value from your `application.properties` during configuration. @@ -509,16 +537,17 @@ If you cannot change the `Filter` class to add `@Order` or implement `Ordered`, Avoid configuring a filter that reads the request body at `Ordered.HIGHEST_PRECEDENCE`, since it might go against the character encoding configuration of your application. If a servlet filter wraps the request, it should be configured with an order that is less than or equal to `OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER`. -TIP: To see the order of every `Filter` in your application, enable debug level logging for the `web` <> (`logging.level.web=debug`). +TIP: To see the order of every `Filter` in your application, enable debug level logging for the `web` xref:features/logging.adoc#features.logging.log-groups[logging group] (`logging.level.web=debug`). Details of the registered filters, including their order and URL patterns, will then be logged at startup. WARNING: Take care when registering `Filter` beans since they are initialized very early in the application lifecycle. -If you need to register a `Filter` that interacts with other beans, consider using a {spring-boot-module-api}/web/servlet/DelegatingFilterProxyRegistrationBean.html[`DelegatingFilterProxyRegistrationBean`] instead. +If you need to register a `Filter` that interacts with other beans, consider using a xref:api:java/org/springframework/boot/web/servlet/DelegatingFilterProxyRegistrationBean.html[`DelegatingFilterProxyRegistrationBean`] instead. [[web.servlet.embedded-container.context-initializer]] -==== Servlet Context Initialization +=== Servlet Context Initialization + Embedded servlet containers do not directly execute the `jakarta.servlet.ServletContainerInitializer` interface or Spring's `org.springframework.web.WebApplicationInitializer` interface. This is an intentional design decision intended to reduce the risk that third party libraries designed to run inside a war may break Spring Boot applications. @@ -528,7 +557,8 @@ The single `onStartup` method provides access to the `ServletContext` and, if ne [[web.servlet.embedded-container.context-initializer.scanning]] -===== Scanning for Servlets, Filters, and listeners +==== Scanning for Servlets, Filters, and listeners + When using an embedded container, automatic registration of classes annotated with `@WebServlet`, `@WebFilter`, and `@WebListener` can be enabled by using `@ServletComponentScan`. TIP: `@ServletComponentScan` has no effect in a standalone container, where the container's built-in discovery mechanisms are used instead. @@ -536,7 +566,8 @@ TIP: `@ServletComponentScan` has no effect in a standalone container, where the [[web.servlet.embedded-container.application-context]] -==== The ServletWebServerApplicationContext +=== The ServletWebServerApplicationContext + Under the hood, Spring Boot uses a different type of `ApplicationContext` for embedded servlet container support. The `ServletWebServerApplicationContext` is a special type of `WebApplicationContext` that bootstraps itself by searching for a single `ServletWebServerFactory` bean. Usually a `TomcatServletWebServerFactory`, `JettyServletWebServerFactory`, or `UndertowServletWebServerFactory` has been auto-configured. @@ -550,36 +581,35 @@ One way to get around this is to inject `ApplicationContext` as a dependency of Another way is to use a callback once the server has started. This can be done using an `ApplicationListener` which listens for the `ApplicationStartedEvent` as follows: -[source,java,indent=0,subs="verbatim"] ----- -include::{docs-java}/web/servlet/embeddedcontainer/applicationcontext/MyDemoBean.java[] ----- +include-code::MyDemoBean[] [[web.servlet.embedded-container.customizing]] -==== Customizing Embedded Servlet Containers +=== Customizing Embedded Servlet Containers + Common servlet container settings can be configured by using Spring `Environment` properties. Usually, you would define the properties in your `application.properties` or `application.yaml` file. Common server settings include: -* Network settings: Listen port for incoming HTTP requests (`server.port`), interface address to bind to `server.address`, and so on. +* Network settings: Listen port for incoming HTTP requests (`server.port`), interface address to bind to (`server.address`), and so on. * Session settings: Whether the session is persistent (`server.servlet.session.persistent`), session timeout (`server.servlet.session.timeout`), location of session data (`server.servlet.session.store-dir`), and session-cookie configuration (`server.servlet.session.cookie.*`). * Error management: Location of the error page (`server.error.path`) and so on. -* <> -* <> +* xref:how-to:webserver.adoc#howto.webserver.configure-ssl[SSL] +* xref:how-to:webserver.adoc#howto.webserver.enable-response-compression[HTTP compression] Spring Boot tries as much as possible to expose common settings, but this is not always possible. For those cases, dedicated namespaces offer server-specific customizations (see `server.tomcat` and `server.undertow`). -For instance, <> can be configured with specific features of the embedded servlet container. +For instance, xref:how-to:webserver.adoc#howto.webserver.configure-access-logs[access logs] can be configured with specific features of the embedded servlet container. -TIP: See the {spring-boot-autoconfigure-module-code}/web/ServerProperties.java[`ServerProperties`] class for a complete list. +TIP: See the xref:api:java/org/springframework/boot/autoconfigure/web/ServerProperties.html[`ServerProperties`] class for a complete list. [[web.servlet.embedded-container.customizing.samesite]] -===== SameSite Cookies +==== SameSite Cookies + The `SameSite` cookie attribute can be used by web browsers to control if and how cookies are submitted in cross-site requests. The attribute is particularly relevant for modern web browsers which have started to change the default value that is used when the attribute is missing. @@ -589,13 +619,13 @@ It is also used to configure Spring Session servlet based `SessionRepository` be For example, if you want your session cookie to have a `SameSite` attribute of `None`, you can add the following to your `application.properties` or `application.yaml` file: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - server: - servlet: - session: - cookie: - same-site: "none" +server: + servlet: + session: + cookie: + same-site: "none" ---- If you want to change the `SameSite` attribute on other cookies added to your `HttpServletResponse`, you can use a `CookieSameSiteSupplier`. @@ -604,25 +634,26 @@ The `CookieSameSiteSupplier` is passed a `Cookie` and may return a `SameSite` va There are a number of convenience factory and filter methods that you can use to quickly match specific cookies. For example, adding the following bean will automatically apply a `SameSite` of `Lax` for all cookies with a name that matches the regular expression `myapp.*`. -include::code:MySameSiteConfiguration[] +include-code::MySameSiteConfiguration[] [[web.servlet.embedded-container.customizing.encoding]] -===== Character Encoding +==== Character Encoding + The character encoding behavior of the embedded servlet container for request and response handling can be configured using the `server.servlet.encoding.*` configuration properties. When a request's `Accept-Language` header indicates a locale for the request it will be automatically mapped to a charset by the servlet container. -Each containers providers default locale to charset mappings and you should verify that they meet your application's needs. +Each container provides default locale to charset mappings and you should verify that they meet your application's needs. When they do not, use the configprop:server.servlet.encoding.mapping[] configuration property to customize the mappings, as shown in the following example: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - server: - servlet: - encoding: - mapping: - ko: "UTF-8" +server: + servlet: + encoding: + mapping: + ko: "UTF-8" ---- In the preceding example, the `ko` (Korean) locale has been mapped to `UTF-8`. @@ -631,34 +662,37 @@ This is equivalent to a `` entry in a `web.xml` fi [[web.servlet.embedded-container.customizing.programmatic]] -===== Programmatic Customization +==== Programmatic Customization + If you need to programmatically configure your embedded servlet container, you can register a Spring bean that implements the `WebServerFactoryCustomizer` interface. `WebServerFactoryCustomizer` provides access to the `ConfigurableServletWebServerFactory`, which includes numerous customization setter methods. The following example shows programmatically setting the port: -include::code:MyWebServerFactoryCustomizer[] +include-code::MyWebServerFactoryCustomizer[] `TomcatServletWebServerFactory`, `JettyServletWebServerFactory` and `UndertowServletWebServerFactory` are dedicated variants of `ConfigurableServletWebServerFactory` that have additional customization setter methods for Tomcat, Jetty and Undertow respectively. The following example shows how to customize `TomcatServletWebServerFactory` that provides access to Tomcat-specific configuration options: -include::code:MyTomcatWebServerFactoryCustomizer[] +include-code::MyTomcatWebServerFactoryCustomizer[] [[web.servlet.embedded-container.customizing.direct]] -===== Customizing ConfigurableServletWebServerFactory Directly +==== Customizing ConfigurableServletWebServerFactory Directly + For more advanced use cases that require you to extend from `ServletWebServerFactory`, you can expose a bean of such type yourself. Setters are provided for many configuration options. Several protected method "`hooks`" are also provided should you need to do something more exotic. -See the {spring-boot-module-api}/web/servlet/server/ConfigurableServletWebServerFactory.html[source code documentation] for details. +See the xref:api:java/org/springframework/boot/web/servlet/server/ConfigurableServletWebServerFactory.html[`ConfigurableServletWebServerFactory`] API documentation for details. NOTE: Auto-configured customizers are still applied on your custom factory, so use that option carefully. [[web.servlet.embedded-container.jsp-limitations]] -==== JSP Limitations +=== JSP Limitations + When running a Spring Boot application that uses an embedded servlet container (and is packaged as an executable archive), there are some limitations in the JSP support. * With Jetty and Tomcat, it should work if you use war packaging. @@ -667,5 +701,5 @@ JSPs are not supported when using an executable jar. * Undertow does not support JSPs. -* Creating a custom `error.jsp` page does not override the default view for <>. - <> should be used instead. +* Creating a custom `error.jsp` page does not override the default view for xref:web/servlet.adoc#web.servlet.spring-mvc.error-handling[error handling]. + xref:web/servlet.adoc#web.servlet.spring-mvc.error-handling.error-pages[Custom error pages] should be used instead. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-graphql.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-graphql.adoc similarity index 75% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-graphql.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-graphql.adoc index 76f2e33abf3b..2161a0f92354 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-graphql.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-graphql.adoc @@ -1,6 +1,7 @@ [[web.graphql]] -== Spring for GraphQL -If you want to build GraphQL applications, you can take advantage of Spring Boot's auto-configuration for {spring-graphql}[Spring for GraphQL]. += Spring for GraphQL + +If you want to build GraphQL applications, you can take advantage of Spring Boot's auto-configuration for {url-spring-graphql-site}[Spring for GraphQL]. The Spring for GraphQL project is based on https://github.com/graphql-java/graphql-java[GraphQL Java]. You'll need the `spring-boot-starter-graphql` starter at a minimum. Because GraphQL is transport-agnostic, you'll also need to have one or more additional starters in your application to expose your GraphQL API over the web: @@ -30,7 +31,8 @@ Because GraphQL is transport-agnostic, you'll also need to have one or more addi [[web.graphql.schema]] -=== GraphQL Schema +== GraphQL Schema + A Spring GraphQL application requires a defined schema at startup. By default, you can write ".graphqls" or ".gqls" schema files under `src/main/resources/graphql/**` and Spring Boot will pick them up automatically. You can customize the locations with configprop:spring.graphql.schema.locations[] and the file extensions with configprop:spring.graphql.schema.file-extensions[]. @@ -40,9 +42,9 @@ you can set configprop:spring.graphql.schema.locations[] to `+"classpath*:graphq In the following sections, we'll consider this sample GraphQL schema, defining two types and two queries: -[source,json,indent=0,subs="verbatim,quotes"] +[source,json,subs="verbatim,quotes"] ---- -include::{docs-resources}/graphql/schema.graphqls[] +include::ROOT:example$resources/graphql/schema.graphqls[] ---- NOTE: By default, https://spec.graphql.org/draft/#sec-Introspection[field introspection] will be allowed on the schema as it is required for tools such as GraphiQL. @@ -51,23 +53,25 @@ If you wish to not expose information about the schema, you can disable introspe [[web.graphql.runtimewiring]] -=== GraphQL RuntimeWiring +== GraphQL RuntimeWiring + The GraphQL Java `RuntimeWiring.Builder` can be used to register custom scalar types, directives, type resolvers, `DataFetcher`, and more. You can declare `RuntimeWiringConfigurer` beans in your Spring config to get access to the `RuntimeWiring.Builder`. -Spring Boot detects such beans and adds them to the {spring-graphql-docs}#execution-graphqlsource[GraphQlSource builder]. +Spring Boot detects such beans and adds them to the {url-spring-graphql-docs}/#execution-graphqlsource[GraphQlSource builder]. -Typically, however, applications will not implement `DataFetcher` directly and will instead create {spring-graphql-docs}#controllers[annotated controllers]. +Typically, however, applications will not implement `DataFetcher` directly and will instead create {url-spring-graphql-docs}/#controllers[annotated controllers]. Spring Boot will automatically detect `@Controller` classes with annotated handler methods and register those as ``DataFetcher``s. Here's a sample implementation for our greeting query with a `@Controller` class: -include::code:GreetingController[] +include-code::GreetingController[] [[web.graphql.data-query]] -=== Querydsl and QueryByExample Repositories Support +== Querydsl and QueryByExample Repositories Support + Spring Data offers support for both Querydsl and QueryByExample repositories. -Spring GraphQL can {spring-graphql-docs}#data[configure Querydsl and QueryByExample repositories as `DataFetcher`]. +Spring GraphQL can {url-spring-graphql-docs}/#data[configure Querydsl and QueryByExample repositories as `DataFetcher`]. Spring Data repositories annotated with `@GraphQlRepository` and extending one of: @@ -79,14 +83,17 @@ Spring Data repositories annotated with `@GraphQlRepository` and extending one o are detected by Spring Boot and considered as candidates for `DataFetcher` for matching top-level queries. + [[web.graphql.transports]] -=== Transports +== Transports [[web.graphql.transports.http-websocket]] -==== HTTP and WebSocket +=== HTTP and WebSocket + The GraphQL HTTP endpoint is at HTTP POST `/graphql` by default. +It also supports the `"text/event-stream"` media type over Server Sent Events for subscriptions only. The path can be customized with configprop:spring.graphql.path[]. TIP: The HTTP endpoint for both Spring MVC and Spring WebFlux is provided by a `RouterFunction` bean with an `@Order` of `0`. @@ -98,58 +105,61 @@ The GraphQL WebSocket endpoint is off by default. To enable it: * For a WebFlux application, no additional dependency is required * For both, the configprop:spring.graphql.websocket.path[] application property must be set -Spring GraphQL provides a {spring-graphql-docs}#web-interception[Web Interception] model. +Spring GraphQL provides a {url-spring-graphql-docs}/#web-interception[Web Interception] model. This is quite useful for retrieving information from an HTTP request header and set it in the GraphQL context or fetching information from the same context and writing it to a response header. With Spring Boot, you can declare a `WebInterceptor` bean to have it registered with the web transport. -{spring-framework-docs}/web/webmvc-cors.html[Spring MVC] and {spring-framework-docs}/web/webflux-cors.html[Spring WebFlux] support CORS (Cross-Origin Resource Sharing) requests. +{url-spring-framework-docs}/web/webmvc-cors.html[Spring MVC] and {url-spring-framework-docs}/web/webflux-cors.html[Spring WebFlux] support CORS (Cross-Origin Resource Sharing) requests. CORS is a critical part of the web config for GraphQL applications that are accessed from browsers using different domains. Spring Boot supports many configuration properties under the `spring.graphql.cors.*` namespace; here's a short configuration sample: -[source,yaml,indent=0,subs="verbatim",configblocks] +[configprops,yaml] ---- - spring: - graphql: - cors: - allowed-origins: "https://example.org" - allowed-methods: GET,POST - max-age: 1800s +spring: + graphql: + cors: + allowed-origins: "https://example.org" + allowed-methods: GET,POST + max-age: 1800s ---- [[web.graphql.transports.rsocket]] -==== RSocket +=== RSocket + RSocket is also supported as a transport, on top of WebSocket or TCP. -Once the <>, we can configure our GraphQL handler on a particular route using configprop:spring.graphql.rsocket.mapping[]. +Once the xref:messaging/rsocket.adoc#messaging.rsocket.server-auto-configuration[RSocket server is configured], we can configure our GraphQL handler on a particular route using configprop:spring.graphql.rsocket.mapping[]. For example, configuring that mapping as `"graphql"` means we can use that as a route when sending requests with the `RSocketGraphQlClient`. Spring Boot auto-configures a `RSocketGraphQlClient.Builder` bean that you can inject in your components: -include::code:RSocketGraphQlClientExample[tag=builder] +include-code::RSocketGraphQlClientExample[tag=builder] And then send a request: -include::code:RSocketGraphQlClientExample[tag=request] +include-code::RSocketGraphQlClientExample[tag=request] [[web.graphql.exception-handling]] -=== Exception Handling +== Exception Handling + Spring GraphQL enables applications to register one or more Spring `DataFetcherExceptionResolver` components that are invoked sequentially. -The Exception must be resolved to a list of `graphql.GraphQLError` objects, see {spring-graphql-docs}#execution-exceptions[Spring GraphQL exception handling documentation]. +The Exception must be resolved to a list of `graphql.GraphQLError` objects, see {url-spring-graphql-docs}/#execution-exceptions[Spring GraphQL exception handling documentation]. Spring Boot will automatically detect `DataFetcherExceptionResolver` beans and register them with the `GraphQlSource.Builder`. [[web.graphql.graphiql]] -=== GraphiQL and Schema printer +== GraphiQL and Schema Printer + Spring GraphQL offers infrastructure for helping developers when consuming or developing a GraphQL API. Spring GraphQL ships with a default https://github.com/graphql/graphiql[GraphiQL] page that is exposed at `"/graphiql"` by default. This page is disabled by default and can be turned on with the configprop:spring.graphql.graphiql.enabled[] property. Many applications exposing such a page will prefer a custom build. -A default implementation is very useful during development, this is why it is exposed automatically with <> during development. +A default implementation is very useful during development, this is why it is exposed automatically with xref:using/devtools.adoc[`spring-boot-devtools`] during development. You can also choose to expose the GraphQL schema in text format at `/graphql/schema` when the configprop:spring.graphql.schema.printer.enabled[] property is enabled. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-hateoas.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-hateoas.adoc similarity index 98% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-hateoas.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-hateoas.adoc index 0731b7067dbe..d49c34e5ff2c 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-hateoas.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-hateoas.adoc @@ -1,5 +1,6 @@ [[web.spring-hateoas]] -== Spring HATEOAS += Spring HATEOAS + If you develop a RESTful API that makes use of hypermedia, Spring Boot provides auto-configuration for Spring HATEOAS that works well with most applications. The auto-configuration replaces the need to use `@EnableHypermediaSupport` and registers a number of beans to ease building hypermedia-based applications, including a `LinkDiscoverers` (for client side support) and an `ObjectMapper` configured to correctly marshal responses into the desired representation. The `ObjectMapper` is customized by setting the various `spring.jackson.*` properties or, if one exists, by a `Jackson2ObjectMapperBuilder` bean. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-security.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-security.adoc new file mode 100644 index 000000000000..233e877d20a0 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-security.adoc @@ -0,0 +1,408 @@ +[[web.security]] += Spring Security + +If {url-spring-security-site}[Spring Security] is on the classpath, then web applications are secured by default. +Spring Boot relies on Spring Security’s content-negotiation strategy to determine whether to use `httpBasic` or `formLogin`. +To add method-level security to a web application, you can also add `@EnableGlobalMethodSecurity` with your desired settings. +Additional information can be found in the {url-spring-security-docs}/servlet/authorization/method-security.html[Spring Security Reference Guide]. + +The default `UserDetailsService` has a single user. +The user name is `user`, and the password is random and is printed at WARN level when the application starts, as shown in the following example: + +[source] +---- +Using generated security password: 78fa095d-3f4c-48b1-ad50-e24c31d5cf35 + +This generated password is for development use only. Your security configuration must be updated before running your application in production. +---- + +NOTE: If you fine-tune your logging configuration, ensure that the `org.springframework.boot.autoconfigure.security` category is set to log `WARN`-level messages. +Otherwise, the default password is not printed. + +You can change the username and password by providing a `spring.security.user.name` and `spring.security.user.password`. + +The basic features you get by default in a web application are: + +* A `UserDetailsService` (or `ReactiveUserDetailsService` in case of a WebFlux application) bean with in-memory store and a single user with a generated password (see xref:api:java/org/springframework/boot/autoconfigure/security/SecurityProperties.User.html[`SecurityProperties.User`] for the properties of the user). +* Form-based login or HTTP Basic security (depending on the `Accept` header in the request) for the entire application (including actuator endpoints if actuator is on the classpath). +* A `DefaultAuthenticationEventPublisher` for publishing authentication events. + +You can provide a different `AuthenticationEventPublisher` by adding a bean for it. + + + +[[web.security.spring-mvc]] +== MVC Security + +The default security configuration is implemented in `SecurityAutoConfiguration` and `UserDetailsServiceAutoConfiguration`. +`SecurityAutoConfiguration` imports `SpringBootWebSecurityConfiguration` for web security and `UserDetailsServiceAutoConfiguration` configures authentication, which is also relevant in non-web applications. + +To switch off the default web application security configuration completely or to combine multiple Spring Security components such as OAuth2 Client and Resource Server, add a bean of type `SecurityFilterChain` (doing so does not disable the `UserDetailsService` configuration or Actuator's security). +To also switch off the `UserDetailsService` configuration, you can add a bean of type `UserDetailsService`, `AuthenticationProvider`, or `AuthenticationManager`. + +The auto-configuration of a `UserDetailsService` will also back off any of the following Spring Security modules is on the classpath: + +- `spring-security-oauth2-client` +- `spring-security-oauth2-resource-server` +- `spring-security-saml2-service-provider` + +To use `UserDetailsService` in addition to one or more of these dependencies, define your own `InMemoryUserDetailsManager` bean. + +Access rules can be overridden by adding a custom `SecurityFilterChain` bean. +Spring Boot provides convenience methods that can be used to override access rules for actuator endpoints and static resources. +`EndpointRequest` can be used to create a `RequestMatcher` that is based on the configprop:management.endpoints.web.base-path[] property. +`PathRequest` can be used to create a `RequestMatcher` for resources in commonly used locations. + + + +[[web.security.spring-webflux]] +== WebFlux Security + +Similar to Spring MVC applications, you can secure your WebFlux applications by adding the `spring-boot-starter-security` dependency. +The default security configuration is implemented in `ReactiveSecurityAutoConfiguration` and `UserDetailsServiceAutoConfiguration`. +`ReactiveSecurityAutoConfiguration` imports `WebFluxSecurityConfiguration` for web security and `UserDetailsServiceAutoConfiguration` configures authentication, which is also relevant in non-web applications. + +To switch off the default web application security configuration completely, you can add a bean of type `WebFilterChainProxy` (doing so does not disable the `UserDetailsService` configuration or Actuator's security). +To also switch off the `UserDetailsService` configuration, you can add a bean of type `ReactiveUserDetailsService` or `ReactiveAuthenticationManager`. + +The auto-configuration will also back off when any of the following Spring Security modules is on the classpath: + +- `spring-security-oauth2-client` +- `spring-security-oauth2-resource-server` + +To use `ReactiveUserDetailsService` in addition to one or more of these dependencies, define your own `MapReactiveUserDetailsService` bean. + +Access rules and the use of multiple Spring Security components such as OAuth 2 Client and Resource Server can be configured by adding a custom `SecurityWebFilterChain` bean. +Spring Boot provides convenience methods that can be used to override access rules for actuator endpoints and static resources. +`EndpointRequest` can be used to create a `ServerWebExchangeMatcher` that is based on the configprop:management.endpoints.web.base-path[] property. + +`PathRequest` can be used to create a `ServerWebExchangeMatcher` for resources in commonly used locations. + +For example, you can customize your security configuration by adding something like: + +include-code::MyWebFluxSecurityConfiguration[] + + + +[[web.security.oauth2]] +== OAuth2 + +https://oauth.net/2/[OAuth2] is a widely used authorization framework that is supported by Spring. + + + +[[web.security.oauth2.client]] +=== Client + +If you have `spring-security-oauth2-client` on your classpath, you can take advantage of some auto-configuration to set up OAuth2/Open ID Connect clients. +This configuration makes use of the properties under `OAuth2ClientProperties`. +The same properties are applicable to both servlet and reactive applications. + +You can register multiple OAuth2 clients and providers under the `spring.security.oauth2.client` prefix, as shown in the following example: + +[configprops,yaml] +---- +spring: + security: + oauth2: + client: + registration: + my-login-client: + client-id: "abcd" + client-secret: "password" + client-name: "Client for OpenID Connect" + provider: "my-oauth-provider" + scope: "openid,profile,email,phone,address" + redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" + client-authentication-method: "client_secret_basic" + authorization-grant-type: "authorization_code" + + my-client-1: + client-id: "abcd" + client-secret: "password" + client-name: "Client for user scope" + provider: "my-oauth-provider" + scope: "user" + redirect-uri: "{baseUrl}/authorized/user" + client-authentication-method: "client_secret_basic" + authorization-grant-type: "authorization_code" + + my-client-2: + client-id: "abcd" + client-secret: "password" + client-name: "Client for email scope" + provider: "my-oauth-provider" + scope: "email" + redirect-uri: "{baseUrl}/authorized/email" + client-authentication-method: "client_secret_basic" + authorization-grant-type: "authorization_code" + + provider: + my-oauth-provider: + authorization-uri: "https://my-auth-server.com/oauth2/authorize" + token-uri: "https://my-auth-server.com/oauth2/token" + user-info-uri: "https://my-auth-server.com/userinfo" + user-info-authentication-method: "header" + jwk-set-uri: "https://my-auth-server.com/oauth2/jwks" + user-name-attribute: "name" +---- + +For OpenID Connect providers that support https://openid.net/specs/openid-connect-discovery-1_0.html[OpenID Connect discovery], the configuration can be further simplified. +The provider needs to be configured with an `issuer-uri` which is the URI that it asserts as its Issuer Identifier. +For example, if the `issuer-uri` provided is "https://example.com", then an "OpenID Provider Configuration Request" will be made to "https://example.com/.well-known/openid-configuration". +The result is expected to be an "OpenID Provider Configuration Response". +The following example shows how an OpenID Connect Provider can be configured with the `issuer-uri`: + +[configprops,yaml] +---- +spring: + security: + oauth2: + client: + provider: + oidc-provider: + issuer-uri: "https://dev-123456.oktapreview.com/oauth2/default/" +---- + +By default, Spring Security's `OAuth2LoginAuthenticationFilter` only processes URLs matching `/login/oauth2/code/*`. +If you want to customize the `redirect-uri` to use a different pattern, you need to provide configuration to process that custom pattern. +For example, for servlet applications, you can add your own `SecurityFilterChain` that resembles the following: + +include-code::MyOAuthClientConfiguration[] + +TIP: Spring Boot auto-configures an `InMemoryOAuth2AuthorizedClientService` which is used by Spring Security for the management of client registrations. +The `InMemoryOAuth2AuthorizedClientService` has limited capabilities and we recommend using it only for development environments. +For production environments, consider using a `JdbcOAuth2AuthorizedClientService` or creating your own implementation of `OAuth2AuthorizedClientService`. + + + +[[web.security.oauth2.client.common-providers]] +==== OAuth2 Client Registration for Common Providers + +For common OAuth2 and OpenID providers, including Google, Github, Facebook, and Okta, we provide a set of provider defaults (`google`, `github`, `facebook`, and `okta`, respectively). + +If you do not need to customize these providers, you can set the `provider` attribute to the one for which you need to infer defaults. +Also, if the key for the client registration matches a default supported provider, Spring Boot infers that as well. + +In other words, the two configurations in the following example use the Google provider: + +[configprops,yaml] +---- +spring: + security: + oauth2: + client: + registration: + my-client: + client-id: "abcd" + client-secret: "password" + provider: "google" + google: + client-id: "abcd" + client-secret: "password" +---- + + + +[[web.security.oauth2.server]] +=== Resource Server + +If you have `spring-security-oauth2-resource-server` on your classpath, Spring Boot can set up an OAuth2 Resource Server. +For JWT configuration, a JWK Set URI or OIDC Issuer URI needs to be specified, as shown in the following examples: + +[configprops,yaml] +---- +spring: + security: + oauth2: + resourceserver: + jwt: + jwk-set-uri: "https://example.com/oauth2/default/v1/keys" +---- + +[configprops,yaml] +---- +spring: + security: + oauth2: + resourceserver: + jwt: + issuer-uri: "https://dev-123456.oktapreview.com/oauth2/default/" +---- + +NOTE: If the authorization server does not support a JWK Set URI, you can configure the resource server with the Public Key used for verifying the signature of the JWT. +This can be done using the configprop:spring.security.oauth2.resourceserver.jwt.public-key-location[] property, where the value needs to point to a file containing the public key in the PEM-encoded x509 format. + +The configprop:spring.security.oauth2.resourceserver.jwt.audiences[] property can be used to specify the expected values of the aud claim in JWTs. +For example, to require JWTs to contain an aud claim with the value `my-audience`: + +[configprops,yaml] +---- +spring: + security: + oauth2: + resourceserver: + jwt: + audiences: + - "my-audience" +---- + +The same properties are applicable for both servlet and reactive applications. +Alternatively, you can define your own `JwtDecoder` bean for servlet applications or a `ReactiveJwtDecoder` for reactive applications. + +In cases where opaque tokens are used instead of JWTs, you can configure the following properties to validate tokens through introspection: + +[configprops,yaml] +---- +spring: + security: + oauth2: + resourceserver: + opaquetoken: + introspection-uri: "https://example.com/check-token" + client-id: "my-client-id" + client-secret: "my-client-secret" +---- + +Again, the same properties are applicable for both servlet and reactive applications. +Alternatively, you can define your own `OpaqueTokenIntrospector` bean for servlet applications or a `ReactiveOpaqueTokenIntrospector` for reactive applications. + + + +[[web.security.oauth2.authorization-server]] +=== Authorization Server + +If you have `spring-security-oauth2-authorization-server` on your classpath, you can take advantage of some auto-configuration to set up a Servlet-based OAuth2 Authorization Server. + +You can register multiple OAuth2 clients under the `spring.security.oauth2.authorizationserver.client` prefix, as shown in the following example: + +[configprops,yaml] +---- +spring: + security: + oauth2: + authorizationserver: + client: + my-client-1: + registration: + client-id: "abcd" + client-secret: "{noop}secret1" + client-authentication-methods: + - "client_secret_basic" + authorization-grant-types: + - "authorization_code" + - "refresh_token" + redirect-uris: + - "https://my-client-1.com/login/oauth2/code/abcd" + - "https://my-client-1.com/authorized" + scopes: + - "openid" + - "profile" + - "email" + - "phone" + - "address" + require-authorization-consent: true + my-client-2: + registration: + client-id: "efgh" + client-secret: "{noop}secret2" + client-authentication-methods: + - "client_secret_jwt" + authorization-grant-types: + - "client_credentials" + scopes: + - "user.read" + - "user.write" + jwk-set-uri: "https://my-client-2.com/jwks" + token-endpoint-authentication-signing-algorithm: "RS256" +---- + +NOTE: The `client-secret` property must be in a format that can be matched by the configured `PasswordEncoder`. +The default instance of `PasswordEncoder` is created via `PasswordEncoderFactories.createDelegatingPasswordEncoder()`. + +The auto-configuration Spring Boot provides for Spring Authorization Server is designed for getting started quickly. +Most applications will require customization and will want to define several beans to override auto-configuration. + +The following components can be defined as beans to override auto-configuration specific to Spring Authorization Server: + +* `RegisteredClientRepository` +* `AuthorizationServerSettings` +* `SecurityFilterChain` +* `com.nimbusds.jose.jwk.source.JWKSource` +* `JwtDecoder` + +TIP: Spring Boot auto-configures an `InMemoryRegisteredClientRepository` which is used by Spring Authorization Server for the management of registered clients. +The `InMemoryRegisteredClientRepository` has limited capabilities and we recommend using it only for development environments. +For production environments, consider using a `JdbcRegisteredClientRepository` or creating your own implementation of `RegisteredClientRepository`. + +Additional information can be found in the {url-spring-authorization-server-docs}/getting-started.html[Getting Started] chapter of the {url-spring-authorization-server-docs}[Spring Authorization Server Reference Guide]. + + + +[[web.security.saml2]] +== SAML 2.0 + + + +[[web.security.saml2.relying-party]] +=== Relying Party + +If you have `spring-security-saml2-service-provider` on your classpath, you can take advantage of some auto-configuration to set up a SAML 2.0 Relying Party. +This configuration makes use of the properties under `Saml2RelyingPartyProperties`. + +A relying party registration represents a paired configuration between an Identity Provider, IDP, and a Service Provider, SP. +You can register multiple relying parties under the `spring.security.saml2.relyingparty` prefix, as shown in the following example: + +[configprops,yaml] +---- +spring: + security: + saml2: + relyingparty: + registration: + my-relying-party1: + signing: + credentials: + - private-key-location: "path-to-private-key" + certificate-location: "path-to-certificate" + decryption: + credentials: + - private-key-location: "path-to-private-key" + certificate-location: "path-to-certificate" + singlelogout: + url: "https://myapp/logout/saml2/slo" + response-url: "https://remoteidp2.slo.url" + binding: "POST" + assertingparty: + verification: + credentials: + - certificate-location: "path-to-verification-cert" + entity-id: "remote-idp-entity-id1" + sso-url: "https://remoteidp1.sso.url" + + my-relying-party2: + signing: + credentials: + - private-key-location: "path-to-private-key" + certificate-location: "path-to-certificate" + decryption: + credentials: + - private-key-location: "path-to-private-key" + certificate-location: "path-to-certificate" + assertingparty: + verification: + credentials: + - certificate-location: "path-to-other-verification-cert" + entity-id: "remote-idp-entity-id2" + sso-url: "https://remoteidp2.sso.url" + singlelogout: + url: "https://remoteidp2.slo.url" + response-url: "https://myapp/logout/saml2/slo" + binding: "POST" +---- + +For SAML2 logout, by default, Spring Security's `Saml2LogoutRequestFilter` and `Saml2LogoutResponseFilter` only process URLs matching `/logout/saml2/slo`. +If you want to customize the `url` to which AP-initiated logout requests get sent to or the `response-url` to which an AP sends logout responses to, to use a different pattern, you need to provide configuration to process that custom pattern. +For example, for servlet applications, you can add your own `SecurityFilterChain` that resembles the following: + +include-code::MySamlRelyingPartyConfiguration[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-session.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-session.adoc similarity index 80% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-session.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-session.adoc index ec7c2304b006..e3604ffecd4d 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-session.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-session.adoc @@ -1,6 +1,7 @@ [[web.spring-session]] -== Spring Session -Spring Boot provides {spring-session}[Spring Session] auto-configuration for a wide range of data stores. += Spring Session + +Spring Boot provides {url-spring-session-site}[Spring Session] auto-configuration for a wide range of data stores. When building a servlet web application, the following stores can be auto-configured: * Redis @@ -8,7 +9,7 @@ When building a servlet web application, the following stores can be auto-config * Hazelcast * MongoDB -Additionally, {spring-boot-for-apache-geode}[Spring Boot for Apache Geode] provides {spring-boot-for-apache-geode-docs}#geode-session[auto-configuration for using Apache Geode as a session store]. +Additionally, {url-spring-boot-for-apache-geode-site}[Spring Boot for Apache Geode] provides {url-spring-boot-for-apache-geode-docs}#geode-session[auto-configuration for using Apache Geode as a session store]. The servlet auto-configuration replaces the need to use `@Enable*HttpSession`. @@ -39,12 +40,12 @@ Similar to the servlet configuration, if you have more than one implementation, Each store has specific additional settings. For instance, it is possible to customize the name of the table for the JDBC store, as shown in the following example: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - session: - jdbc: - table-name: "SESSIONS" +spring: + session: + jdbc: + table-name: "SESSIONS" ---- For setting the timeout of the session you can use the configprop:spring.session.timeout[] property. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/partials/nav-reference.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/partials/nav-reference.adoc new file mode 100644 index 000000000000..0c674a831435 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/partials/nav-reference.adoc @@ -0,0 +1,93 @@ +* xref:reference:index.adoc[] +** xref:reference:using/index.adoc[] +*** xref:reference:using/build-systems.adoc[] +*** xref:reference:using/structuring-your-code.adoc[] +*** xref:reference:using/configuration-classes.adoc[] +*** xref:reference:using/auto-configuration.adoc[] +*** xref:reference:using/spring-beans-and-dependency-injection.adoc[] +*** xref:reference:using/using-the-springbootapplication-annotation.adoc[] +*** xref:reference:using/running-your-application.adoc[] +*** xref:reference:using/devtools.adoc[] +*** xref:reference:using/packaging-for-production.adoc[] + +** xref:reference:features/index.adoc[] +*** xref:reference:features/spring-application.adoc[] +*** xref:reference:features/external-config.adoc[] +*** xref:reference:features/profiles.adoc[] +*** xref:reference:features/logging.adoc[] +*** xref:reference:features/internationalization.adoc[] +*** xref:reference:features/aop.adoc[] +*** xref:reference:features/json.adoc[] +*** xref:reference:features/task-execution-and-scheduling.adoc[] +*** xref:reference:features/dev-services.adoc[] +*** xref:reference:features/developing-auto-configuration.adoc[] +*** xref:reference:features/kotlin.adoc[] +*** xref:reference:features/ssl.adoc[] + +** xref:reference:web/index.adoc[] +*** xref:reference:web/servlet.adoc[] +*** xref:reference:web/reactive.adoc[] +*** xref:reference:web/graceful-shutdown.adoc[] +*** xref:reference:web/spring-security.adoc[] +*** xref:reference:web/spring-session.adoc[] +*** xref:reference:web/spring-graphql.adoc[] +*** xref:reference:web/spring-hateoas.adoc[] + +** xref:reference:data/index.adoc[] +*** xref:reference:data/sql.adoc[] +*** xref:reference:data/nosql.adoc[] + +** xref:reference:io/index.adoc[] +*** xref:reference:io/caching.adoc[] +*** xref:reference:io/hazelcast.adoc[] +*** xref:reference:io/quartz.adoc[] +*** xref:reference:io/email.adoc[] +*** xref:reference:io/validation.adoc[] +*** xref:reference:io/rest-client.adoc[] +*** xref:reference:io/webservices.adoc[] +*** xref:reference:io/jta.adoc[] + +** xref:reference:messaging/index.adoc[] +*** xref:reference:messaging/jms.adoc[] +*** xref:reference:messaging/amqp.adoc[] +*** xref:reference:messaging/kafka.adoc[] +*** xref:reference:messaging/pulsar.adoc[] +*** xref:reference:messaging/rsocket.adoc[] +*** xref:reference:messaging/spring-integration.adoc[] +*** xref:reference:messaging/websockets.adoc[] + +** xref:reference:testing/index.adoc[] +*** xref:reference:testing/test-scope-dependencies.adoc[] +*** xref:reference:testing/spring-applications.adoc[] +*** xref:reference:testing/spring-boot-applications.adoc[] +*** xref:reference:testing/testcontainers.adoc[] +*** xref:reference:testing/test-utilities.adoc[] + +** xref:reference:packaging/index.adoc[] +*** xref:reference:packaging/efficient.adoc[] +*** xref:reference:packaging/class-data-sharing.adoc[] +*** xref:reference:packaging/aot.adoc[] +*** xref:reference:packaging/native-image/index.adoc[] +**** xref:reference:packaging/native-image/introducing-graalvm-native-images.adoc[] +**** xref:reference:packaging/native-image/advanced-topics.adoc[] +*** xref:reference:packaging/checkpoint-restore.adoc[] +*** xref:reference:packaging/container-images/index.adoc[] +**** xref:reference:packaging/container-images/efficient-images.adoc[] +**** xref:reference:packaging/container-images/dockerfiles.adoc[] +**** xref:reference:packaging/container-images/cloud-native-buildpacks.adoc[] + +** xref:reference:actuator/index.adoc[] +*** xref:reference:actuator/enabling.adoc[] +*** xref:reference:actuator/endpoints.adoc[] +*** xref:reference:actuator/monitoring.adoc[] +*** xref:reference:actuator/jmx.adoc[] +*** xref:reference:actuator/observability.adoc[] +*** xref:reference:actuator/loggers.adoc[] +*** xref:reference:actuator/metrics.adoc[] +*** xref:reference:actuator/tracing.adoc[] +*** xref:reference:actuator/auditing.adoc[] +*** xref:reference:actuator/http-exchanges.adoc[] +*** xref:reference:actuator/process-monitoring.adoc[] +*** xref:reference:actuator/cloud-foundry.adoc[] + + diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata/annotation-processor.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/configuration-metadata/annotation-processor.adoc similarity index 75% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata/annotation-processor.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/configuration-metadata/annotation-processor.adoc index 89360f2f73d3..976ef570353a 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata/annotation-processor.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/configuration-metadata/annotation-processor.adoc @@ -1,41 +1,43 @@ [[appendix.configuration-metadata.annotation-processor]] -== Generating Your Own Metadata by Using the Annotation Processor += Generating Your Own Metadata by Using the Annotation Processor + You can easily generate your own configuration metadata file from items annotated with `@ConfigurationProperties` by using the `spring-boot-configuration-processor` jar. The jar includes a Java annotation processor which is invoked as your project is compiled. [[appendix.configuration-metadata.annotation-processor.configuring]] -=== Configuring the Annotation Processor +== Configuring the Annotation Processor + To use the processor, include a dependency on `spring-boot-configuration-processor`. With Maven the dependency should be declared as optional, as shown in the following example: -[source,xml,indent=0,subs="verbatim"] +[source,xml] ---- - - org.springframework.boot - spring-boot-configuration-processor - true - + + org.springframework.boot + spring-boot-configuration-processor + true + ---- With Gradle, the dependency should be declared in the `annotationProcessor` configuration, as shown in the following example: -[source,gradle,indent=0,subs="verbatim"] +[source,gradle] ---- - dependencies { - annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" - } +dependencies { + annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" +} ---- If you are using an `additional-spring-configuration-metadata.json` file, the `compileJava` task should be configured to depend on the `processResources` task, as shown in the following example: -[source,gradle,indent=0,subs="verbatim"] +[source,gradle] ---- - tasks.named('compileJava') { - inputs.files(tasks.named('processResources')) - } +tasks.named('compileJava') { + inputs.files(tasks.named('processResources')) +} ---- This dependency ensures that the additional metadata is available when the annotation processor runs during compilation. @@ -47,15 +49,15 @@ There are several ways to do this. With Maven, you can configure the `maven-apt-plugin` explicitly and add the dependency to the annotation processor only there. You could also let the AspectJ plugin run all the processing and disable annotation processing in the `maven-compiler-plugin` configuration, as follows: -[source,xml,indent=0,subs="verbatim"] +[source,xml] ---- - - org.apache.maven.plugins - maven-compiler-plugin - - none - - + + org.apache.maven.plugins + maven-compiler-plugin + + none + + ---- ==== @@ -69,7 +71,8 @@ If you are not using this attribute, and annotation processors are picked up by [[appendix.configuration-metadata.annotation-processor.automatic-metadata-generation]] -=== Automatic Metadata Generation +== Automatic Metadata Generation + The processor picks up both classes and methods that are annotated with `@ConfigurationProperties`. If the class has a single parameterized constructor, one property is created per constructor parameter, unless the constructor is annotated with `@Autowired`. @@ -79,36 +82,38 @@ The annotation processor also supports the use of the `@Data`, `@Value`, `@Gette Consider the following example: -include::code:MyServerProperties[] +include-code::MyServerProperties[] This exposes three properties where `my.server.name` has no default and `my.server.ip` and `my.server.port` defaults to `"127.0.0.1"` and `9797` respectively. The Javadoc on fields is used to populate the `description` attribute. For instance, the description of `my.server.ip` is "IP address to listen to.". NOTE: You should only use plain text with `@ConfigurationProperties` field Javadoc, since they are not processed before being added to the JSON. +If you use `@ConfigurationProperties` with record class then record components' descriptions should be provided via class-level Javadoc tag `@param` (there are no explicit instance fields in record classes to put regular field-level Javadocs on). + The annotation processor applies a number of heuristics to extract the default value from the source model. Default values have to be provided statically. In particular, do not refer to a constant defined in another class. Also, the annotation processor cannot auto-detect default values for ``Enum``s and ``Collections``s. -For cases where the default value could not be detected, <> should be provided. +For cases where the default value could not be detected, xref:configuration-metadata/annotation-processor.adoc#appendix.configuration-metadata.annotation-processor.adding-additional-metadata[manual metadata] should be provided. Consider the following example: -include::code:MyMessagingProperties[] +include-code::MyMessagingProperties[] -In order to document default values for properties in the class above, you could add the following content to <>: +In order to document default values for properties in the class above, you could add the following content to xref:configuration-metadata/annotation-processor.adoc#appendix.configuration-metadata.annotation-processor.adding-additional-metadata[the manual metadata of the module]: -[source,json,indent=0,subs="verbatim"] +[source,json] ---- - {"properties": [ - { - "name": "my.messaging.addresses", - "defaultValue": ["a", "b"] - }, - { - "name": "my.messaging.container-type", - "defaultValue": "simple" - } - ]} +{"properties": [ + { + "name": "my.messaging.addresses", + "defaultValue": ["a", "b"] + }, + { + "name": "my.messaging.container-type", + "defaultValue": "simple" + } +]} ---- NOTE: Only the `name` of the property is required to document additional metadata for existing properties. @@ -116,22 +121,24 @@ NOTE: Only the `name` of the property is required to document additional metadat [[appendix.configuration-metadata.annotation-processor.automatic-metadata-generation.nested-properties]] -==== Nested Properties +=== Nested Properties + The annotation processor automatically considers inner classes as nested properties. Rather than documenting the `ip` and `port` at the root of the namespace, we could create a sub-namespace for it. Consider the updated example: -include::code:MyServerProperties[] +include-code::MyServerProperties[] The preceding example produces metadata information for `my.server.name`, `my.server.host.ip`, and `my.server.host.port` properties. -You can use the `@NestedConfigurationProperty` annotation on a field to indicate that a regular (non-inner) class should be treated as if it were nested. +You can use the `@NestedConfigurationProperty` annotation on a field or a getter method to indicate that a regular (non-inner) class should be treated as if it were nested. TIP: This has no effect on collections and maps, as those types are automatically identified, and a single metadata property is generated for each of them. [[appendix.configuration-metadata.annotation-processor.adding-additional-metadata]] -=== Adding Additional Metadata +== Adding Additional Metadata + Spring Boot's configuration file handling is quite flexible, and it is often the case that properties may exist that are not bound to a `@ConfigurationProperties` bean. You may also need to tune some attributes of an existing key. To support such cases and let you provide custom "hints", the annotation processor automatically merges items from `META-INF/additional-spring-configuration-metadata.json` into the main metadata file. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata/format.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/configuration-metadata/format.adoc similarity index 83% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata/format.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/configuration-metadata/format.adoc index 589851084265..47e99f521d85 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata/format.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/configuration-metadata/format.adoc @@ -1,78 +1,79 @@ [[appendix.configuration-metadata.format]] -== Metadata Format += Metadata Format + Configuration metadata files are located inside jars under `META-INF/spring-configuration-metadata.json`. They use a JSON format with items categorized under either "`groups`" or "`properties`" and additional values hints categorized under "hints", as shown in the following example: -[source,json,indent=0,subs="verbatim"] +[source,json] ---- - {"groups": [ - { - "name": "server", - "type": "org.springframework.boot.autoconfigure.web.ServerProperties", - "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties" - }, - { - "name": "spring.jpa.hibernate", - "type": "org.springframework.boot.autoconfigure.orm.jpa.JpaProperties$Hibernate", - "sourceType": "org.springframework.boot.autoconfigure.orm.jpa.JpaProperties", - "sourceMethod": "getHibernate()" - } - ... - ],"properties": [ - { - "name": "server.port", - "type": "java.lang.Integer", - "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties" - }, - { - "name": "server.address", - "type": "java.net.InetAddress", - "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties" - }, - { - "name": "spring.jpa.hibernate.ddl-auto", - "type": "java.lang.String", - "description": "DDL mode. This is actually a shortcut for the \"hibernate.hbm2ddl.auto\" property.", - "sourceType": "org.springframework.boot.autoconfigure.orm.jpa.JpaProperties$Hibernate" - } - ... - ],"hints": [ - { - "name": "spring.jpa.hibernate.ddl-auto", - "values": [ - { - "value": "none", - "description": "Disable DDL handling." - }, - { - "value": "validate", - "description": "Validate the schema, make no changes to the database." - }, - { - "value": "update", - "description": "Update the schema if necessary." - }, - { - "value": "create", - "description": "Create the schema and destroy previous data." - }, - { - "value": "create-drop", - "description": "Create and then destroy the schema at the end of the session." - } - ] - } - ]} +{"groups": [ + { + "name": "server", + "type": "org.springframework.boot.autoconfigure.web.ServerProperties", + "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties" + }, + { + "name": "spring.jpa.hibernate", + "type": "org.springframework.boot.autoconfigure.orm.jpa.JpaProperties$Hibernate", + "sourceType": "org.springframework.boot.autoconfigure.orm.jpa.JpaProperties", + "sourceMethod": "getHibernate()" + } + ... +],"properties": [ + { + "name": "server.port", + "type": "java.lang.Integer", + "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties" + }, + { + "name": "server.address", + "type": "java.net.InetAddress", + "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties" + }, + { + "name": "spring.jpa.hibernate.ddl-auto", + "type": "java.lang.String", + "description": "DDL mode. This is actually a shortcut for the \"hibernate.hbm2ddl.auto\" property.", + "sourceType": "org.springframework.boot.autoconfigure.orm.jpa.JpaProperties$Hibernate" + } + ... +],"hints": [ + { + "name": "spring.jpa.hibernate.ddl-auto", + "values": [ + { + "value": "none", + "description": "Disable DDL handling." + }, + { + "value": "validate", + "description": "Validate the schema, make no changes to the database." + }, + { + "value": "update", + "description": "Update the schema if necessary." + }, + { + "value": "create", + "description": "Create the schema and destroy previous data." + }, + { + "value": "create-drop", + "description": "Create and then destroy the schema at the end of the session." + } + ] + } +]} ---- Each "`property`" is a configuration item that the user specifies with a given value. For example, `server.port` and `server.address` might be specified in your `application.properties`/`application.yaml`, as follows: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - server: - port: 9090 - address: 127.0.0.1 +server: + port: 9090 + address: 127.0.0.1 ---- The "`groups`" are higher level items that do not themselves specify a value but instead provide a contextual grouping for properties. @@ -87,7 +88,8 @@ For example, when a developer is configuring the configprop:spring.jpa.hibernate [[appendix.configuration-metadata.format.group]] -=== Group Attributes +== Group Attributes + The JSON object contained in the `groups` array can contain the attributes shown in the following table: [cols="1,1,4"] @@ -128,7 +130,8 @@ The JSON object contained in the `groups` array can contain the attributes shown [[appendix.configuration-metadata.format.property]] -=== Property Attributes +== Property Attributes + The JSON object contained in the `properties` array can contain the attributes described in the following table: [cols="1,1,4"] @@ -198,6 +201,11 @@ The JSON object contained in the `deprecation` attribute of each `properties` el | String | The full name of the property that _replaces_ this deprecated property. If there is no replacement for this property, it may be omitted. + +| `since` +| String +| The version in which the property became deprecated. + Can be omitted. |=== NOTE: Prior to Spring Boot 1.3, a single `deprecated` boolean attribute can be used instead of the `deprecation` element. @@ -208,7 +216,7 @@ Deprecation can also be specified declaratively in code by adding the `@Deprecat For instance, assume that the `my.app.target` property was confusing and was renamed to `my.app.name`. The following example shows how to handle that situation: -include::code:MyProperties[] +include-code::MyProperties[] NOTE: There is no way to set a `level`. `warning` is always assumed, since code is still handling the property. @@ -221,7 +229,8 @@ Doing so is particularly useful when a `replacement` is provided. [[appendix.configuration-metadata.format.hints]] -=== Hint Attributes +== Hint Attributes + The JSON object contained in the `hints` array can contain the attributes shown in the following table: [cols="1,1,4"] @@ -284,7 +293,8 @@ The JSON object contained in the `providers` attribute of each `hint` element ca [[appendix.configuration-metadata.format.repeated-items]] -=== Repeated Metadata Items +== Repeated Metadata Items + Objects with the same "`property`" and "`group`" name can appear multiple times within a metadata file. For example, you could bind two separate classes to the same prefix, with each having potentially overlapping property names. While the same names appearing in the metadata multiple times should not be common, consumers of metadata should take care to ensure that they support it. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/configuration-metadata/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/configuration-metadata/index.adoc new file mode 100644 index 000000000000..fb2d4281652a --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/configuration-metadata/index.adoc @@ -0,0 +1,9 @@ +[appendix] +[[appendix.configuration-metadata]] += Configuration Metadata + +Spring Boot jars include metadata files that provide details of all supported configuration properties. +The files are designed to let IDE developers offer contextual help and "`code completion`" as users are working with `application.properties` or `application.yaml` files. + +The majority of the metadata file is generated automatically at compile time by processing all items annotated with `@ConfigurationProperties`. +However, it is possible to xref:configuration-metadata/annotation-processor.adoc#appendix.configuration-metadata.annotation-processor.adding-additional-metadata[write part of the metadata manually] for corner cases or more advanced use cases. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/configuration-metadata/manual-hints.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/configuration-metadata/manual-hints.adoc new file mode 100644 index 000000000000..587e3f98a3de --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/configuration-metadata/manual-hints.adoc @@ -0,0 +1,375 @@ +[[appendix.configuration-metadata.manual-hints]] += Providing Manual Hints + +To improve the user experience and further assist the user in configuring a given property, you can provide additional metadata that: + +* Describes the list of potential values for a property. +* Associates a provider, to attach a well defined semantic to a property, so that a tool can discover the list of potential values based on the project's context. + + + +[[appendix.configuration-metadata.manual-hints.value-hint]] +== Value Hint + +The `name` attribute of each hint refers to the `name` of a property. +In the xref:configuration-metadata/format.adoc[initial example shown earlier], we provide five values for the `spring.jpa.hibernate.ddl-auto` property: `none`, `validate`, `update`, `create`, and `create-drop`. +Each value may have a description as well. + +If your property is of type `Map`, you can provide hints for both the keys and the values (but not for the map itself). +The special `.keys` and `.values` suffixes must refer to the keys and the values, respectively. + +Assume a `my.contexts` maps magic `String` values to an integer, as shown in the following example: + +include-code::MyProperties[] + +The magic values are (in this example) are `sample1` and `sample2`. +In order to offer additional content assistance for the keys, you could add the following JSON to xref:configuration-metadata/annotation-processor.adoc#appendix.configuration-metadata.annotation-processor.adding-additional-metadata[the manual metadata of the module]: + +[source,json] +---- +{"hints": [ + { + "name": "my.contexts.keys", + "values": [ + { + "value": "sample1" + }, + { + "value": "sample2" + } + ] + } +]} +---- + +TIP: We recommend that you use an `Enum` for those two values instead. +If your IDE supports it, this is by far the most effective approach to auto-completion. + + + +[[appendix.configuration-metadata.manual-hints.value-providers]] +== Value Providers + +Providers are a powerful way to attach semantics to a property. +In this section, we define the official providers that you can use for your own hints. +However, your favorite IDE may implement some of these or none of them. +Also, it could eventually provide its own. + +NOTE: As this is a new feature, IDE vendors must catch up with how it works. +Adoption times naturally vary. + +The following table summarizes the list of supported providers: + +[cols="2,4"] +|=== +| Name | Description + +| `any` +| Permits any additional value to be provided. + +| `class-reference` +| Auto-completes the classes available in the project. + Usually constrained by a base class that is specified by the `target` parameter. + +| `handle-as` +| Handles the property as if it were defined by the type defined by the mandatory `target` parameter. + +| `logger-name` +| Auto-completes valid logger names and xref:reference:features/logging.adoc#features.logging.log-groups[logger groups]. + Typically, package and class names available in the current project can be auto-completed as well as defined groups. + +| `spring-bean-reference` +| Auto-completes the available bean names in the current project. + Usually constrained by a base class that is specified by the `target` parameter. + +| `spring-profile-name` +| Auto-completes the available Spring profile names in the project. +|=== + +TIP: Only one provider can be active for a given property, but you can specify several providers if they can all manage the property _in some way_. +Make sure to place the most powerful provider first, as the IDE must use the first one in the JSON section that it can handle. +If no provider for a given property is supported, no special content assistance is provided, either. + + + +[[appendix.configuration-metadata.manual-hints.value-providers.any]] +=== Any + +The special **any** provider value permits any additional values to be provided. +Regular value validation based on the property type should be applied if this is supported. + +This provider is typically used if you have a list of values and any extra values should still be considered as valid. + +The following example offers `on` and `off` as auto-completion values for `system.state`: + +[source,json] +---- +{"hints": [ + { + "name": "system.state", + "values": [ + { + "value": "on" + }, + { + "value": "off" + } + ], + "providers": [ + { + "name": "any" + } + ] + } +]} +---- + +Note that, in the preceding example, any other value is also allowed. + + + +[[appendix.configuration-metadata.manual-hints.value-providers.class-reference]] +=== Class Reference + +The **class-reference** provider auto-completes classes available in the project. +This provider supports the following parameters: + +[cols="1,1,2,4"] +|=== +| Parameter | Type | Default value | Description + +| `target` +| `String` (`Class`) +| _none_ +| The fully qualified name of the class that should be assignable to the chosen value. + Typically used to filter out-non candidate classes. + Note that this information can be provided by the type itself by exposing a class with the appropriate upper bound. + +| `concrete` +| `boolean` +| true +| Specify whether only concrete classes are to be considered as valid candidates. +|=== + + +The following metadata snippet corresponds to the standard `server.servlet.jsp.class-name` property that defines the `JspServlet` class name to use: + +[source,json] +---- +{"hints": [ + { + "name": "server.servlet.jsp.class-name", + "providers": [ + { + "name": "class-reference", + "parameters": { + "target": "jakarta.servlet.http.HttpServlet" + } + } + ] + } +]} +---- + + + +[[appendix.configuration-metadata.manual-hints.value-providers.handle-as]] +=== Handle As + +The **handle-as** provider lets you substitute the type of the property to a more high-level type. +This typically happens when the property has a `java.lang.String` type, because you do not want your configuration classes to rely on classes that may not be on the classpath. +This provider supports the following parameters: + +[cols="1,1,2,4"] +|=== +| Parameter | Type | Default value | Description + +| **`target`** +| `String` (`Class`) +| _none_ +| The fully qualified name of the type to consider for the property. + This parameter is mandatory. +|=== + +The following types can be used: + +* Any `java.lang.Enum`: Lists the possible values for the property. + (We recommend defining the property with the `Enum` type, as no further hint should be required for the IDE to auto-complete the values) +* `java.nio.charset.Charset`: Supports auto-completion of charset/encoding values (such as `UTF-8`) +* `java.util.Locale`: auto-completion of locales (such as `en_US`) +* `org.springframework.util.MimeType`: Supports auto-completion of content type values (such as `text/plain`) +* `org.springframework.core.io.Resource`: Supports auto-completion of Spring’s Resource abstraction to refer to a file on the filesystem or on the classpath (such as `classpath:/sample.properties`) + +TIP: If multiple values can be provided, use a `Collection` or _Array_ type to teach the IDE about it. + +The following metadata snippet corresponds to the standard `spring.liquibase.change-log` property that defines the path to the changelog to use. +It is actually used internally as a `org.springframework.core.io.Resource` but cannot be exposed as such, because we need to keep the original String value to pass it to the Liquibase API. + +[source,json] +---- +{"hints": [ + { + "name": "spring.liquibase.change-log", + "providers": [ + { + "name": "handle-as", + "parameters": { + "target": "org.springframework.core.io.Resource" + } + } + ] + } +]} +---- + + + +[[appendix.configuration-metadata.manual-hints.value-providers.logger-name]] +=== Logger Name + +The **logger-name** provider auto-completes valid logger names and xref:reference:features/logging.adoc#features.logging.log-groups[logger groups]. +Typically, package and class names available in the current project can be auto-completed. +If groups are enabled (default) and if a custom logger group is identified in the configuration, auto-completion for it should be provided. +Specific frameworks may have extra magic logger names that can be supported as well. + +This provider supports the following parameters: + +[cols="1,1,2,4"] +|=== +| Parameter | Type | Default value | Description + +| `group` +| `boolean` +| `true` +| Specify whether known groups should be considered. +|=== + +Since a logger name can be any arbitrary name, this provider should allow any value but could highlight valid package and class names that are not available in the project's classpath. + +The following metadata snippet corresponds to the standard `logging.level` property. +Keys are _logger names_, and values correspond to the standard log levels or any custom level. +As Spring Boot defines a few logger groups out-of-the-box, dedicated value hints have been added for those. + +[source,json] +---- +{"hints": [ + { + "name": "logging.level.keys", + "values": [ + { + "value": "root", + "description": "Root logger used to assign the default logging level." + }, + { + "value": "sql", + "description": "SQL logging group including Hibernate SQL logger." + }, + { + "value": "web", + "description": "Web logging group including codecs." + } + ], + "providers": [ + { + "name": "logger-name" + } + ] + }, + { + "name": "logging.level.values", + "values": [ + { + "value": "trace" + }, + { + "value": "debug" + }, + { + "value": "info" + }, + { + "value": "warn" + }, + { + "value": "error" + }, + { + "value": "fatal" + }, + { + "value": "off" + } + + ], + "providers": [ + { + "name": "any" + } + ] + } +]} +---- + + + +[[appendix.configuration-metadata.manual-hints.value-providers.spring-bean-reference]] +=== Spring Bean Reference + +The **spring-bean-reference** provider auto-completes the beans that are defined in the configuration of the current project. +This provider supports the following parameters: + +[cols="1,1,2,4"] +|=== +| Parameter | Type | Default value | Description + +| `target` +| `String` (`Class`) +| _none_ +| The fully qualified name of the bean class that should be assignable to the candidate. + Typically used to filter out non-candidate beans. +|=== + +The following metadata snippet corresponds to the standard `spring.jmx.server` property that defines the name of the `MBeanServer` bean to use: + +[source,json] +---- +{"hints": [ + { + "name": "spring.jmx.server", + "providers": [ + { + "name": "spring-bean-reference", + "parameters": { + "target": "javax.management.MBeanServer" + } + } + ] + } +]} +---- + +NOTE: The binder is not aware of the metadata. +If you provide that hint, you still need to transform the bean name into an actual Bean reference using by the `ApplicationContext`. + + + +[[appendix.configuration-metadata.manual-hints.value-providers.spring-profile-name]] +=== Spring Profile Name + +The **spring-profile-name** provider auto-completes the Spring profiles that are defined in the configuration of the current project. + +The following metadata snippet corresponds to the standard `spring.profiles.active` property that defines the name of the Spring profile(s) to enable: + +[source,json] +---- +{"hints": [ + { + "name": "spring.profiles.active", + "providers": [ + { + "name": "spring-profile-name" + } + ] + } +]} +---- diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/alternatives.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/alternatives.adoc similarity index 91% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/alternatives.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/alternatives.adoc index 30d4bbc6f2d6..e94a71dc77b6 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/alternatives.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/alternatives.adoc @@ -1,5 +1,6 @@ [[appendix.executable-jar.alternatives]] -== Alternative Single Jar Solutions += Alternative Single Jar Solutions + If the preceding restrictions mean that you cannot use Spring Boot Loader, consider the following alternatives: * https://maven.apache.org/plugins/maven-shade-plugin/[Maven Shade Plugin] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/index.adoc new file mode 100644 index 000000000000..2c6940ea1762 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/index.adoc @@ -0,0 +1,8 @@ +[appendix] +[[appendix.executable-jar]] += The Executable Jar Format + +The `spring-boot-loader` modules lets Spring Boot support executable jar and war files. +If you use the Maven plugin or the Gradle plugin, executable jars are automatically generated, and you generally do not need to know the details of how they work. + +If you need to create executable jars from a different build system or if you are just curious about the underlying technology, this appendix provides some background. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/jarfile-class.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/jarfile-class.adoc new file mode 100644 index 000000000000..acf4a073ded2 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/jarfile-class.adoc @@ -0,0 +1,36 @@ +[[appendix.executable-jar.jarfile-class]] += Spring Boot's "`NestedJarFile`" Class + +The core class used to support loading nested jars is `org.springframework.boot.loader.jar.NestedJarFile`. +It lets you load jar content from nested child jar data. +When first loaded, the location of each `JarEntry` is mapped to a physical file offset of the outer jar, as shown in the following example: + +[source] +---- +myapp.jar ++-------------------+-------------------------+ +| /BOOT-INF/classes | /BOOT-INF/lib/mylib.jar | +|+-----------------+||+-----------+----------+| +|| A.class ||| B.class | C.class || +|+-----------------+||+-----------+----------+| ++-------------------+-------------------------+ + ^ ^ ^ + 0063 3452 3980 +---- + +The preceding example shows how `A.class` can be found in `/BOOT-INF/classes` in `myapp.jar` at position `0063`. +`B.class` from the nested jar can actually be found in `myapp.jar` at position `3452`, and `C.class` is at position `3980`. + +Armed with this information, we can load specific nested entries by seeking to the appropriate part of the outer jar. +We do not need to unpack the archive, and we do not need to read all entry data into memory. + + + +[[appendix.executable-jar.jarfile-class.compatibility]] +== Compatibility With the Standard Java "`JarFile`" + +Spring Boot Loader strives to remain compatible with existing code and libraries. +`org.springframework.boot.loader.jar.NestedJarFile` extends from `java.util.jar.JarFile` and should work as a drop-in replacement. + +Nested JAR URLs of the form `jar:nested:/path/myjar.jar/!BOOT-INF/lib/mylib.jar!/B.class` are supported and open a connection compatible with `java.net.JarURLConnection`. +These can be used with Java's `URLClassLoader`. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/launching.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/launching.adoc new file mode 100644 index 000000000000..30b51f505953 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/launching.adoc @@ -0,0 +1,41 @@ +[[appendix.executable-jar.launching]] += Launching Executable Jars + +The `org.springframework.boot.loader.launch.Launcher` class is a special bootstrap class that is used as an executable jar's main entry point. +It is the actual `Main-Class` in your jar file, and it is used to setup an appropriate `ClassLoader` and ultimately call your `main()` method. + +There are three launcher subclasses (`JarLauncher`, `WarLauncher`, and `PropertiesLauncher`). +Their purpose is to load resources (`.class` files and so on) from nested jar files or war files in directories (as opposed to those explicitly on the classpath). +In the case of `JarLauncher` and `WarLauncher`, the nested paths are fixed. +`JarLauncher` looks in `BOOT-INF/lib/`, and `WarLauncher` looks in `WEB-INF/lib/` and `WEB-INF/lib-provided/`. +You can add extra jars in those locations if you want more. + +The `PropertiesLauncher` looks in `BOOT-INF/lib/` in your application archive by default. +You can add additional locations by setting an environment variable called `LOADER_PATH` or `loader.path` in `loader.properties` (which is a comma-separated list of directories, archives, or directories within archives). + + + +[[appendix.executable-jar.launching.manifest]] +== Launcher Manifest + +You need to specify an appropriate `Launcher` as the `Main-Class` attribute of `META-INF/MANIFEST.MF`. +The actual class that you want to launch (that is, the class that contains a `main` method) should be specified in the `Start-Class` attribute. + +The following example shows a typical `MANIFEST.MF` for an executable jar file: + +[source,manifest] +---- +Main-Class: org.springframework.boot.loader.launch.JarLauncher +Start-Class: com.mycompany.project.MyApplication +---- + +For a war file, it would be as follows: + +[source,manifest] +---- +Main-Class: org.springframework.boot.loader.launch.WarLauncher +Start-Class: com.mycompany.project.MyApplication +---- + +NOTE: You need not specify `Class-Path` entries in your manifest file. +The classpath is deduced from the nested jars. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/nested-jars.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/nested-jars.adoc new file mode 100644 index 000000000000..e08d8e9f96f5 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/nested-jars.adoc @@ -0,0 +1,149 @@ +[[appendix.executable-jar.nested-jars]] += Nested JARs + +Java does not provide any standard way to load nested jar files (that is, jar files that are themselves contained within a jar). +This can be problematic if you need to distribute a self-contained application that can be run from the command line without unpacking. + +To solve this problem, many developers use "`shaded`" jars. +A shaded jar packages all classes, from all jars, into a single "`uber jar`". +The problem with shaded jars is that it becomes hard to see which libraries are actually in your application. +It can also be problematic if the same filename is used (but with different content) in multiple jars. +Spring Boot takes a different approach and lets you actually nest jars directly. + + + +[[appendix.executable-jar.nested-jars.jar-structure]] +== The Executable Jar File Structure + +Spring Boot Loader-compatible jar files should be structured in the following way: + +[source] +---- +example.jar + | + +-META-INF + | +-MANIFEST.MF + +-org + | +-springframework + | +-boot + | +-loader + | +- + +-BOOT-INF + +-classes + | +-mycompany + | +-project + | +-YourClasses.class + +-lib + +-dependency1.jar + +-dependency2.jar +---- + +Application classes should be placed in a nested `BOOT-INF/classes` directory. +Dependencies should be placed in a nested `BOOT-INF/lib` directory. + + + +[[appendix.executable-jar.nested-jars.war-structure]] +== The Executable War File Structure + +Spring Boot Loader-compatible war files should be structured in the following way: + +[source] +---- +example.war + | + +-META-INF + | +-MANIFEST.MF + +-org + | +-springframework + | +-boot + | +-loader + | +- + +-WEB-INF + +-classes + | +-com + | +-mycompany + | +-project + | +-YourClasses.class + +-lib + | +-dependency1.jar + | +-dependency2.jar + +-lib-provided + +-servlet-api.jar + +-dependency3.jar +---- + +Dependencies should be placed in a nested `WEB-INF/lib` directory. +Any dependencies that are required when running embedded but are not required when deploying to a traditional web container should be placed in `WEB-INF/lib-provided`. + + + +[[appendix.executable-jar.nested-jars.index-files]] +== Index Files + +Spring Boot Loader-compatible jar and war archives can include additional index files under the `BOOT-INF/` directory. +A `classpath.idx` file can be provided for both jars and wars, and it provides the ordering that jars should be added to the classpath. +The `layers.idx` file can be used only for jars, and it allows a jar to be split into logical layers for Docker/OCI image creation. + +Index files follow a YAML compatible syntax so that they can be easily parsed by third-party tools. +These files, however, are _not_ parsed internally as YAML and they must be written in exactly the formats described below in order to be used. + + + +[[appendix.executable-jar.nested-jars.classpath-index]] +== Classpath Index + +The classpath index file can be provided in `BOOT-INF/classpath.idx`. +Typically, it is generated automatically by Spring Boot's Maven and Gradle build plugins. +It provides a list of jar names (including the directory) in the order that they should be added to the classpath. +When generated by the build plugins, this classpath ordering matches that used by the build system for running and testing the application. +Each line must start with dash space (`"-·"`) and names must be in double quotes. + +For example, given the following jar: + +[source] +---- +example.jar + | + +-META-INF + | +-... + +-BOOT-INF + +-classes + | +... + +-lib + +-dependency1.jar + +-dependency2.jar +---- + +The index file would look like this: + +[source] +---- +- "BOOT-INF/lib/dependency2.jar" +- "BOOT-INF/lib/dependency1.jar" +---- + + + +[[appendix.executable-jar.nested-jars.layer-index]] +== Layer Index + +The layers index file can be provided in `BOOT-INF/layers.idx`. +It provides a list of layers and the parts of the jar that should be contained within them. +Layers are written in the order that they should be added to the Docker/OCI image. +Layers names are written as quoted strings prefixed with dash space (`"-·"`) and with a colon (`":"`) suffix. +Layer content is either a file or directory name written as a quoted string prefixed by space space dash space (`"··-·"`). +A directory name ends with `/`, a file name does not. +When a directory name is used it means that all files inside that directory are in the same layer. + +A typical example of a layers index would be: + +[source] +---- +- "dependencies": + - "BOOT-INF/lib/dependency1.jar" + - "BOOT-INF/lib/dependency2.jar" +- "application": + - "BOOT-INF/classes/" + - "META-INF/" +---- diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/property-launcher.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/property-launcher.adoc similarity index 97% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/property-launcher.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/property-launcher.adoc index 675a2bc27801..2ecc34fda377 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/property-launcher.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/property-launcher.adoc @@ -1,5 +1,6 @@ [[appendix.executable-jar.property-launcher]] -== PropertiesLauncher Features += PropertiesLauncher Features + `PropertiesLauncher` has a few special features that can be enabled with external properties (System properties, environment variables, manifest entries, or `loader.properties`). The following table describes these properties: @@ -64,7 +65,7 @@ When specified as environment variables or manifest entries, the following names | `LOADER_SYSTEM` |=== -TIP: Build plugins automatically move the `Main-Class` attribute to `Start-Class` when the fat jar is built. +TIP: Build plugins automatically move the `Main-Class` attribute to `Start-Class` when the uber jar is built. If you use that, specify the name of the class to launch by using the `Main-Class` attribute and leaving out `Start-Class`. The following rules apply to working with `PropertiesLauncher`: diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/restrictions.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/restrictions.adoc similarity index 96% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/restrictions.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/restrictions.adoc index 0d35f1c2944d..f0745b21d67b 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/restrictions.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/restrictions.adoc @@ -1,5 +1,6 @@ [[appendix.executable-jar.restrictions]] -== Executable Jar Restrictions += Executable Jar Restrictions + You need to consider the following restrictions when working with a Spring Boot Loader packaged application: diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/partials/nav-specification.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/partials/nav-specification.adoc new file mode 100644 index 000000000000..1f48d4c8b99b --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/partials/nav-specification.adoc @@ -0,0 +1,14 @@ +* Specifications + +** xref:specification:configuration-metadata/index.adoc[] +*** xref:specification:configuration-metadata/format.adoc[] +*** xref:specification:configuration-metadata/manual-hints.adoc[] +*** xref:specification:configuration-metadata/annotation-processor.adoc[] + +** xref:specification:executable-jar/index.adoc[] +*** xref:specification:executable-jar/nested-jars.adoc[] +*** xref:specification:executable-jar/jarfile-class.adoc[] +*** xref:specification:executable-jar/launching.adoc[] +*** xref:specification:executable-jar/property-launcher.adoc[] +*** xref:specification:executable-jar/restrictions.adoc[] +*** xref:specification:executable-jar/alternatives.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/tutorial/pages/first-application/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/tutorial/pages/first-application/index.adoc new file mode 100644 index 000000000000..a6a26ab0d56e --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/tutorial/pages/first-application/index.adoc @@ -0,0 +1,536 @@ +[[getting-started.first-application]] += Developing Your First Spring Boot Application + +This section describes how to develop a small "`Hello World!`" web application that highlights some of Spring Boot's key features. +You can choose between Maven or Gradle as the build system. + +[TIP] +==== +The https://spring.io[spring.io] website contains many "`Getting Started`" https://spring.io/guides[guides] that use Spring Boot. +If you need to solve a specific problem, check there first. + +You can shortcut the steps below by going to https://start.spring.io and choosing the "Web" starter from the dependencies searcher. +Doing so generates a new project structure so that you can xref:tutorial:first-application/index.adoc#getting-started.first-application.code[start coding right away]. +Check the https://github.com/spring-io/start.spring.io/blob/main/USING.adoc[start.spring.io user guide] for more details. +==== + + + +[[getting-started.first-application.prerequisites]] +== Prerequisites + +Before we begin, open a terminal and run the following commands to ensure that you have a valid version of Java installed: + +[source,shell] +---- +$ java -version +openjdk version "17.0.4.1" 2022-08-12 LTS +OpenJDK Runtime Environment (build 17.0.4.1+1-LTS) +OpenJDK 64-Bit Server VM (build 17.0.4.1+1-LTS, mixed mode, sharing) +---- + +NOTE: This sample needs to be created in its own directory. +Subsequent instructions assume that you have created a suitable directory and that it is your current directory. + + + +[[getting-started.first-application.prerequisites.maven]] +=== Maven + +If you want to use Maven, ensure that you have Maven installed: + +[source,shell] +---- +$ mvn -v +Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0) +Maven home: usr/Users/developer/tools/maven/3.8.5 +Java version: 17.0.4.1, vendor: BellSoft, runtime: /Users/developer/sdkman/candidates/java/17.0.4.1-librca +---- + + + +[[getting-started.first-application.prerequisites.gradle]] +=== Gradle + +If you want to use Gradle, ensure that you have Gradle installed: + +[source,shell] +---- +$ gradle --version + +------------------------------------------------------------ +Gradle 8.1.1 +------------------------------------------------------------ + +Build time: 2023-04-21 12:31:26 UTC +Revision: 1cf537a851c635c364a4214885f8b9798051175b + +Kotlin: 1.8.10 +Groovy: 3.0.15 +Ant: Apache Ant(TM) version 1.10.11 compiled on July 10 2021 +JVM: 17.0.7 (BellSoft 17.0.7+7-LTS) +OS: Linux 6.2.12-200.fc37.aarch64 aarch64 +---- + + + +[[getting-started.first-application.pom]] +== Setting Up the Project With Maven + +We need to start by creating a Maven `pom.xml` file. +The `pom.xml` is the recipe that is used to build your project. +Open your favorite text editor and add the following: + +[source,xml,subs="verbatim,attributes"] +---- + + + 4.0.0 + + com.example + myproject + 0.0.1-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + {version-spring-boot} + + + + +ifeval::["{artifact-release-type}" != "release"] + + + + spring-snapshots + https://repo.spring.io/snapshot + true + + + spring-milestones + https://repo.spring.io/milestone + + + + + spring-snapshots + https://repo.spring.io/snapshot + + + spring-milestones + https://repo.spring.io/milestone + + +endif::[] + +---- + +The preceding listing should give you a working build. +You can test it by running `mvn package` (for now, you can ignore the "`jar will be empty - no content was marked for inclusion!`" warning). + +NOTE: At this point, you could import the project into an IDE (most modern Java IDEs include built-in support for Maven). +For simplicity, we continue to use a plain text editor for this example. + + + +[[getting-started.first-application.gradle]] +== Setting Up the Project With Gradle + +We need to start by creating a Gradle `build.gradle` file. +The `build.gradle` is the build script that is used to build your project. +Open your favorite text editor and add the following: + +[source,gradle,subs="verbatim,attributes"] +---- +plugins { + id 'java' + id 'org.springframework.boot' version '{version-spring-boot}' +} + +apply plugin: 'io.spring.dependency-management' + +group = 'com.example' +version = '0.0.1-SNAPSHOT' +sourceCompatibility = '17' + +repositories { + mavenCentral() +ifeval::["{artifact-release-type}" != "release"] + maven { url 'https://repo.spring.io/milestone' } + maven { url 'https://repo.spring.io/snapshot' } +endif::[] +} + +dependencies { +} +---- + +The preceding listing should give you a working build. +You can test it by running `gradle classes`. + +NOTE: At this point, you could import the project into an IDE (most modern Java IDEs include built-in support for Gradle). +For simplicity, we continue to use a plain text editor for this example. + + + +[[getting-started.first-application.dependencies]] +== Adding Classpath Dependencies + +Spring Boot provides a number of starters that let you add jars to your classpath. +Starters provide dependencies that you are likely to need when developing a specific type of application. + + + +[[getting-started.first-application.dependencies.maven]] +=== Maven + +Most Spring Boot applications use the `spring-boot-starter-parent` in the `parent` section of the POM. +The `spring-boot-starter-parent` is a special starter that provides useful Maven defaults. +It also provides a xref:reference:using/build-systems.adoc#using.build-systems.dependency-management[`dependency-management`] section so that you can omit `version` tags for "`blessed`" dependencies. + +Since we are developing a web application, we add a `spring-boot-starter-web` dependency. +Before that, we can look at what we currently have by running the following command: + +[source,shell] +---- +$ mvn dependency:tree + +[INFO] com.example:myproject:jar:0.0.1-SNAPSHOT +---- + +The `mvn dependency:tree` command prints a tree representation of your project dependencies. +You can see that `spring-boot-starter-parent` provides no dependencies by itself. +To add the necessary dependencies, edit your `pom.xml` and add the `spring-boot-starter-web` dependency immediately below the `parent` section: + +[source,xml] +---- + + + org.springframework.boot + spring-boot-starter-web + + +---- + +If you run `mvn dependency:tree` again, you see that there are now a number of additional dependencies, including the Tomcat web server and Spring Boot itself. + + + +[[getting-started.first-application.dependencies.gradle]] +=== Gradle + +Most Spring Boot applications use the `org.springframework.boot` Gradle plugin. +This plugin provides useful defaults and Gradle tasks. +The `io.spring.dependency-management` Gradle plugin provides xref:reference:using/build-systems.adoc#using.build-systems.dependency-management[dependency management] so that you can omit `version` tags for "`blessed`" dependencies. + +Since we are developing a web application, we add a `spring-boot-starter-web` dependency. +Before that, we can look at what we currently have by running the following command: + +[source,shell] +---- +$ gradle dependencies + +> Task :dependencies + +------------------------------------------------------------ +Root project 'myproject' +------------------------------------------------------------ +---- + +The `gradle dependencies` command prints a tree representation of your project dependencies. +Right now, the project has no dependencies. +To add the necessary dependencies, edit your `build.gradle` and add the `spring-boot-starter-web` dependency in the `dependencies` section: + +[source,gradle] +---- +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' +} +---- + +If you run `gradle dependencies` again, you see that there are now a number of additional dependencies, including the Tomcat web server and Spring Boot itself. + + + +[[getting-started.first-application.code]] +== Writing the Code + +To finish our application, we need to create a single Java file. +By default, Maven and Gradle compile sources from `src/main/java`, so you need to create that directory structure and then add a file named `src/main/java/MyApplication.java` to contain the following code: + +[chomp_package_replacement=com.example] +include-code::MyApplication[] + +Although there is not much code here, quite a lot is going on. +We step through the important parts in the next few sections. + + + +[[getting-started.first-application.code.mvc-annotations]] +=== The @RestController and @RequestMapping Annotations + +The first annotation on our `MyApplication` class is `@RestController`. +This is known as a _stereotype_ annotation. +It provides hints for people reading the code and for Spring that the class plays a specific role. +In this case, our class is a web `@Controller`, so Spring considers it when handling incoming web requests. + +The `@RequestMapping` annotation provides "`routing`" information. +It tells Spring that any HTTP request with the `/` path should be mapped to the `home` method. +The `@RestController` annotation tells Spring to render the resulting string directly back to the caller. + +TIP: The `@RestController` and `@RequestMapping` annotations are Spring MVC annotations (they are not specific to Spring Boot). +See the {url-spring-framework-docs}/web/webmvc.html[MVC section] in the Spring Reference Documentation for more details. + + + +[[getting-started.first-application.code.spring-boot-application]] +=== The @SpringBootApplication Annotation + +The second class-level annotation is `@SpringBootApplication`. +This annotation is known as a _meta-annotation_, it combines `@SpringBootConfiguration`, `@EnableAutoConfiguration` and `@ComponentScan`. + +Of those, the annotation we're most interested in here is `@EnableAutoConfiguration`. +`@EnableAutoConfiguration` tells Spring Boot to "`guess`" how you want to configure Spring, based on the jar dependencies that you have added. +Since `spring-boot-starter-web` added Tomcat and Spring MVC, the auto-configuration assumes that you are developing a web application and sets up Spring accordingly. + +.Starters and Auto-configuration +**** +Auto-configuration is designed to work well with starters, but the two concepts are not directly tied. +You are free to pick and choose jar dependencies outside of the starters. +Spring Boot still does its best to auto-configure your application. +**** + + + +[[getting-started.first-application.code.main-method]] +=== The "`main`" Method + +The final part of our application is the `main` method. +This is a standard method that follows the Java convention for an application entry point. +Our main method delegates to Spring Boot's `SpringApplication` class by calling `run`. +`SpringApplication` bootstraps our application, starting Spring, which, in turn, starts the auto-configured Tomcat web server. +We need to pass `MyApplication.class` as an argument to the `run` method to tell `SpringApplication` which is the primary Spring component. +The `args` array is also passed through to expose any command-line arguments. + + + +[[getting-started.first-application.run]] +== Running the Example + + + +[[getting-started.first-application.run.maven]] +=== Maven + +At this point, your application should work. +Since you used the `spring-boot-starter-parent` POM, you have a useful `run` goal that you can use to start the application. +Type `mvn spring-boot:run` from the root project directory to start the application. +You should see output similar to the following: + +[source,shell,subs="verbatim,attributes"] +---- +$ mvn spring-boot:run + + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: (v{version-spring-boot}) +....... . . . +....... . . . (log output here) +....... . . . +........ Started MyApplication in 0.906 seconds (process running for 6.514) +---- + +If you open a web browser to `http://localhost:8080`, you should see the following output: + +[source] +---- +Hello World! +---- + +To gracefully exit the application, press `ctrl-c`. + + + +[[getting-started.first-application.run.gradle]] +=== Gradle + +At this point, your application should work. +Since you used the `org.springframework.boot` Gradle plugin, you have a useful `bootRun` goal that you can use to start the application. +Type `gradle bootRun` from the root project directory to start the application. +You should see output similar to the following: + +[source,shell,subs="verbatim,attributes"] +---- +$ gradle bootRun + + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: (v{version-spring-boot}) +....... . . . +....... . . . (log output here) +....... . . . +........ Started MyApplication in 0.906 seconds (process running for 6.514) +---- + +If you open a web browser to `http://localhost:8080`, you should see the following output: + +[source] +---- +Hello World! +---- + +To gracefully exit the application, press `ctrl-c`. + + + +[[getting-started.first-application.executable-jar]] +== Creating an Executable Jar + +We finish our example by creating a completely self-contained executable jar file that we could run in production. +Executable jars (sometimes called "`uber jars`" or "`fat jars`") are archives containing your compiled classes along with all of the jar dependencies that your code needs to run. + +.Executable jars and Java +**** +Java does not provide a standard way to load nested jar files (jar files that are themselves contained within a jar). +This can be problematic if you are looking to distribute a self-contained application. + +To solve this problem, many developers use "`uber`" jars. +An uber jar packages all the classes from all the application's dependencies into a single archive. +The problem with this approach is that it becomes hard to see which libraries are in your application. +It can also be problematic if the same filename is used (but with different content) in multiple jars. + +Spring Boot takes a xref:specification:executable-jar/index.adoc[different approach] and lets you actually nest jars directly. +**** + + + +[[getting-started.first-application.executable-jar.maven]] +=== Maven + +To create an executable jar, we need to add the `spring-boot-maven-plugin` to our `pom.xml`. +To do so, insert the following lines just below the `dependencies` section: + +[source,xml] +---- + + + + org.springframework.boot + spring-boot-maven-plugin + + + +---- + +NOTE: The `spring-boot-starter-parent` POM includes `` configuration to bind the `repackage` goal. +If you do not use the parent POM, you need to declare this configuration yourself. +See the xref:maven-plugin:getting-started.adoc[plugin documentation] for details. + +Save your `pom.xml` and run `mvn package` from the command line, as follows: + +[source,shell,subs="verbatim,attributes"] +---- +$ mvn package + +[INFO] Scanning for projects... +[INFO] +[INFO] ------------------------------------------------------------------------ +[INFO] Building myproject 0.0.1-SNAPSHOT +[INFO] ------------------------------------------------------------------------ +[INFO] .... .. +[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ myproject --- +[INFO] Building jar: /Users/developer/example/spring-boot-example/target/myproject-0.0.1-SNAPSHOT.jar +[INFO] +[INFO] --- spring-boot-maven-plugin:{version-spring-boot}:repackage (default) @ myproject --- +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +---- + +If you look in the `target` directory, you should see `myproject-0.0.1-SNAPSHOT.jar`. +The file should be around 18 MB in size. +If you want to peek inside, you can use `jar tvf`, as follows: + +[source,shell] +---- +$ jar tvf target/myproject-0.0.1-SNAPSHOT.jar +---- + +You should also see a much smaller file named `myproject-0.0.1-SNAPSHOT.jar.original` in the `target` directory. +This is the original jar file that Maven created before it was repackaged by Spring Boot. + +To run that application, use the `java -jar` command, as follows: + +[source,shell,subs="verbatim,attributes"] +---- +$ java -jar target/myproject-0.0.1-SNAPSHOT.jar + + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: (v{version-spring-boot}) +....... . . . +....... . . . (log output here) +....... . . . +........ Started MyApplication in 0.999 seconds (process running for 1.253) +---- + +As before, to exit the application, press `ctrl-c`. + + + +[[getting-started.first-application.executable-jar.gradle]] +=== Gradle + +To create an executable jar, we need to run `gradle bootJar` from the command line, as follows: + +[source,shell,subs="verbatim,attributes"] +---- +$ gradle bootJar + +BUILD SUCCESSFUL in 639ms +3 actionable tasks: 3 executed +---- + +If you look in the `build/libs` directory, you should see `myproject-0.0.1-SNAPSHOT.jar`. +The file should be around 18 MB in size. +If you want to peek inside, you can use `jar tvf`, as follows: + +[source,shell] +---- +$ jar tvf build/libs/myproject-0.0.1-SNAPSHOT.jar +---- + +To run that application, use the `java -jar` command, as follows: + +[source,shell] +---- +$ java -jar build/libs/myproject-0.0.1-SNAPSHOT.jar + + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: (v{version-spring-boot}) +....... . . . +....... . . . (log output here) +....... . . . +........ Started MyApplication in 0.999 seconds (process running for 1.253) +---- + +As before, to exit the application, press `ctrl-c`. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/tutorial/pages/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/tutorial/pages/index.adoc new file mode 100644 index 000000000000..7a8e6fab1d84 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/tutorial/pages/index.adoc @@ -0,0 +1,3 @@ += Tutorials + +This section provides tutorials to help you get started using Spring Boot. \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/tutorial/partials/nav-tutorial.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/tutorial/partials/nav-tutorial.adoc new file mode 100644 index 000000000000..cdcb9807d602 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/tutorial/partials/nav-tutorial.adoc @@ -0,0 +1,2 @@ +* xref:tutorial:index.adoc[] +** xref:tutorial:first-application/index.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/nav.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/nav.adoc new file mode 100644 index 000000000000..5cbf2e5e4bae --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/nav.adoc @@ -0,0 +1,11 @@ +include::ROOT:partial$nav-root.adoc[] +include::tutorial:partial$nav-tutorial.adoc[] +include::reference:partial$nav-reference.adoc[] +include::how-to:partial$nav-how-to.adoc[] +include::build-tool-plugin:partial$nav-build-tool-plugin.adoc[] +include::cli:partial$nav-cli.adoc[] +include::api:partial$nav-rest-api.adoc[] +include::api:partial$nav-java-api.adoc[] +include::api:partial$nav-kotlin-api.adoc[] +include::specification:partial$nav-specification.adoc[] +include::appendix:partial$nav-appendix.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator.adoc deleted file mode 100644 index 9bef63c8b686..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator.adoc +++ /dev/null @@ -1,36 +0,0 @@ - -[[actuator]] -= Production-ready Features -include::attributes.adoc[] - - - -Spring Boot includes a number of additional features to help you monitor and manage your application when you push it to production. -You can choose to manage and monitor your application by using HTTP endpoints or with JMX. -Auditing, health, and metrics gathering can also be automatically applied to your application. - -include::actuator/enabling.adoc[] - -include::actuator/endpoints.adoc[] - -include::actuator/monitoring.adoc[] - -include::actuator/jmx.adoc[] - -include::actuator/observability.adoc[] - -include::actuator/loggers.adoc[] - -include::actuator/metrics.adoc[] - -include::actuator/tracing.adoc[] - -include::actuator/auditing.adoc[] - -include::actuator/http-exchanges.adoc[] - -include::actuator/process-monitoring.adoc[] - -include::actuator/cloud-foundry.adoc[] - -include::actuator/whats-next.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/enabling.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/enabling.adoc deleted file mode 100644 index 4de5a8503582..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/enabling.adoc +++ /dev/null @@ -1,31 +0,0 @@ -[[actuator.enabling]] -== Enabling Production-ready Features -The {spring-boot-code}/spring-boot-project/spring-boot-actuator[`spring-boot-actuator`] module provides all of Spring Boot's production-ready features. -The recommended way to enable the features is to add a dependency on the `spring-boot-starter-actuator` "`Starter`". - -.Definition of Actuator -**** -An actuator is a manufacturing term that refers to a mechanical device for moving or controlling something. -Actuators can generate a large amount of motion from a small change. -**** - -To add the actuator to a Maven-based project, add the following "`Starter`" dependency: - -[source,xml,indent=0,subs="verbatim"] ----- - - - org.springframework.boot - spring-boot-starter-actuator - - ----- - -For Gradle, use the following declaration: - -[source,gradle,indent=0,subs="verbatim"] ----- - dependencies { - implementation 'org.springframework.boot:spring-boot-starter-actuator' - } ----- diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/endpoints.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/endpoints.adoc deleted file mode 100644 index 0d5a036e94ed..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/endpoints.adoc +++ /dev/null @@ -1,1216 +0,0 @@ -[[actuator.endpoints]] -== Endpoints -Actuator endpoints let you monitor and interact with your application. -Spring Boot includes a number of built-in endpoints and lets you add your own. -For example, the `health` endpoint provides basic application health information. - -You can <> each individual endpoint and <>. -An endpoint is considered to be available when it is both enabled and exposed. -The built-in endpoints are auto-configured only when they are available. -Most applications choose exposure over HTTP, where the ID of the endpoint and a prefix of `/actuator` is mapped to a URL. -For example, by default, the `health` endpoint is mapped to `/actuator/health`. - -TIP: To learn more about the Actuator's endpoints and their request and response formats, see the separate API documentation ({spring-boot-actuator-restapi-docs}[HTML] or {spring-boot-actuator-restapi-pdfdocs}[PDF]). - -The following technology-agnostic endpoints are available: - -[cols="2,5"] -|=== -| ID | Description - -| `auditevents` -| Exposes audit events information for the current application. - Requires an `AuditEventRepository` bean. - -| `beans` -| Displays a complete list of all the Spring beans in your application. - -| `caches` -| Exposes available caches. - -| `conditions` -| Shows the conditions that were evaluated on configuration and auto-configuration classes and the reasons why they did or did not match. - -| `configprops` -| Displays a collated list of all `@ConfigurationProperties`. -Subject to <>. - -| `env` -| Exposes properties from Spring's `ConfigurableEnvironment`. -Subject to <>. - -| `flyway` -| Shows any Flyway database migrations that have been applied. - Requires one or more `Flyway` beans. - -| `health` -| Shows application health information. - -| `httpexchanges` -| Displays HTTP exchange information (by default, the last 100 HTTP request-response exchanges). - Requires an `HttpExchangeRepository` bean. - -| `info` -| Displays arbitrary application info. - -| `integrationgraph` -| Shows the Spring Integration graph. - Requires a dependency on `spring-integration-core`. - -| `loggers` -| Shows and modifies the configuration of loggers in the application. - -| `liquibase` -| Shows any Liquibase database migrations that have been applied. - Requires one or more `Liquibase` beans. - -| `metrics` -| Shows "`metrics`" information for the current application. - -| `mappings` -| Displays a collated list of all `@RequestMapping` paths. - -|`quartz` -|Shows information about Quartz Scheduler jobs. -Subject to <>. - -| `scheduledtasks` -| Displays the scheduled tasks in your application. - -| `sessions` -| Allows retrieval and deletion of user sessions from a Spring Session-backed session store. - Requires a servlet-based web application that uses Spring Session. - -| `shutdown` -| Lets the application be gracefully shutdown. - Only works when using jar packaging. - Disabled by default. - -| `startup` -| Shows the <> collected by the `ApplicationStartup`. - Requires the `SpringApplication` to be configured with a `BufferingApplicationStartup`. - -| `threaddump` -| Performs a thread dump. -|=== - -If your application is a web application (Spring MVC, Spring WebFlux, or Jersey), you can use the following additional endpoints: - -[cols="2,5"] -|=== -| ID | Description - -| `heapdump` -| Returns a heap dump file. - On a HotSpot JVM, an `HPROF`-format file is returned. - On an OpenJ9 JVM, a `PHD`-format file is returned. - -| `logfile` -| Returns the contents of the logfile (if the `logging.file.name` or the `logging.file.path` property has been set). - Supports the use of the HTTP `Range` header to retrieve part of the log file's content. - -| `prometheus` -| Exposes metrics in a format that can be scraped by a Prometheus server. - Requires a dependency on `micrometer-registry-prometheus`. -|=== - - - -[[actuator.endpoints.enabling]] -=== Enabling Endpoints -By default, all endpoints except for `shutdown` are enabled. -To configure the enablement of an endpoint, use its `management.endpoint..enabled` property. -The following example enables the `shutdown` endpoint: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - endpoint: - shutdown: - enabled: true ----- - -If you prefer endpoint enablement to be opt-in rather than opt-out, set the configprop:management.endpoints.enabled-by-default[] property to `false` and use individual endpoint `enabled` properties to opt back in. -The following example enables the `info` endpoint and disables all other endpoints: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - endpoints: - enabled-by-default: false - endpoint: - info: - enabled: true ----- - -NOTE: Disabled endpoints are removed entirely from the application context. -If you want to change only the technologies over which an endpoint is exposed, use the <> instead. - - - -[[actuator.endpoints.exposing]] -=== Exposing Endpoints -By default, only the health endpoint is exposed over HTTP and JMX. -Since Endpoints may contain sensitive information, you should carefully consider when to expose them. - -To change which endpoints are exposed, use the following technology-specific `include` and `exclude` properties: - -[cols="3,1"] -|=== -| Property | Default - -| configprop:management.endpoints.jmx.exposure.exclude[] -| - -| configprop:management.endpoints.jmx.exposure.include[] -| `health` - -| configprop:management.endpoints.web.exposure.exclude[] -| - -| configprop:management.endpoints.web.exposure.include[] -| `health` -|=== - -The `include` property lists the IDs of the endpoints that are exposed. -The `exclude` property lists the IDs of the endpoints that should not be exposed. -The `exclude` property takes precedence over the `include` property. -You can configure both the `include` and the `exclude` properties with a list of endpoint IDs. - -For example, to only expose the `health` and `info` endpoints over JMX, use the following property: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - endpoints: - jmx: - exposure: - include: "health,info" ----- - -`*` can be used to select all endpoints. -For example, to expose everything over HTTP except the `env` and `beans` endpoints, use the following properties: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - endpoints: - web: - exposure: - include: "*" - exclude: "env,beans" ----- - -NOTE: `*` has a special meaning in YAML, so be sure to add quotation marks if you want to include (or exclude) all endpoints. - -NOTE: If your application is exposed publicly, we strongly recommend that you also <>. - -TIP: If you want to implement your own strategy for when endpoints are exposed, you can register an `EndpointFilter` bean. - - - -[[actuator.endpoints.security]] -=== Security -For security purposes, only the `/health` endpoint is exposed over HTTP by default. -You can use the configprop:management.endpoints.web.exposure.include[] property to configure the endpoints that are exposed. - -NOTE: Before setting the `management.endpoints.web.exposure.include`, ensure that the exposed actuators do not contain sensitive information, are secured by placing them behind a firewall, or are secured by something like Spring Security. - -If Spring Security is on the classpath and no other `SecurityFilterChain` bean is present, all actuators other than `/health` are secured by Spring Boot auto-configuration. -If you define a custom `SecurityFilterChain` bean, Spring Boot auto-configuration backs off and lets you fully control the actuator access rules. - -If you wish to configure custom security for HTTP endpoints (for example, to allow only users with a certain role to access them), Spring Boot provides some convenient `RequestMatcher` objects that you can use in combination with Spring Security. - -A typical Spring Security configuration might look something like the following example: - -include::code:typical/MySecurityConfiguration[] - -The preceding example uses `EndpointRequest.toAnyEndpoint()` to match a request to any endpoint and then ensures that all have the `ENDPOINT_ADMIN` role. -Several other matcher methods are also available on `EndpointRequest`. -See the API documentation ({spring-boot-actuator-restapi-docs}[HTML] or {spring-boot-actuator-restapi-pdfdocs}[PDF]) for details. - -If you deploy applications behind a firewall, you may prefer that all your actuator endpoints can be accessed without requiring authentication. -You can do so by changing the configprop:management.endpoints.web.exposure.include[] property, as follows: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - endpoints: - web: - exposure: - include: "*" ----- - -Additionally, if Spring Security is present, you would need to add custom security configuration that allows unauthenticated access to the endpoints, as the following example shows: - -include::code:exposeall/MySecurityConfiguration[] - -NOTE: In both of the preceding examples, the configuration applies only to the actuator endpoints. -Since Spring Boot's security configuration backs off completely in the presence of any `SecurityFilterChain` bean, you need to configure an additional `SecurityFilterChain` bean with rules that apply to the rest of the application. - - - -[[actuator.endpoints.security.csrf]] -==== Cross Site Request Forgery Protection -Since Spring Boot relies on Spring Security's defaults, CSRF protection is turned on by default. -This means that the actuator endpoints that require a `POST` (shutdown and loggers endpoints), a `PUT`, or a `DELETE` get a 403 (forbidden) error when the default security configuration is in use. - -NOTE: We recommend disabling CSRF protection completely only if you are creating a service that is used by non-browser clients. - -You can find additional information about CSRF protection in the {spring-security-docs}/features/exploits/csrf.html[Spring Security Reference Guide]. - - - -[[actuator.endpoints.caching]] -=== Configuring Endpoints -Endpoints automatically cache responses to read operations that do not take any parameters. -To configure the amount of time for which an endpoint caches a response, use its `cache.time-to-live` property. -The following example sets the time-to-live of the `beans` endpoint's cache to 10 seconds: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - endpoint: - beans: - cache: - time-to-live: "10s" ----- - -NOTE: The `management.endpoint.` prefix uniquely identifies the endpoint that is being configured. - - - -[[actuator.endpoints.sanitization]] -=== Sanitize Sensitive Values -Information returned by the `/env`, `/configprops` and `/quartz` endpoints can be sensitive, so by default values are always fully sanitized (replaced by `+******+`). - -Values can only be viewed in an unsanitized form when: - -- The `show-values` property has been set to something other than `NEVER` -- No custom `<>` beans apply - -The `show-values` property can be configured for sanitizable endpoints to one of the following values: - -- `NEVER` - values are always fully sanitized (replaced by `+******+`) -- `ALWAYS` - values are shown to all users (as long as no `SanitizingFunction` bean applies) -- `WHEN_AUTHORIZED` - values are shown only to authorized users (as long as no `SanitizingFunction` bean applies) - -For HTTP endpoints, a user is considered to be authorized if they have authenticated and have the roles configured by the endpoint's roles property. -By default, any authenticated user is authorized. - -For JMX endpoints, all users are always authorized. - -The following example allows all users with the `admin` role to view values from the `/env` endpoint in their original form. -Unauthorized users, or users without the `admin` role, will see only sanitized values. - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - endpoint: - env: - show-values: WHEN_AUTHORIZED - roles: "admin" ----- - -NOTE: This example assumes that no `<>` beans have been defined. - - - -[[actuator.endpoints.hypermedia]] -=== Hypermedia for Actuator Web Endpoints -A "`discovery page`" is added with links to all the endpoints. -The "`discovery page`" is available on `/actuator` by default. - -To disable the "`discovery page`", add the following property to your application properties: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - endpoints: - web: - discovery: - enabled: false ----- - -When a custom management context path is configured, the "`discovery page`" automatically moves from `/actuator` to the root of the management context. -For example, if the management context path is `/management`, the discovery page is available from `/management`. -When the management context path is set to `/`, the discovery page is disabled to prevent the possibility of a clash with other mappings. - - - -[[actuator.endpoints.cors]] -=== CORS Support -https://en.wikipedia.org/wiki/Cross-origin_resource_sharing[Cross-origin resource sharing] (CORS) is a https://www.w3.org/TR/cors/[W3C specification] that lets you specify in a flexible way what kind of cross-domain requests are authorized. -If you use Spring MVC or Spring WebFlux, you can configure Actuator's web endpoints to support such scenarios. - -CORS support is disabled by default and is only enabled once you have set the configprop:management.endpoints.web.cors.allowed-origins[] property. -The following configuration permits `GET` and `POST` calls from the `example.com` domain: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - endpoints: - web: - cors: - allowed-origins: "https://example.com" - allowed-methods: "GET,POST" ----- - -TIP: See {spring-boot-actuator-autoconfigure-module-code}/endpoint/web/CorsEndpointProperties.java[`CorsEndpointProperties`] for a complete list of options. - - - -[[actuator.endpoints.implementing-custom]] -=== Implementing Custom Endpoints -If you add a `@Bean` annotated with `@Endpoint`, any methods annotated with `@ReadOperation`, `@WriteOperation`, or `@DeleteOperation` are automatically exposed over JMX and, in a web application, over HTTP as well. -Endpoints can be exposed over HTTP by using Jersey, Spring MVC, or Spring WebFlux. -If both Jersey and Spring MVC are available, Spring MVC is used. - -The following example exposes a read operation that returns a custom object: - -include::code:MyEndpoint[tag=read] - -You can also write technology-specific endpoints by using `@JmxEndpoint` or `@WebEndpoint`. -These endpoints are restricted to their respective technologies. -For example, `@WebEndpoint` is exposed only over HTTP and not over JMX. - -You can write technology-specific extensions by using `@EndpointWebExtension` and `@EndpointJmxExtension`. -These annotations let you provide technology-specific operations to augment an existing endpoint. - -Finally, if you need access to web-framework-specific functionality, you can implement servlet or Spring `@Controller` and `@RestController` endpoints at the cost of them not being available over JMX or when using a different web framework. - - - -[[actuator.endpoints.implementing-custom.input]] -==== Receiving Input -Operations on an endpoint receive input through their parameters. -When exposed over the web, the values for these parameters are taken from the URL's query parameters and from the JSON request body. -When exposed over JMX, the parameters are mapped to the parameters of the MBean's operations. -Parameters are required by default. -They can be made optional by annotating them with either `@javax.annotation.Nullable` or `@org.springframework.lang.Nullable`. - -You can map each root property in the JSON request body to a parameter of the endpoint. -Consider the following JSON request body: - -[source,json,indent=0,subs="verbatim"] ----- - { - "name": "test", - "counter": 42 - } ----- - -You can use this to invoke a write operation that takes `String name` and `int counter` parameters, as the following example shows: - -include::code:../MyEndpoint[tag=write] - -TIP: Because endpoints are technology agnostic, only simple types can be specified in the method signature. -In particular, declaring a single parameter with a `CustomData` type that defines a `name` and `counter` properties is not supported. - -NOTE: To let the input be mapped to the operation method's parameters, Java code that implements an endpoint should be compiled with `-parameters`, and Kotlin code that implements an endpoint should be compiled with `-java-parameters`. -This will happen automatically if you use Spring Boot's Gradle plugin or if you use Maven and `spring-boot-starter-parent`. - - - -[[actuator.endpoints.implementing-custom.input.conversion]] -===== Input Type Conversion -The parameters passed to endpoint operation methods are, if necessary, automatically converted to the required type. -Before calling an operation method, the input received over JMX or HTTP is converted to the required types by using an instance of `ApplicationConversionService` as well as any `Converter` or `GenericConverter` beans qualified with `@EndpointConverter`. - - - -[[actuator.endpoints.implementing-custom.web]] -==== Custom Web Endpoints -Operations on an `@Endpoint`, `@WebEndpoint`, or `@EndpointWebExtension` are automatically exposed over HTTP using Jersey, Spring MVC, or Spring WebFlux. -If both Jersey and Spring MVC are available, Spring MVC is used. - - - -[[actuator.endpoints.implementing-custom.web.request-predicates]] -===== Web Endpoint Request Predicates -A request predicate is automatically generated for each operation on a web-exposed endpoint. - - - -[[actuator.endpoints.implementing-custom.web.path-predicates]] -===== Path -The path of the predicate is determined by the ID of the endpoint and the base path of the web-exposed endpoints. -The default base path is `/actuator`. -For example, an endpoint with an ID of `sessions` uses `/actuator/sessions` as its path in the predicate. - -You can further customize the path by annotating one or more parameters of the operation method with `@Selector`. -Such a parameter is added to the path predicate as a path variable. -The variable's value is passed into the operation method when the endpoint operation is invoked. -If you want to capture all remaining path elements, you can add `@Selector(Match=ALL_REMAINING)` to the last parameter and make it a type that is conversion-compatible with a `String[]`. - - - -[[actuator.endpoints.implementing-custom.web.method-predicates]] -===== HTTP method -The HTTP method of the predicate is determined by the operation type, as shown in the following table: - -[cols="3, 1"] -|=== -| Operation | HTTP method - -| `@ReadOperation` -| `GET` - -| `@WriteOperation` -| `POST` - -| `@DeleteOperation` -| `DELETE` -|=== - - - -[[actuator.endpoints.implementing-custom.web.consumes-predicates]] -===== Consumes -For a `@WriteOperation` (HTTP `POST`) that uses the request body, the `consumes` clause of the predicate is `application/vnd.spring-boot.actuator.v2+json, application/json`. -For all other operations, the `consumes` clause is empty. - - - -[[actuator.endpoints.implementing-custom.web.produces-predicates]] -===== Produces -The `produces` clause of the predicate can be determined by the `produces` attribute of the `@DeleteOperation`, `@ReadOperation`, and `@WriteOperation` annotations. -The attribute is optional. -If it is not used, the `produces` clause is determined automatically. - -If the operation method returns `void` or `Void`, the `produces` clause is empty. -If the operation method returns a `org.springframework.core.io.Resource`, the `produces` clause is `application/octet-stream`. -For all other operations, the `produces` clause is `application/vnd.spring-boot.actuator.v2+json, application/json`. - - - -[[actuator.endpoints.implementing-custom.web.response-status]] -===== Web Endpoint Response Status -The default response status for an endpoint operation depends on the operation type (read, write, or delete) and what, if anything, the operation returns. - -If a `@ReadOperation` returns a value, the response status will be 200 (OK). -If it does not return a value, the response status will be 404 (Not Found). - -If a `@WriteOperation` or `@DeleteOperation` returns a value, the response status will be 200 (OK). -If it does not return a value, the response status will be 204 (No Content). - -If an operation is invoked without a required parameter or with a parameter that cannot be converted to the required type, the operation method is not called, and the response status will be 400 (Bad Request). - - - -[[actuator.endpoints.implementing-custom.web.range-requests]] -===== Web Endpoint Range Requests -You can use an HTTP range request to request part of an HTTP resource. -When using Spring MVC or Spring Web Flux, operations that return a `org.springframework.core.io.Resource` automatically support range requests. - -NOTE: Range requests are not supported when using Jersey. - - - -[[actuator.endpoints.implementing-custom.web.security]] -===== Web Endpoint Security -An operation on a web endpoint or a web-specific endpoint extension can receive the current `java.security.Principal` or `org.springframework.boot.actuate.endpoint.SecurityContext` as a method parameter. -The former is typically used in conjunction with `@Nullable` to provide different behavior for authenticated and unauthenticated users. -The latter is typically used to perform authorization checks by using its `isUserInRole(String)` method. - - - -[[actuator.endpoints.implementing-custom.servlet]] -==== Servlet Endpoints -A servlet can be exposed as an endpoint by implementing a class annotated with `@ServletEndpoint` that also implements `Supplier`. -Servlet endpoints provide deeper integration with the servlet container but at the expense of portability. -They are intended to be used to expose an existing servlet as an endpoint. -For new endpoints, the `@Endpoint` and `@WebEndpoint` annotations should be preferred whenever possible. - - - -[[actuator.endpoints.implementing-custom.controller]] -==== Controller Endpoints -You can use `@ControllerEndpoint` and `@RestControllerEndpoint` to implement an endpoint that is exposed only by Spring MVC or Spring WebFlux. -Methods are mapped by using the standard annotations for Spring MVC and Spring WebFlux, such as `@RequestMapping` and `@GetMapping`, with the endpoint's ID being used as a prefix for the path. -Controller endpoints provide deeper integration with Spring's web frameworks but at the expense of portability. -The `@Endpoint` and `@WebEndpoint` annotations should be preferred whenever possible. - - - -[[actuator.endpoints.health]] -=== Health Information -You can use health information to check the status of your running application. -It is often used by monitoring software to alert someone when a production system goes down. -The information exposed by the `health` endpoint depends on the configprop:management.endpoint.health.show-details[] and configprop:management.endpoint.health.show-components[] properties, which can be configured with one of the following values: - -[cols="1, 3"] -|=== -| Name | Description - -| `never` -| Details are never shown. - -| `when-authorized` -| Details are shown only to authorized users. - Authorized roles can be configured by using `management.endpoint.health.roles`. - -| `always` -| Details are shown to all users. -|=== - -The default value is `never`. -A user is considered to be authorized when they are in one or more of the endpoint's roles. -If the endpoint has no configured roles (the default), all authenticated users are considered to be authorized. -You can configure the roles by using the configprop:management.endpoint.health.roles[] property. - -NOTE: If you have secured your application and wish to use `always`, your security configuration must permit access to the health endpoint for both authenticated and unauthenticated users. - -Health information is collected from the content of a {spring-boot-actuator-module-code}/health/HealthContributorRegistry.java[`HealthContributorRegistry`] (by default, all {spring-boot-actuator-module-code}/health/HealthContributor.java[`HealthContributor`] instances defined in your `ApplicationContext`). -Spring Boot includes a number of auto-configured `HealthContributors`, and you can also write your own. - -A `HealthContributor` can be either a `HealthIndicator` or a `CompositeHealthContributor`. -A `HealthIndicator` provides actual health information, including a `Status`. -A `CompositeHealthContributor` provides a composite of other `HealthContributors`. -Taken together, contributors form a tree structure to represent the overall system health. - -By default, the final system health is derived by a `StatusAggregator`, which sorts the statuses from each `HealthIndicator` based on an ordered list of statuses. -The first status in the sorted list is used as the overall health status. -If no `HealthIndicator` returns a status that is known to the `StatusAggregator`, an `UNKNOWN` status is used. - -TIP: You can use the `HealthContributorRegistry` to register and unregister health indicators at runtime. - - - -[[actuator.endpoints.health.auto-configured-health-indicators]] -==== Auto-configured HealthIndicators -When appropriate, Spring Boot auto-configures the `HealthIndicators` listed in the following table. -You can also enable or disable selected indicators by configuring `management.health.key.enabled`, -with the `key` listed in the following table: - -[cols="2,4,6"] -|=== -| Key | Name | Description - -| `cassandra` -| {spring-boot-actuator-module-code}/cassandra/CassandraDriverHealthIndicator.java[`CassandraDriverHealthIndicator`] -| Checks that a Cassandra database is up. - -| `couchbase` -| {spring-boot-actuator-module-code}/couchbase/CouchbaseHealthIndicator.java[`CouchbaseHealthIndicator`] -| Checks that a Couchbase cluster is up. - -| `db` -| {spring-boot-actuator-module-code}/jdbc/DataSourceHealthIndicator.java[`DataSourceHealthIndicator`] -| Checks that a connection to `DataSource` can be obtained. - -| `diskspace` -| {spring-boot-actuator-module-code}/system/DiskSpaceHealthIndicator.java[`DiskSpaceHealthIndicator`] -| Checks for low disk space. - -| `elasticsearch` -| {spring-boot-actuator-module-code}/elasticsearch/ElasticsearchRestClientHealthIndicator.java[`ElasticsearchRestClientHealthIndicator`] -| Checks that an Elasticsearch cluster is up. - -| `hazelcast` -| {spring-boot-actuator-module-code}/hazelcast/HazelcastHealthIndicator.java[`HazelcastHealthIndicator`] -| Checks that a Hazelcast server is up. - -| `influxdb` -| {spring-boot-actuator-module-code}/influx/InfluxDbHealthIndicator.java[`InfluxDbHealthIndicator`] -| Checks that an InfluxDB server is up. - -| `jms` -| {spring-boot-actuator-module-code}/jms/JmsHealthIndicator.java[`JmsHealthIndicator`] -| Checks that a JMS broker is up. - -| `ldap` -| {spring-boot-actuator-module-code}/ldap/LdapHealthIndicator.java[`LdapHealthIndicator`] -| Checks that an LDAP server is up. - -| `mail` -| {spring-boot-actuator-module-code}/mail/MailHealthIndicator.java[`MailHealthIndicator`] -| Checks that a mail server is up. - -| `mongo` -| {spring-boot-actuator-module-code}/data/mongo/MongoHealthIndicator.java[`MongoHealthIndicator`] -| Checks that a Mongo database is up. - -| `neo4j` -| {spring-boot-actuator-module-code}/neo4j/Neo4jHealthIndicator.java[`Neo4jHealthIndicator`] -| Checks that a Neo4j database is up. - -| `ping` -| {spring-boot-actuator-module-code}/health/PingHealthIndicator.java[`PingHealthIndicator`] -| Always responds with `UP`. - -| `rabbit` -| {spring-boot-actuator-module-code}/amqp/RabbitHealthIndicator.java[`RabbitHealthIndicator`] -| Checks that a Rabbit server is up. - -| `redis` -| {spring-boot-actuator-module-code}/data/redis/RedisHealthIndicator.java[`RedisHealthIndicator`] -| Checks that a Redis server is up. -|=== - -TIP: You can disable them all by setting the configprop:management.health.defaults.enabled[] property. - -Additional `HealthIndicators` are available but are not enabled by default: - -[cols="3,4,6"] -|=== -| Key | Name | Description - -| `livenessstate` -| {spring-boot-actuator-module-code}/availability/LivenessStateHealthIndicator.java[`LivenessStateHealthIndicator`] -| Exposes the "`Liveness`" application availability state. - -| `readinessstate` -| {spring-boot-actuator-module-code}/availability/ReadinessStateHealthIndicator.java[`ReadinessStateHealthIndicator`] -| Exposes the "`Readiness`" application availability state. -|=== - - - -[[actuator.endpoints.health.writing-custom-health-indicators]] -==== Writing Custom HealthIndicators -To provide custom health information, you can register Spring beans that implement the {spring-boot-actuator-module-code}/health/HealthIndicator.java[`HealthIndicator`] interface. -You need to provide an implementation of the `health()` method and return a `Health` response. -The `Health` response should include a status and can optionally include additional details to be displayed. -The following code shows a sample `HealthIndicator` implementation: - -include::code:MyHealthIndicator[] - -NOTE: The identifier for a given `HealthIndicator` is the name of the bean without the `HealthIndicator` suffix, if it exists. -In the preceding example, the health information is available in an entry named `my`. - -TIP: Health indicators are usually called over HTTP and need to respond before any connection timeouts. -Spring Boot will log a warning message for any health indicator that takes longer than 10 seconds to respond. -If you want to configure this threshold, you can use the configprop:management.endpoint.health.logging.slow-indicator-threshold[] property. - -In addition to Spring Boot's predefined {spring-boot-actuator-module-code}/health/Status.java[`Status`] types, `Health` can return a custom `Status` that represents a new system state. -In such cases, you also need to provide a custom implementation of the {spring-boot-actuator-module-code}/health/StatusAggregator.java[`StatusAggregator`] interface, or you must configure the default implementation by using the configprop:management.endpoint.health.status.order[] configuration property. - -For example, assume a new `Status` with a code of `FATAL` is being used in one of your `HealthIndicator` implementations. -To configure the severity order, add the following property to your application properties: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - endpoint: - health: - status: - order: "fatal,down,out-of-service,unknown,up" ----- - -The HTTP status code in the response reflects the overall health status. -By default, `OUT_OF_SERVICE` and `DOWN` map to 503. -Any unmapped health statuses, including `UP`, map to 200. -You might also want to register custom status mappings if you access the health endpoint over HTTP. -Configuring a custom mapping disables the defaults mappings for `DOWN` and `OUT_OF_SERVICE`. -If you want to retain the default mappings, you must explicitly configure them, alongside any custom mappings. -For example, the following property maps `FATAL` to 503 (service unavailable) and retains the default mappings for `DOWN` and `OUT_OF_SERVICE`: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - endpoint: - health: - status: - http-mapping: - down: 503 - fatal: 503 - out-of-service: 503 ----- - -TIP: If you need more control, you can define your own `HttpCodeStatusMapper` bean. - -The following table shows the default status mappings for the built-in statuses: - -[cols="1,3"] -|=== -| Status | Mapping - -| `DOWN` -| `SERVICE_UNAVAILABLE` (`503`) - -| `OUT_OF_SERVICE` -| `SERVICE_UNAVAILABLE` (`503`) - -| `UP` -| No mapping by default, so HTTP status is `200` - -| `UNKNOWN` -| No mapping by default, so HTTP status is `200` -|=== - - - -[[actuator.endpoints.health.reactive-health-indicators]] -==== Reactive Health Indicators -For reactive applications, such as those that use Spring WebFlux, `ReactiveHealthContributor` provides a non-blocking contract for getting application health. -Similar to a traditional `HealthContributor`, health information is collected from the content of a {spring-boot-actuator-module-code}/health/ReactiveHealthContributorRegistry.java[`ReactiveHealthContributorRegistry`] (by default, all {spring-boot-actuator-module-code}/health/HealthContributor.java[`HealthContributor`] and {spring-boot-actuator-module-code}/health/ReactiveHealthContributor.java[`ReactiveHealthContributor`] instances defined in your `ApplicationContext`). -Regular `HealthContributors` that do not check against a reactive API are executed on the elastic scheduler. - -TIP: In a reactive application, you should use the `ReactiveHealthContributorRegistry` to register and unregister health indicators at runtime. -If you need to register a regular `HealthContributor`, you should wrap it with `ReactiveHealthContributor#adapt`. - -To provide custom health information from a reactive API, you can register Spring beans that implement the {spring-boot-actuator-module-code}/health/ReactiveHealthIndicator.java[`ReactiveHealthIndicator`] interface. -The following code shows a sample `ReactiveHealthIndicator` implementation: - -include::code:MyReactiveHealthIndicator[] - -TIP: To handle the error automatically, consider extending from `AbstractReactiveHealthIndicator`. - - - -[[actuator.endpoints.health.auto-configured-reactive-health-indicators]] -==== Auto-configured ReactiveHealthIndicators -When appropriate, Spring Boot auto-configures the following `ReactiveHealthIndicators`: - -[cols="2,4,6"] -|=== -| Key | Name | Description - -| `cassandra` -| {spring-boot-actuator-module-code}/cassandra/CassandraDriverReactiveHealthIndicator.java[`CassandraDriverReactiveHealthIndicator`] -| Checks that a Cassandra database is up. - -| `couchbase` -| {spring-boot-actuator-module-code}/couchbase/CouchbaseReactiveHealthIndicator.java[`CouchbaseReactiveHealthIndicator`] -| Checks that a Couchbase cluster is up. - -| `elasticsearch` -| {spring-boot-actuator-module-code}/data/elasticsearch/ElasticsearchReactiveHealthIndicator.java[`ElasticsearchReactiveHealthIndicator`] -| Checks that an Elasticsearch cluster is up. - -| `mongo` -| {spring-boot-actuator-module-code}/data/mongo/MongoReactiveHealthIndicator.java[`MongoReactiveHealthIndicator`] -| Checks that a Mongo database is up. - -| `neo4j` -| {spring-boot-actuator-module-code}/neo4j/Neo4jReactiveHealthIndicator.java[`Neo4jReactiveHealthIndicator`] -| Checks that a Neo4j database is up. - -| `redis` -| {spring-boot-actuator-module-code}/data/redis/RedisReactiveHealthIndicator.java[`RedisReactiveHealthIndicator`] -| Checks that a Redis server is up. -|=== - -TIP: If necessary, reactive indicators replace the regular ones. -Also, any `HealthIndicator` that is not handled explicitly is wrapped automatically. - - - -[[actuator.endpoints.health.groups]] -==== Health Groups -It is sometimes useful to organize health indicators into groups that you can use for different purposes. - -To create a health indicator group, you can use the `management.endpoint.health.group.` property and specify a list of health indicator IDs to `include` or `exclude`. -For example, to create a group that includes only database indicators you can define the following: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - endpoint: - health: - group: - custom: - include: "db" ----- - -You can then check the result by hitting `http://localhost:8080/actuator/health/custom`. - -Similarly, to create a group that excludes the database indicators from the group and includes all the other indicators, you can define the following: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - endpoint: - health: - group: - custom: - exclude: "db" ----- - -By default, startup will fail if a health group includes or excludes a health indicator that does not exist. -To disable this behavior set configprop:management.endpoint.health.validate-group-membership[] to `false`. - -By default, groups inherit the same `StatusAggregator` and `HttpCodeStatusMapper` settings as the system health. -However, you can also define these on a per-group basis. -You can also override the `show-details` and `roles` properties if required: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - endpoint: - health: - group: - custom: - show-details: "when-authorized" - roles: "admin" - status: - order: "fatal,up" - http-mapping: - fatal: 500 - out-of-service: 500 ----- - -TIP: You can use `@Qualifier("groupname")` if you need to register custom `StatusAggregator` or `HttpCodeStatusMapper` beans for use with the group. - -A health group can also include/exclude a `CompositeHealthContributor`. -You can also include/exclude only a certain component of a `CompositeHealthContributor`. -This can be done using the fully qualified name of the component as follows: - -[source,properties,indent=0,subs="verbatim"] ----- - management.endpoint.health.group.custom.include="test/primary" - management.endpoint.health.group.custom.exclude="test/primary/b" ----- - -In the example above, the `custom` group will include the `HealthContributor` with the name `primary` which is a component of the composite `test`. -Here, `primary` itself is a composite and the `HealthContributor` with the name `b` will be excluded from the `custom` group. - - -Health groups can be made available at an additional path on either the main or management port. -This is useful in cloud environments such as Kubernetes, where it is quite common to use a separate management port for the actuator endpoints for security purposes. -Having a separate port could lead to unreliable health checks because the main application might not work properly even if the health check is successful. -The health group can be configured with an additional path as follows: - -[source,properties,indent=0,subs="verbatim"] ----- - management.endpoint.health.group.live.additional-path="server:/healthz" ----- - -This would make the `live` health group available on the main server port at `/healthz`. -The prefix is mandatory and must be either `server:` (represents the main server port) or `management:` (represents the management port, if configured.) -The path must be a single path segment. - - - -[[actuator.endpoints.health.datasource]] -==== DataSource Health -The `DataSource` health indicator shows the health of both standard data sources and routing data source beans. -The health of a routing data source includes the health of each of its target data sources. -In the health endpoint's response, each of a routing data source's targets is named by using its routing key. -If you prefer not to include routing data sources in the indicator's output, set configprop:management.health.db.ignore-routing-data-sources[] to `true`. - - - -[[actuator.endpoints.kubernetes-probes]] -=== Kubernetes Probes -Applications deployed on Kubernetes can provide information about their internal state with https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes[Container Probes]. -Depending on https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/[your Kubernetes configuration], the kubelet calls those probes and reacts to the result. - -By default, Spring Boot manages your <>. -If deployed in a Kubernetes environment, actuator gathers the "`Liveness`" and "`Readiness`" information from the `ApplicationAvailability` interface and uses that information in dedicated <>: `LivenessStateHealthIndicator` and `ReadinessStateHealthIndicator`. -These indicators are shown on the global health endpoint (`"/actuator/health"`). -They are also exposed as separate HTTP Probes by using <>: `"/actuator/health/liveness"` and `"/actuator/health/readiness"`. - -You can then configure your Kubernetes infrastructure with the following endpoint information: - -[source,yaml,indent=0,subs="verbatim"] ----- - livenessProbe: - httpGet: - path: "/actuator/health/liveness" - port: - failureThreshold: ... - periodSeconds: ... - - readinessProbe: - httpGet: - path: "/actuator/health/readiness" - port: - failureThreshold: ... - periodSeconds: ... ----- - -NOTE: `` should be set to the port that the actuator endpoints are available on. -It could be the main web server port or a separate management port if the `"management.server.port"` property has been set. - -These health groups are automatically enabled only if the application <>. -You can enable them in any environment by using the configprop:management.endpoint.health.probes.enabled[] configuration property. - -NOTE: If an application takes longer to start than the configured liveness period, Kubernetes mentions the `"startupProbe"` as a possible solution. -Generally speaking, the `"startupProbe"` is not necessarily needed here, as the `"readinessProbe"` fails until all startup tasks are done. -This means your application will not receive traffic until it is ready. -However, if your application takes a long time to start, consider using a `"startupProbe"` to make sure that Kubernetes won't kill your application while it is in the process of starting. -See the section that describes <>. - -If your Actuator endpoints are deployed on a separate management context, the endpoints do not use the same web infrastructure (port, connection pools, framework components) as the main application. -In this case, a probe check could be successful even if the main application does not work properly (for example, it cannot accept new connections). -For this reason, it is a good idea to make the `liveness` and `readiness` health groups available on the main server port. -This can be done by setting the following property: - -[source,properties,indent=0,subs="verbatim"] ----- - management.endpoint.health.probes.add-additional-paths=true ----- - -This would make the `liveness` group available at `/livez` and the `readiness` group available at `/readyz` on the main server port. -Paths can be customized using the `additional-path` property on each group, see <> for details. - - - -[[actuator.endpoints.kubernetes-probes.external-state]] -==== Checking External State With Kubernetes Probes -Actuator configures the "`liveness`" and "`readiness`" probes as Health Groups. -This means that all the <> are available for them. -You can, for example, configure additional Health Indicators: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - endpoint: - health: - group: - readiness: - include: "readinessState,customCheck" ----- - -By default, Spring Boot does not add other health indicators to these groups. - -The "`liveness`" probe should not depend on health checks for external systems. -If the <> is broken, Kubernetes tries to solve that problem by restarting the application instance. -This means that if an external system (such as a database, a Web API, or an external cache) fails, Kubernetes might restart all application instances and create cascading failures. - -As for the "`readiness`" probe, the choice of checking external systems must be made carefully by the application developers. -For this reason, Spring Boot does not include any additional health checks in the readiness probe. -If the <> is unready, Kubernetes does not route traffic to that instance. -Some external systems might not be shared by application instances, in which case they could be included in a readiness probe. -Other external systems might not be essential to the application (the application could have circuit breakers and fallbacks), in which case they definitely should not be included. -Unfortunately, an external system that is shared by all application instances is common, and you have to make a judgement call: Include it in the readiness probe and expect that the application is taken out of service when the external service is down or leave it out and deal with failures higher up the stack, perhaps by using a circuit breaker in the caller. - -NOTE: If all instances of an application are unready, a Kubernetes Service with `type=ClusterIP` or `NodePort` does not accept any incoming connections. -There is no HTTP error response (503 and so on), since there is no connection. -A service with `type=LoadBalancer` might or might not accept connections, depending on the provider. -A service that has an explicit https://kubernetes.io/docs/concepts/services-networking/ingress/[ingress] also responds in a way that depends on the implementation -- the ingress service itself has to decide how to handle the "`connection refused`" from downstream. -HTTP 503 is quite likely in the case of both load balancer and ingress. - -Also, if an application uses Kubernetes https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/[autoscaling], it may react differently to applications being taken out of the load-balancer, depending on its autoscaler configuration. - - - -[[actuator.endpoints.kubernetes-probes.lifecycle]] -==== Application Lifecycle and Probe States -An important aspect of the Kubernetes Probes support is its consistency with the application lifecycle. -There is a significant difference between the `AvailabilityState` (which is the in-memory, internal state of the application) -and the actual probe (which exposes that state). -Depending on the phase of application lifecycle, the probe might not be available. - -Spring Boot publishes <>, -and probes can listen to such events and expose the `AvailabilityState` information. - -The following tables show the `AvailabilityState` and the state of HTTP connectors at different stages. - -When a Spring Boot application starts: - -[cols="2,2,2,3,5"] -|=== -|Startup phase |LivenessState |ReadinessState |HTTP server |Notes - -|Starting -|`BROKEN` -|`REFUSING_TRAFFIC` -|Not started -|Kubernetes checks the "liveness" Probe and restarts the application if it takes too long. - -|Started -|`CORRECT` -|`REFUSING_TRAFFIC` -|Refuses requests -|The application context is refreshed. The application performs startup tasks and does not receive traffic yet. - -|Ready -|`CORRECT` -|`ACCEPTING_TRAFFIC` -|Accepts requests -|Startup tasks are finished. The application is receiving traffic. -|=== - -When a Spring Boot application shuts down: - -[cols="2,2,2,3,5"] -|=== -|Shutdown phase |Liveness State |Readiness State |HTTP server |Notes - -|Running -|`CORRECT` -|`ACCEPTING_TRAFFIC` -|Accepts requests -|Shutdown has been requested. - -|Graceful shutdown -|`CORRECT` -|`REFUSING_TRAFFIC` -|New requests are rejected -|If enabled, <>. - -|Shutdown complete -|N/A -|N/A -|Server is shut down -|The application context is closed and the application is shut down. -|=== - -TIP: See <> for more information about Kubernetes deployment. - - - -[[actuator.endpoints.info]] -=== Application Information -Application information exposes various information collected from all {spring-boot-actuator-module-code}/info/InfoContributor.java[`InfoContributor`] beans defined in your `ApplicationContext`. -Spring Boot includes a number of auto-configured `InfoContributor` beans, and you can write your own. - - - -[[actuator.endpoints.info.auto-configured-info-contributors]] -==== Auto-configured InfoContributors -When appropriate, Spring auto-configures the following `InfoContributor` beans: - -[cols="1,4,8,4"] -|=== -| ID | Name | Description | Prerequisites - -| `build` -| {spring-boot-actuator-module-code}/info/BuildInfoContributor.java[`BuildInfoContributor`] -| Exposes build information. -| A `META-INF/build-info.properties` resource. - -| `env` -| {spring-boot-actuator-module-code}/info/EnvironmentInfoContributor.java[`EnvironmentInfoContributor`] -| Exposes any property from the `Environment` whose name starts with `info.`. -| None. - -| `git` -| {spring-boot-actuator-module-code}/info/GitInfoContributor.java[`GitInfoContributor`] -| Exposes git information. -| A `git.properties` resource. - -| `java` -| {spring-boot-actuator-module-code}/info/JavaInfoContributor.java[`JavaInfoContributor`] -| Exposes Java runtime information. -| None. - -| `os` -| {spring-boot-actuator-module-code}/info/OsInfoContributor.java[`OsInfoContributor`] -| Exposes Operating System information. -| None. - -|=== - -Whether an individual contributor is enabled is controlled by its `management.info..enabled` property. -Different contributors have different defaults for this property, depending on their prerequisites and the nature of the information that they expose. - -With no prerequisites to indicate that they should be enabled, the `env`, `java`, and `os` contributors are disabled by default. -Each can be enabled by setting its `management.info..enabled` property to `true`. - -The `build` and `git` info contributors are enabled by default. -Each can be disabled by setting its `management.info..enabled` property to `false`. -Alternatively, to disable every contributor that is usually enabled by default, set the configprop:management.info.defaults.enabled[] property to `false`. - - - -[[actuator.endpoints.info.custom-application-information]] -==== Custom Application Information -When the `env` contributor is enabled, you can customize the data exposed by the `info` endpoint by setting `+info.*+` Spring properties. -All `Environment` properties under the `info` key are automatically exposed. -For example, you could add the following settings to your `application.properties` file: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - info: - app: - encoding: "UTF-8" - java: - source: "17" - target: "17" ----- - -[TIP] -==== -Rather than hardcoding those values, you could also <>. - -Assuming you use Maven, you could rewrite the preceding example as follows: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - info: - app: - encoding: "@project.build.sourceEncoding@" - java: - source: "@java.version@" - target: "@java.version@" ----- -==== - - - -[[actuator.endpoints.info.git-commit-information]] -==== Git Commit Information -Another useful feature of the `info` endpoint is its ability to publish information about the state of your `git` source code repository when the project was built. -If a `GitProperties` bean is available, you can use the `info` endpoint to expose these properties. - -TIP: A `GitProperties` bean is auto-configured if a `git.properties` file is available at the root of the classpath. -See "<>" for more detail. - -By default, the endpoint exposes `git.branch`, `git.commit.id`, and `git.commit.time` properties, if present. -If you do not want any of these properties in the endpoint response, they need to be excluded from the `git.properties` file. -If you want to display the full git information (that is, the full content of `git.properties`), use the configprop:management.info.git.mode[] property, as follows: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - info: - git: - mode: "full" ----- - -To disable the git commit information from the `info` endpoint completely, set the configprop:management.info.git.enabled[] property to `false`, as follows: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - info: - git: - enabled: false ----- - - - -[[actuator.endpoints.info.build-information]] -==== Build Information -If a `BuildProperties` bean is available, the `info` endpoint can also publish information about your build. -This happens if a `META-INF/build-info.properties` file is available in the classpath. - -TIP: The Maven and Gradle plugins can both generate that file. -See "<>" for more details. - - - -[[actuator.endpoints.info.java-information]] -==== Java Information -The `info` endpoint publishes information about your Java runtime environment, see {spring-boot-module-api}/info/JavaInfo.html[`JavaInfo`] for more details. - - - -[[actuator.endpoints.info.os-information]] -==== OS Information -The `info` endpoint publishes information about your Operating System, see {spring-boot-module-api}/info/OsInfo.html[`OsInfo`] for more details. - - - -[[actuator.endpoints.info.writing-custom-info-contributors]] -==== Writing Custom InfoContributors -To provide custom application information, you can register Spring beans that implement the {spring-boot-actuator-module-code}/info/InfoContributor.java[`InfoContributor`] interface. - -The following example contributes an `example` entry with a single value: - -include::code:MyInfoContributor[] - -If you reach the `info` endpoint, you should see a response that contains the following additional entry: - -[source,json,indent=0,subs="verbatim"] ----- - { - "example": { - "key" : "value" - } - } ----- diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/loggers.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/loggers.adoc deleted file mode 100644 index 816d5acd9cd9..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/loggers.adoc +++ /dev/null @@ -1,31 +0,0 @@ -[[actuator.loggers]] -== Loggers -Spring Boot Actuator includes the ability to view and configure the log levels of your application at runtime. -You can view either the entire list or an individual logger's configuration, which is made up of both the explicitly configured logging level as well as the effective logging level given to it by the logging framework. -These levels can be one of: - -* `TRACE` -* `DEBUG` -* `INFO` -* `WARN` -* `ERROR` -* `FATAL` -* `OFF` -* `null` - -`null` indicates that there is no explicit configuration. - - - -[[actuator.loggers.configure]] -=== Configure a Logger -To configure a given logger, `POST` a partial entity to the resource's URI, as the following example shows: - -[source,json,indent=0,subs="verbatim"] ----- - { - "configuredLevel": "DEBUG" - } ----- - -TIP: To "`reset`" the specific level of the logger (and use the default configuration instead), you can pass a value of `null` as the `configuredLevel`. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc deleted file mode 100644 index 39a21c43711d..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc +++ /dev/null @@ -1,1194 +0,0 @@ -[[actuator.metrics]] -== Metrics -Spring Boot Actuator provides dependency management and auto-configuration for https://micrometer.io[Micrometer], an application metrics facade that supports {micrometer-docs}[numerous monitoring systems], including: - -- <> -- <> -- <> -- <> -- <> -- <> -- <> -- <> -- <> -- <> -- <> -- <> -- <> -- <> -- <> -- <> -- <> -- <> -- <> - -TIP: To learn more about Micrometer's capabilities, see its {micrometer-docs}[reference documentation], in particular the {micrometer-concepts-docs}[concepts section]. - - - -[[actuator.metrics.getting-started]] -=== Getting started -Spring Boot auto-configures a composite `MeterRegistry` and adds a registry to the composite for each of the supported implementations that it finds on the classpath. -Having a dependency on `micrometer-registry-\{system}` in your runtime classpath is enough for Spring Boot to configure the registry. - -Most registries share common features. -For instance, you can disable a particular registry even if the Micrometer registry implementation is on the classpath. -The following example disables Datadog: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - datadog: - metrics: - export: - enabled: false ----- - -You can also disable all registries unless stated otherwise by the registry-specific property, as the following example shows: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - defaults: - metrics: - export: - enabled: false ----- - -Spring Boot also adds any auto-configured registries to the global static composite registry on the `Metrics` class, unless you explicitly tell it not to: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - metrics: - use-global-registry: false ----- - -You can register any number of `MeterRegistryCustomizer` beans to further configure the registry, such as applying common tags, before any meters are registered with the registry: - -include::code:commontags/MyMeterRegistryConfiguration[] - -You can apply customizations to particular registry implementations by being more specific about the generic type: - -include::code:specifictype/MyMeterRegistryConfiguration[] - -Spring Boot also <> that you can control through configuration or dedicated annotation markers. - - - -[[actuator.metrics.export]] -=== Supported Monitoring Systems -This section briefly describes each of the supported monitoring systems. - - - -[[actuator.metrics.export.appoptics]] -==== AppOptics -By default, the AppOptics registry periodically pushes metrics to `https://api.appoptics.com/v1/measurements`. -To export metrics to SaaS {micrometer-implementation-docs}/appOptics[AppOptics], your API token must be provided: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - appoptics: - metrics: - export: - api-token: "YOUR_TOKEN" ----- - - - -[[actuator.metrics.export.atlas]] -==== Atlas -By default, metrics are exported to {micrometer-implementation-docs}/atlas[Atlas] running on your local machine. -You can provide the location of the https://github.com/Netflix/atlas[Atlas server]: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - atlas: - metrics: - export: - uri: "https://atlas.example.com:7101/api/v1/publish" ----- - - - -[[actuator.metrics.export.datadog]] -==== Datadog -A Datadog registry periodically pushes metrics to https://www.datadoghq.com[datadoghq]. -To export metrics to {micrometer-implementation-docs}/datadog[Datadog], you must provide your API key: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - datadog: - metrics: - export: - api-key: "YOUR_KEY" ----- - -If you additionally provide an application key (optional), then metadata such as meter descriptions, types, and base units will also be exported: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - datadog: - metrics: - export: - api-key: "YOUR_API_KEY" - application-key: "YOUR_APPLICATION_KEY" ----- - -By default, metrics are sent to the Datadog US https://docs.datadoghq.com/getting_started/site[site] (`https://api.datadoghq.com`). -If your Datadog project is hosted on one of the other sites, or you need to send metrics through a proxy, configure the URI accordingly: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - datadog: - metrics: - export: - uri: "https://api.datadoghq.eu" ----- - -You can also change the interval at which metrics are sent to Datadog: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - datadog: - metrics: - export: - step: "30s" ----- - - - -[[actuator.metrics.export.dynatrace]] -==== Dynatrace -Dynatrace offers two metrics ingest APIs, both of which are implemented for {micrometer-implementation-docs}/dynatrace[Micrometer]. -You can find the Dynatrace documentation on Micrometer metrics ingest {dynatrace-docs}/micrometer-metrics-ingest[here]. -Configuration properties in the `v1` namespace apply only when exporting to the {dynatrace-docs}/api-metrics[Timeseries v1 API]. -Configuration properties in the `v2` namespace apply only when exporting to the {dynatrace-docs}/api-metrics-v2-post-datapoints[Metrics v2 API]. -Note that this integration can export only to either the `v1` or `v2` version of the API at a time, with `v2` being preferred. -If the `device-id` (required for v1 but not used in v2) is set in the `v1` namespace, metrics are exported to the `v1` endpoint. -Otherwise, `v2` is assumed. - - - -[[actuator.metrics.export.dynatrace.v2-api]] -===== v2 API -You can use the v2 API in two ways. - - - -[[actuator.metrics.export.dynatrace.v2-api.auto-config]] -====== Auto-configuration -Dynatrace auto-configuration is available for hosts that are monitored by the OneAgent or by the Dynatrace Operator for Kubernetes. - -**Local OneAgent:** If a OneAgent is running on the host, metrics are automatically exported to the {dynatrace-docs}/local-api[local OneAgent ingest endpoint]. -The ingest endpoint forwards the metrics to the Dynatrace backend. - -**Dynatrace Kubernetes Operator:** When running in Kubernetes with the Dynatrace Operator installed, the registry will automatically pick up your endpoint URI and API token from the operator instead. - -This is the default behavior and requires no special setup beyond a dependency on `io.micrometer:micrometer-registry-dynatrace`. - - - -[[actuator.metrics.export.dynatrace.v2-api.manual-config]] -====== Manual configuration -If no auto-configuration is available, the endpoint of the {dynatrace-docs}/api-metrics-v2-post-datapoints[Metrics v2 API] and an API token are required. -The {dynatrace-docs}/api-authentication[API token] must have the "`Ingest metrics`" (`metrics.ingest`) permission set. -We recommend limiting the scope of the token to this one permission. -You must ensure that the endpoint URI contains the path (for example, `/api/v2/metrics/ingest`): - -The URL of the Metrics API v2 ingest endpoint is different according to your deployment option: - -* SaaS: `+https://{your-environment-id}.live.dynatrace.com/api/v2/metrics/ingest+` -* Managed deployments: `+https://{your-domain}/e/{your-environment-id}/api/v2/metrics/ingest+` - -The example below configures metrics export using the `example` environment id: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - dynatrace: - metrics: - export: - uri: "https://example.live.dynatrace.com/api/v2/metrics/ingest" - api-token: "YOUR_TOKEN" ----- - -When using the Dynatrace v2 API, the following optional features are available (more details can be found in the {dynatrace-docs}/micrometer-metrics-ingest#dt-configuration-properties[Dynatrace documentation]): - -* Metric key prefix: Sets a prefix that is prepended to all exported metric keys. -* Enrich with Dynatrace metadata: If a OneAgent or Dynatrace operator is running, enrich metrics with additional metadata (for example, about the host, process, or pod). -* Default dimensions: Specify key-value pairs that are added to all exported metrics. -If tags with the same key are specified with Micrometer, they overwrite the default dimensions. -* Use Dynatrace Summary instruments: In some cases the Micrometer Dynatrace registry created metrics that were rejected. -In Micrometer 1.9.x, this was fixed by introducing Dynatrace-specific summary instruments. -Setting this toggle to `false` forces Micrometer to fall back to the behavior that was the default before 1.9.x. -It should only be used when encountering problems while migrating from Micrometer 1.8.x to 1.9.x. - -It is possible to not specify a URI and API token, as shown in the following example. -In this scenario, the automatically configured endpoint is used: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - dynatrace: - metrics: - export: - # Specify uri and api-token here if not using the local OneAgent endpoint. - v2: - metric-key-prefix: "your.key.prefix" - enrich-with-dynatrace-metadata: true - default-dimensions: - key1: "value1" - key2: "value2" - use-dynatrace-summary-instruments: true # (default: true) ----- - - - -[[actuator.metrics.export.dynatrace.v1-api]] -===== v1 API (Legacy) -The Dynatrace v1 API metrics registry pushes metrics to the configured URI periodically by using the {dynatrace-docs}/api-metrics[Timeseries v1 API]. -For backwards-compatibility with existing setups, when `device-id` is set (required for v1, but not used in v2), metrics are exported to the Timeseries v1 endpoint. -To export metrics to {micrometer-implementation-docs}/dynatrace[Dynatrace], your API token, device ID, and URI must be provided: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - dynatrace: - metrics: - export: - uri: "https://{your-environment-id}.live.dynatrace.com" - api-token: "YOUR_TOKEN" - v1: - device-id: "YOUR_DEVICE_ID" ----- - -For the v1 API, you must specify the base environment URI without a path, as the v1 endpoint path is added automatically. - - - -[[actuator.metrics.export.dynatrace.version-independent-settings]] -===== Version-independent Settings -In addition to the API endpoint and token, you can also change the interval at which metrics are sent to Dynatrace. -The default export interval is `60s`. -The following example sets the export interval to 30 seconds: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - dynatrace: - metrics: - export: - step: "30s" ----- - -You can find more information on how to set up the Dynatrace exporter for Micrometer in the {micrometer-implementation-docs}/dynatrace[Micrometer documentation] and the {dynatrace-docs}/micrometer-metrics-ingest[Dynatrace documentation]. - - - -[[actuator.metrics.export.elastic]] -==== Elastic -By default, metrics are exported to {micrometer-implementation-docs}/elastic[Elastic] running on your local machine. -You can provide the location of the Elastic server to use by using the following property: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - elastic: - metrics: - export: - host: "https://elastic.example.com:8086" ----- - -[[actuator.metrics.export.ganglia]] -==== Ganglia -By default, metrics are exported to {micrometer-implementation-docs}/ganglia[Ganglia] running on your local machine. -You can provide the http://ganglia.sourceforge.net[Ganglia server] host and port, as the following example shows: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - ganglia: - metrics: - export: - host: "ganglia.example.com" - port: 9649 ----- - - - -[[actuator.metrics.export.graphite]] -==== Graphite -By default, metrics are exported to {micrometer-implementation-docs}/graphite[Graphite] running on your local machine. -You can provide the https://graphiteapp.org[Graphite server] host and port, as the following example shows: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - graphite: - metrics: - export: - host: "graphite.example.com" - port: 9004 ----- - -Micrometer provides a default `HierarchicalNameMapper` that governs how a dimensional meter ID is {micrometer-implementation-docs}/graphite#_hierarchical_name_mapping[mapped to flat hierarchical names]. - -[TIP] -==== -To take control over this behavior, define your `GraphiteMeterRegistry` and supply your own `HierarchicalNameMapper`. -An auto-configured `GraphiteConfig` and `Clock` beans are provided unless you define your own: - -include::code:MyGraphiteConfiguration[] -==== - - - -[[actuator.metrics.export.humio]] -==== Humio -By default, the Humio registry periodically pushes metrics to https://cloud.humio.com. -To export metrics to SaaS {micrometer-implementation-docs}/humio[Humio], you must provide your API token: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - humio: - metrics: - export: - api-token: "YOUR_TOKEN" ----- - -You should also configure one or more tags to identify the data source to which metrics are pushed: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - humio: - metrics: - export: - tags: - alpha: "a" - bravo: "b" ----- - - - -[[actuator.metrics.export.influx]] -==== Influx -By default, metrics are exported to an {micrometer-implementation-docs}/influx[Influx] v1 instance running on your local machine with the default configuration. -To export metrics to InfluxDB v2, configure the `org`, `bucket`, and authentication `token` for writing metrics. -You can provide the location of the https://www.influxdata.com[Influx server] to use by using: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - influx: - metrics: - export: - uri: "https://influx.example.com:8086" ----- - - - -[[actuator.metrics.export.jmx]] -==== JMX -Micrometer provides a hierarchical mapping to {micrometer-implementation-docs}/jmx[JMX], primarily as a cheap and portable way to view metrics locally. -By default, metrics are exported to the `metrics` JMX domain. -You can provide the domain to use by using: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - jmx: - metrics: - export: - domain: "com.example.app.metrics" ----- - -Micrometer provides a default `HierarchicalNameMapper` that governs how a dimensional meter ID is {micrometer-implementation-docs}/jmx#_hierarchical_name_mapping[mapped to flat hierarchical names]. - -[TIP] -==== -To take control over this behavior, define your `JmxMeterRegistry` and supply your own `HierarchicalNameMapper`. -An auto-configured `JmxConfig` and `Clock` beans are provided unless you define your own: - -include::code:MyJmxConfiguration[] -==== - - - -[[actuator.metrics.export.kairos]] -==== KairosDB -By default, metrics are exported to {micrometer-implementation-docs}/kairos[KairosDB] running on your local machine. -You can provide the location of the https://kairosdb.github.io/[KairosDB server] to use by using: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - kairos: - metrics: - export: - uri: "https://kairosdb.example.com:8080/api/v1/datapoints" ----- - - - -[[actuator.metrics.export.newrelic]] -==== New Relic -A New Relic registry periodically pushes metrics to {micrometer-implementation-docs}/new-relic[New Relic]. -To export metrics to https://newrelic.com[New Relic], you must provide your API key and account ID: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - newrelic: - metrics: - export: - api-key: "YOUR_KEY" - account-id: "YOUR_ACCOUNT_ID" ----- - -You can also change the interval at which metrics are sent to New Relic: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - newrelic: - metrics: - export: - step: "30s" ----- - -By default, metrics are published through REST calls, but you can also use the Java Agent API if you have it on the classpath: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - newrelic: - metrics: - export: - client-provider-type: "insights-agent" ----- - -Finally, you can take full control by defining your own `NewRelicClientProvider` bean. - - - -[[actuator.metrics.export.otlp]] -==== OpenTelemetry -By default, metrics are exported to {micrometer-implementation-docs}/otlp[OpenTelemetry] running on your local machine. -You can provide the location of the https://opentelemetry.io/[OpenTelemetry metric endpoint] to use by using: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - otlp: - metrics: - export: - url: "https://otlp.example.com:4318/v1/metrics" ----- - - - -[[actuator.metrics.export.prometheus]] -==== Prometheus -{micrometer-implementation-docs}/prometheus[Prometheus] expects to scrape or poll individual application instances for metrics. -Spring Boot provides an actuator endpoint at `/actuator/prometheus` to present a https://prometheus.io[Prometheus scrape] with the appropriate format. - -TIP: By default, the endpoint is not available and must be exposed. See <> for more details. - -The following example `scrape_config` adds to `prometheus.yml`: - -[source,yaml,indent=0,subs="verbatim"] ----- - scrape_configs: - - job_name: "spring" - metrics_path: "/actuator/prometheus" - static_configs: - - targets: ["HOST:PORT"] ----- - -https://prometheus.io/docs/prometheus/latest/feature_flags/#exemplars-storage[Prometheus Exemplars] are also supported. -To enable this feature, a `SpanContextSupplier` bean should be present. -If you use https://micrometer.io/docs/tracing[Micrometer Tracing], this will be auto-configured for you, but you can always create your own if you want. -Please check the https://prometheus.io/docs/prometheus/latest/feature_flags/#exemplars-storage[Prometheus Docs], since this feature needs to be explicitly enabled on Prometheus' side, and it is only supported using the https://github.com/OpenObservability/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#exemplars[OpenMetrics] format. - -For ephemeral or batch jobs that may not exist long enough to be scraped, you can use https://github.com/prometheus/pushgateway[Prometheus Pushgateway] support to expose the metrics to Prometheus. -To enable Prometheus Pushgateway support, add the following dependency to your project: - -[source,xml,indent=0,subs="verbatim"] ----- - - io.prometheus - simpleclient_pushgateway - ----- - -When the Prometheus Pushgateway dependency is present on the classpath and the configprop:management.prometheus.metrics.export.pushgateway.enabled[] property is set to `true`, a `PrometheusPushGatewayManager` bean is auto-configured. -This manages the pushing of metrics to a Prometheus Pushgateway. - -You can tune the `PrometheusPushGatewayManager` by using properties under `management.prometheus.metrics.export.pushgateway`. -For advanced configuration, you can also provide your own `PrometheusPushGatewayManager` bean. - - - -[[actuator.metrics.export.signalfx]] -==== SignalFx -SignalFx registry periodically pushes metrics to {micrometer-implementation-docs}/signalFx[SignalFx]. -To export metrics to https://www.signalfx.com[SignalFx], you must provide your access token: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - signalfx: - metrics: - export: - access-token: "YOUR_ACCESS_TOKEN" ----- - -You can also change the interval at which metrics are sent to SignalFx: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - signalfx: - metrics: - export: - step: "30s" ----- - - - -[[actuator.metrics.export.simple]] -==== Simple -Micrometer ships with a simple, in-memory backend that is automatically used as a fallback if no other registry is configured. -This lets you see what metrics are collected in the <>. - -The in-memory backend disables itself as soon as you use any other available backend. -You can also disable it explicitly: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - simple: - metrics: - export: - enabled: false ----- - - - -[[actuator.metrics.export.stackdriver]] -==== Stackdriver -The Stackdriver registry periodically pushes metrics to https://cloud.google.com/stackdriver/[Stackdriver]. -To export metrics to SaaS {micrometer-implementation-docs}/stackdriver[Stackdriver], you must provide your Google Cloud project ID: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - stackdriver: - metrics: - export: - project-id: "my-project" ----- - -You can also change the interval at which metrics are sent to Stackdriver: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - stackdriver: - metrics: - export: - step: "30s" ----- - - - -[[actuator.metrics.export.statsd]] -==== StatsD -The StatsD registry eagerly pushes metrics over UDP to a StatsD agent. -By default, metrics are exported to a {micrometer-implementation-docs}/statsD[StatsD] agent running on your local machine. -You can provide the StatsD agent host, port, and protocol to use by using: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - statsd: - metrics: - export: - host: "statsd.example.com" - port: 9125 - protocol: "udp" ----- - -You can also change the StatsD line protocol to use (it defaults to Datadog): - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - statsd: - metrics: - export: - flavor: "etsy" ----- - - - -[[actuator.metrics.export.wavefront]] -==== Wavefront -The Wavefront registry periodically pushes metrics to {micrometer-implementation-docs}/wavefront[Wavefront]. -If you are exporting metrics to https://www.wavefront.com/[Wavefront] directly, you must provide your API token: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - wavefront: - api-token: "YOUR_API_TOKEN" ----- - -Alternatively, you can use a Wavefront sidecar or an internal proxy in your environment to forward metrics data to the Wavefront API host: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - wavefront: - uri: "proxy://localhost:2878" ----- - -NOTE: If you publish metrics to a Wavefront proxy (as described in https://docs.wavefront.com/proxies_installing.html[the Wavefront documentation]), the host must be in the `proxy://HOST:PORT` format. - -You can also change the interval at which metrics are sent to Wavefront: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - wavefront: - metrics: - export: - step: "30s" ----- - - - -[[actuator.metrics.supported]] -=== Supported Metrics and Meters -Spring Boot provides automatic meter registration for a wide variety of technologies. -In most situations, the defaults provide sensible metrics that can be published to any of the supported monitoring systems. - - - -[[actuator.metrics.supported.jvm]] -==== JVM Metrics -Auto-configuration enables JVM Metrics by using core Micrometer classes. -JVM metrics are published under the `jvm.` meter name. - -The following JVM metrics are provided: - -* Various memory and buffer pool details -* Statistics related to garbage collection -* Thread utilization -* The number of classes loaded and unloaded -* JVM version information -* JIT compilation time - - - -[[actuator.metrics.supported.system]] -==== System Metrics -Auto-configuration enables system metrics by using core Micrometer classes. -System metrics are published under the `system.`, `process.`, and `disk.` meter names. - -The following system metrics are provided: - -* CPU metrics -* File descriptor metrics -* Uptime metrics (both the amount of time the application has been running and a fixed gauge of the absolute start time) -* Disk space available - - - -[[actuator.metrics.supported.application-startup]] -==== Application Startup Metrics -Auto-configuration exposes application startup time metrics: - -* `application.started.time`: time taken to start the application. -* `application.ready.time`: time taken for the application to be ready to service requests. - -Metrics are tagged by the fully qualified name of the application class. - - - -[[actuator.metrics.supported.logger]] -==== Logger Metrics -Auto-configuration enables the event metrics for both Logback and Log4J2. -The details are published under the `log4j2.events.` or `logback.events.` meter names. - - - -[[actuator.metrics.supported.tasks]] -==== Task Execution and Scheduling Metrics -Auto-configuration enables the instrumentation of all available `ThreadPoolTaskExecutor` and `ThreadPoolTaskScheduler` beans, as long as the underling `ThreadPoolExecutor` is available. -Metrics are tagged by the name of the executor, which is derived from the bean name. - - - -[[actuator.metrics.supported.spring-mvc]] -==== Spring MVC Metrics - -Auto-configuration enables the instrumentation of all requests handled by Spring MVC controllers and functional handlers. -By default, metrics are generated with the name, `http.server.requests`. -You can customize the name by setting the configprop:management.observations.http.server.requests.name[] property. - -See the {spring-framework-docs}/integration/observability.html#observability.http-server.servlet[Spring Framework reference documentation for more information on produced observations]. - -To add to the default tags, provide a `@Bean` that extends `DefaultServerRequestObservationConvention` from the `org.springframework.http.server.observation` package. -To replace the default tags, provide a `@Bean` that implements `ServerRequestObservationConvention`. - - -TIP: In some cases, exceptions handled in web controllers are not recorded as request metrics tags. -Applications can opt in and record exceptions by <>. - -By default, all requests are handled. -To customize the filter, provide a `@Bean` that implements `FilterRegistrationBean`. - - - -[[actuator.metrics.supported.spring-webflux]] -==== Spring WebFlux Metrics -Auto-configuration enables the instrumentation of all requests handled by Spring WebFlux controllers and functional handlers. -By default, metrics are generated with the name, `http.server.requests`. -You can customize the name by setting the configprop:management.observations.http.server.requests.name[] property. - -See the {spring-framework-docs}/integration/observability.html#observability.http-server.reactive[Spring Framework reference documentation for more information on produced observations]. - -To add to the default tags, provide a `@Bean` that extends `DefaultServerRequestObservationConvention` from the `org.springframework.http.server.reactive.observation` package. -To replace the default tags, provide a `@Bean` that implements `ServerRequestObservationConvention`. - -TIP: In some cases, exceptions handled in controllers and handler functions are not recorded as request metrics tags. -Applications can opt in and record exceptions by <>. - - - -[[actuator.metrics.supported.jersey]] -==== Jersey Server Metrics -Auto-configuration enables the instrumentation of all requests handled by the Jersey JAX-RS implementation. -By default, metrics are generated with the name, `http.server.requests`. -You can customize the name by setting the configprop:management.observations.http.server.requests.name[] property. - -By default, Jersey server metrics are tagged with the following information: - -|=== -| Tag | Description - -| `exception` -| The simple class name of any exception that was thrown while handling the request. - -| `method` -| The request's method (for example, `GET` or `POST`) - -| `outcome` -| The request's outcome, based on the status code of the response. - 1xx is `INFORMATIONAL`, 2xx is `SUCCESS`, 3xx is `REDIRECTION`, 4xx is `CLIENT_ERROR`, and 5xx is `SERVER_ERROR` - -| `status` -| The response's HTTP status code (for example, `200` or `500`) - -| `uri` -| The request's URI template prior to variable substitution, if possible (for example, `/api/person/\{id}`) -|=== - -To customize the tags, provide a `@Bean` that implements `JerseyTagsProvider`. - - - -[[actuator.metrics.supported.http-clients]] -==== HTTP Client Metrics -Spring Boot Actuator manages the instrumentation of both `RestTemplate` and `WebClient`. -For that, you have to inject the auto-configured builder and use it to create instances: - -* `RestTemplateBuilder` for `RestTemplate` -* `WebClient.Builder` for `WebClient` - -You can also manually apply the customizers responsible for this instrumentation, namely `ObservationRestTemplateCustomizer` and `ObservationWebClientCustomizer`. - -By default, metrics are generated with the name, `http.client.requests`. -You can customize the name by setting the configprop:management.observations.http.client.requests.name[] property. - -See the {spring-framework-docs}/integration/observability.html#observability.http-client[Spring Framework reference documentation for more information on produced observations]. - -To customize the tags when using `RestTemplate`, provide a `@Bean` that implements `ClientRequestObservationConvention` from the `org.springframework.http.client.observation` package. -To customize the tags when using `WebClient`, provide a `@Bean` that implements `ClientRequestObservationConvention` from the `org.springframework.web.reactive.function.client` package. - - - -[[actuator.metrics.supported.tomcat]] -==== Tomcat Metrics -Auto-configuration enables the instrumentation of Tomcat only when an `MBeanRegistry` is enabled. -By default, the `MBeanRegistry` is disabled, but you can enable it by setting configprop:server.tomcat.mbeanregistry.enabled[] to `true`. - -Tomcat metrics are published under the `tomcat.` meter name. - - - -[[actuator.metrics.supported.cache]] -==== Cache Metrics -Auto-configuration enables the instrumentation of all available `Cache` instances on startup, with metrics prefixed with `cache`. -Cache instrumentation is standardized for a basic set of metrics. -Additional, cache-specific metrics are also available. - -The following cache libraries are supported: - -* Cache2k -* Caffeine -* Hazelcast -* Any compliant JCache (JSR-107) implementation -* Redis - -Metrics are tagged by the name of the cache and by the name of the `CacheManager`, which is derived from the bean name. - -NOTE: Only caches that are configured on startup are bound to the registry. -For caches not defined in the cache’s configuration, such as caches created on the fly or programmatically after the startup phase, an explicit registration is required. -A `CacheMetricsRegistrar` bean is made available to make that process easier. - - - -[[actuator.metrics.supported.spring-batch]] -==== Spring Batch Metrics - -See the {spring-batch-docs}monitoring-and-metrics.html[Spring Batch reference documentation]. - - - -[[actuator.metrics.supported.spring-graphql]] -==== Spring GraphQL Metrics - -See the {spring-graphql-docs}[Spring GraphQL reference documentation]. - - - -[[actuator.metrics.supported.jdbc]] -==== DataSource Metrics -Auto-configuration enables the instrumentation of all available `DataSource` objects with metrics prefixed with `jdbc.connections`. -Data source instrumentation results in gauges that represent the currently active, idle, maximum allowed, and minimum allowed connections in the pool. - -Metrics are also tagged by the name of the `DataSource` computed based on the bean name. - -TIP: By default, Spring Boot provides metadata for all supported data sources. -You can add additional `DataSourcePoolMetadataProvider` beans if your favorite data source is not supported. -See `DataSourcePoolMetadataProvidersConfiguration` for examples. - -Also, Hikari-specific metrics are exposed with a `hikaricp` prefix. -Each metric is tagged by the name of the pool (you can control it with `spring.datasource.name`). - - - -[[actuator.metrics.supported.hibernate]] -==== Hibernate Metrics -If `org.hibernate.orm:hibernate-micrometer` is on the classpath, all available Hibernate `EntityManagerFactory` instances that have statistics enabled are instrumented with a metric named `hibernate`. - -Metrics are also tagged by the name of the `EntityManagerFactory`, which is derived from the bean name. - -To enable statistics, the standard JPA property `hibernate.generate_statistics` must be set to `true`. -You can enable that on the auto-configured `EntityManagerFactory`: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - jpa: - properties: - "[hibernate.generate_statistics]": true ----- - - - -[[actuator.metrics.supported.spring-data-repository]] -==== Spring Data Repository Metrics -Auto-configuration enables the instrumentation of all Spring Data `Repository` method invocations. -By default, metrics are generated with the name, `spring.data.repository.invocations`. -You can customize the name by setting the configprop:management.metrics.data.repository.metric-name[] property. - -The `@Timed` annotation from the `io.micrometer.core.annotation` package is supported on `Repository` interfaces and methods. -If you do not want to record metrics for all `Repository` invocations, you can set configprop:management.metrics.data.repository.autotime.enabled[] to `false` and exclusively use `@Timed` annotations instead. - -NOTE: A `@Timed` annotation with `longTask = true` enables a long task timer for the method. -Long task timers require a separate metric name and can be stacked with a short task timer. - -By default, repository invocation related metrics are tagged with the following information: - -|=== -| Tag | Description - -| `repository` -| The simple class name of the source `Repository`. - -| `method` -| The name of the `Repository` method that was invoked. - -| `state` -| The result state (`SUCCESS`, `ERROR`, `CANCELED`, or `RUNNING`). - -| `exception` -| The simple class name of any exception that was thrown from the invocation. -|=== - -To replace the default tags, provide a `@Bean` that implements `RepositoryTagsProvider`. - - - -[[actuator.metrics.supported.rabbitmq]] -==== RabbitMQ Metrics -Auto-configuration enables the instrumentation of all available RabbitMQ connection factories with a metric named `rabbitmq`. - - - -[[actuator.metrics.supported.spring-integration]] -==== Spring Integration Metrics -Spring Integration automatically provides {spring-integration-docs}system-management.html#micrometer-integration[Micrometer support] whenever a `MeterRegistry` bean is available. -Metrics are published under the `spring.integration.` meter name. - - - -[[actuator.metrics.supported.kafka]] -==== Kafka Metrics -Auto-configuration registers a `MicrometerConsumerListener` and `MicrometerProducerListener` for the auto-configured consumer factory and producer factory, respectively. -It also registers a `KafkaStreamsMicrometerListener` for `StreamsBuilderFactoryBean`. -For more detail, see the {spring-kafka-docs}#micrometer-native[Micrometer Native Metrics] section of the Spring Kafka documentation. - - - -[[actuator.metrics.supported.mongodb]] -==== MongoDB Metrics -This section briefly describes the available metrics for MongoDB. - - - -[[actuator.metrics.supported.mongodb.command]] -===== MongoDB Command Metrics -Auto-configuration registers a `MongoMetricsCommandListener` with the auto-configured `MongoClient`. - -A timer metric named `mongodb.driver.commands` is created for each command issued to the underlying MongoDB driver. -Each metric is tagged with the following information by default: -|=== -| Tag | Description - -| `command` -| The name of the command issued. - -| `cluster.id` -| The identifier of the cluster to which the command was sent. - -| `server.address` -| The address of the server to which the command was sent. - -| `status` -| The outcome of the command (`SUCCESS` or `FAILED`). -|=== - -To replace the default metric tags, define a `MongoCommandTagsProvider` bean, as the following example shows: - -include::code:MyCommandTagsProviderConfiguration[] - -To disable the auto-configured command metrics, set the following property: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - metrics: - mongo: - command: - enabled: false ----- - - - -[[actuator.metrics.supported.mongodb.connection-pool]] -===== MongoDB Connection Pool Metrics -Auto-configuration registers a `MongoMetricsConnectionPoolListener` with the auto-configured `MongoClient`. - -The following gauge metrics are created for the connection pool: - -* `mongodb.driver.pool.size` reports the current size of the connection pool, including idle and and in-use members. -* `mongodb.driver.pool.checkedout` reports the count of connections that are currently in use. -* `mongodb.driver.pool.waitqueuesize` reports the current size of the wait queue for a connection from the pool. - -Each metric is tagged with the following information by default: -|=== -| Tag | Description - -| `cluster.id` -| The identifier of the cluster to which the connection pool corresponds. - -| `server.address` -| The address of the server to which the connection pool corresponds. -|=== - -To replace the default metric tags, define a `MongoConnectionPoolTagsProvider` bean: - -include::code:MyConnectionPoolTagsProviderConfiguration[] - -To disable the auto-configured connection pool metrics, set the following property: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - metrics: - mongo: - connectionpool: - enabled: false ----- - - - -[[actuator.metrics.supported.jetty]] -==== Jetty Metrics -Auto-configuration binds metrics for Jetty's `ThreadPool` by using Micrometer's `JettyServerThreadPoolMetrics`. -Metrics for Jetty's `Connector` instances are bound by using Micrometer's `JettyConnectionMetrics` and, when configprop:server.ssl.enabled[] is set to `true`, Micrometer's `JettySslHandshakeMetrics`. - - - -[[actuator.metrics.supported.timed-annotation]] -==== @Timed Annotation Support -To use `@Timed` where it is not directly supported by Spring Boot, refer to the {micrometer-concepts-docs}#_the_timed_annotation[Micrometer documentation]. - - - -[[actuator.metrics.supported.redis]] -==== Redis Metrics -Auto-configuration registers a `MicrometerCommandLatencyRecorder` for the auto-configured `LettuceConnectionFactory`. -For more detail, see the {lettuce-docs}#command.latency.metrics.micrometer[Micrometer Metrics section] of the Lettuce documentation. - - - -[[actuator.metrics.registering-custom]] -=== Registering Custom Metrics -To register custom metrics, inject `MeterRegistry` into your component: - -include::code:MyBean[] - -If your metrics depend on other beans, we recommend that you use a `MeterBinder` to register them: - -include::code:MyMeterBinderConfiguration[] - -Using a `MeterBinder` ensures that the correct dependency relationships are set up and that the bean is available when the metric's value is retrieved. -A `MeterBinder` implementation can also be useful if you find that you repeatedly instrument a suite of metrics across components or applications. - -NOTE: By default, metrics from all `MeterBinder` beans are automatically bound to the Spring-managed `MeterRegistry`. - - - -[[actuator.metrics.customizing]] -=== Customizing Individual Metrics -If you need to apply customizations to specific `Meter` instances, you can use the `io.micrometer.core.instrument.config.MeterFilter` interface. - -For example, if you want to rename the `mytag.region` tag to `mytag.area` for all meter IDs beginning with `com.example`, you can do the following: - -include::code:MyMetricsFilterConfiguration[] - -NOTE: By default, all `MeterFilter` beans are automatically bound to the Spring-managed `MeterRegistry`. -Make sure to register your metrics by using the Spring-managed `MeterRegistry` and not any of the static methods on `Metrics`. -These use the global registry that is not Spring-managed. - - - -[[actuator.metrics.customizing.common-tags]] -==== Common Tags -Common tags are generally used for dimensional drill-down on the operating environment, such as host, instance, region, stack, and others. -Commons tags are applied to all meters and can be configured, as the following example shows: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - metrics: - tags: - region: "us-east-1" - stack: "prod" ----- - -The preceding example adds `region` and `stack` tags to all meters with a value of `us-east-1` and `prod`, respectively. - -NOTE: The order of common tags is important if you use Graphite. -As the order of common tags cannot be guaranteed by using this approach, Graphite users are advised to define a custom `MeterFilter` instead. - - - -[[actuator.metrics.customizing.per-meter-properties]] -==== Per-meter Properties -In addition to `MeterFilter` beans, you can apply a limited set of customization on a per-meter basis using properties. -Per-meter customizations are applied, using Spring Boot's `PropertiesMeterFilter`, to any meter IDs that start with the given name. -The following example filters out any meters that have an ID starting with `example.remote`. - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - metrics: - enable: - example: - remote: false ----- - -The following properties allow per-meter customization: - -.Per-meter customizations -|=== -| Property | Description - -| configprop:management.metrics.enable[] -| Whether to accept meters with certain IDs. - Meters that are not accepted are filtered from the `MeterRegistry`. - -| configprop:management.metrics.distribution.percentiles-histogram[] -| Whether to publish a histogram suitable for computing aggregable (across dimension) percentile approximations. - -| configprop:management.metrics.distribution.minimum-expected-value[], configprop:management.metrics.distribution.maximum-expected-value[] -| Publish fewer histogram buckets by clamping the range of expected values. - -| configprop:management.metrics.distribution.percentiles[] -| Publish percentile values computed in your application - -| configprop:management.metrics.distribution.expiry[], configprop:management.metrics.distribution.buffer-length[] -| Give greater weight to recent samples by accumulating them in ring buffers which rotate after a configurable expiry, with a -configurable buffer length. - -| configprop:management.metrics.distribution.slo[] -| Publish a cumulative histogram with buckets defined by your service-level objectives. -|=== - -For more details on the concepts behind `percentiles-histogram`, `percentiles`, and `slo`, see the {micrometer-concepts-docs}#_histograms_and_percentiles["`Histograms and percentiles`" section] of the Micrometer documentation. - - - -[[actuator.metrics.endpoint]] -=== Metrics Endpoint -Spring Boot provides a `metrics` endpoint that you can use diagnostically to examine the metrics collected by an application. -The endpoint is not available by default and must be exposed. -See <> for more details. - -Navigating to `/actuator/metrics` displays a list of available meter names. -You can drill down to view information about a particular meter by providing its name as a selector -- for example, `/actuator/metrics/jvm.memory.max`. - -[TIP] -==== -The name you use here should match the name used in the code, not the name after it has been naming-convention normalized for a monitoring system to which it is shipped. -In other words, if `jvm.memory.max` appears as `jvm_memory_max` in Prometheus because of its snake case naming convention, you should still use `jvm.memory.max` as the selector when inspecting the meter in the `metrics` endpoint. -==== - -You can also add any number of `tag=KEY:VALUE` query parameters to the end of the URL to dimensionally drill down on a meter -- for example, `/actuator/metrics/jvm.memory.max?tag=area:nonheap`. - -[TIP] -==== -The reported measurements are the _sum_ of the statistics of all meters that match the meter name and any tags that have been applied. -In the preceding example, the returned `Value` statistic is the sum of the maximum memory footprints of the "`Code Cache`", "`Compressed Class Space`", and "`Metaspace`" areas of the heap. -If you wanted to see only the maximum size for the "`Metaspace`", you could add an additional `tag=id:Metaspace` -- that is, `/actuator/metrics/jvm.memory.max?tag=area:nonheap&tag=id:Metaspace`. -==== - -[[actuator.metrics.micrometer-observation]] -=== Integration with Micrometer Observation -A `DefaultMeterObservationHandler` is automatically registered on the `ObservationRegistry`, which creates metrics for every completed observation. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/monitoring.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/monitoring.adoc deleted file mode 100644 index 499ec66a1351..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/monitoring.adoc +++ /dev/null @@ -1,148 +0,0 @@ -[[actuator.monitoring]] -== Monitoring and Management Over HTTP -If you are developing a web application, Spring Boot Actuator auto-configures all enabled endpoints to be exposed over HTTP. -The default convention is to use the `id` of the endpoint with a prefix of `/actuator` as the URL path. -For example, `health` is exposed as `/actuator/health`. - -TIP: Actuator is supported natively with Spring MVC, Spring WebFlux, and Jersey. -If both Jersey and Spring MVC are available, Spring MVC is used. - -NOTE: Jackson is a required dependency in order to get the correct JSON responses as documented in the API documentation ({spring-boot-actuator-restapi-docs}[HTML] or {spring-boot-actuator-restapi-pdfdocs}[PDF]). - - - -[[actuator.monitoring.customizing-management-server-context-path]] -=== Customizing the Management Endpoint Paths -Sometimes, it is useful to customize the prefix for the management endpoints. -For example, your application might already use `/actuator` for another purpose. -You can use the configprop:management.endpoints.web.base-path[] property to change the prefix for your management endpoint, as the following example shows: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - endpoints: - web: - base-path: "/manage" ----- - -The preceding `application.properties` example changes the endpoint from `/actuator/\{id}` to `/manage/\{id}` (for example, `/manage/info`). - -NOTE: Unless the management port has been configured to <>, `management.endpoints.web.base-path` is relative to `server.servlet.context-path` (for servlet web applications) or `spring.webflux.base-path` (for reactive web applications). -If `management.server.port` is configured, `management.endpoints.web.base-path` is relative to `management.server.base-path`. - -If you want to map endpoints to a different path, you can use the configprop:management.endpoints.web.path-mapping[] property. - -The following example remaps `/actuator/health` to `/healthcheck`: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - endpoints: - web: - base-path: "/" - path-mapping: - health: "healthcheck" ----- - - - -[[actuator.monitoring.customizing-management-server-port]] -=== Customizing the Management Server Port -Exposing management endpoints by using the default HTTP port is a sensible choice for cloud-based deployments. -If, however, your application runs inside your own data center, you may prefer to expose endpoints by using a different HTTP port. - -You can set the configprop:management.server.port[] property to change the HTTP port, as the following example shows: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - server: - port: 8081 ----- - -NOTE: On Cloud Foundry, by default, applications receive requests only on port 8080 for both HTTP and TCP routing. -If you want to use a custom management port on Cloud Foundry, you need to explicitly set up the application's routes to forward traffic to the custom port. - - - -[[actuator.monitoring.management-specific-ssl]] -=== Configuring Management-specific SSL -When configured to use a custom port, you can also configure the management server with its own SSL by using the various `management.server.ssl.*` properties. -For example, doing so lets a management server be available over HTTP while the main application uses HTTPS, as the following property settings show: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - server: - port: 8443 - ssl: - enabled: true - key-store: "classpath:store.jks" - key-password: "secret" - management: - server: - port: 8080 - ssl: - enabled: false ----- - -Alternatively, both the main server and the management server can use SSL but with different key stores, as follows: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - server: - port: 8443 - ssl: - enabled: true - key-store: "classpath:main.jks" - key-password: "secret" - management: - server: - port: 8080 - ssl: - enabled: true - key-store: "classpath:management.jks" - key-password: "secret" ----- - - - -[[actuator.monitoring.customizing-management-server-address]] -=== Customizing the Management Server Address -You can customize the address on which the management endpoints are available by setting the configprop:management.server.address[] property. -Doing so can be useful if you want to listen only on an internal or ops-facing network or to listen only for connections from `localhost`. - -NOTE: You can listen on a different address only when the port differs from the main server port. - -The following example `application.properties` does not allow remote management connections: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - server: - port: 8081 - address: "127.0.0.1" ----- - - - -[[actuator.monitoring.disabling-http-endpoints]] -=== Disabling HTTP Endpoints -If you do not want to expose endpoints over HTTP, you can set the management port to `-1`, as the following example shows: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - server: - port: -1 ----- - -You can also achieve this by using the configprop:management.endpoints.web.exposure.exclude[] property, as the following example shows: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - endpoints: - web: - exposure: - exclude: "*" ----- diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc deleted file mode 100644 index b7db70d50219..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc +++ /dev/null @@ -1,24 +0,0 @@ -[[actuator.observability]] -== Observability - -Observability is the ability to observe the internal state of a running system from the outside. -It consists of the three pillars logging, metrics and traces. - -For metrics and traces, Spring Boot uses https://micrometer.io/docs/observation[Micrometer Observation]. -To create your own observations (which will lead to metrics and traces), you can inject an `ObservationRegistry`. - -include::code:MyCustomObservation[] - -NOTE: Low cardinality tags will be added to metrics and traces, while high cardinality tags will only be added to traces. - -Beans of type `ObservationPredicate`, `GlobalObservationConvention` and `ObservationHandler` will be automatically registered on the `ObservationRegistry`. -You can additionally register any number of `ObservationRegistryCustomizer` beans to further configure the registry. - -For more details please see the https://micrometer.io/docs/observation[Micrometer Observation documentation]. - -TIP: Observability for JDBC and R2DBC can be configured using separate projects. -For JDBC, the https://github.com/jdbc-observations/datasource-micrometer[Datasource Micrometer project] provides a Spring Boot starter which automatically creates observations when JDBC operations are invoked. -Read more about it https://jdbc-observations.github.io/datasource-micrometer/docs/current/docs/html/[in the reference documentation]. -For R2DBC, the https://github.com/spring-projects-experimental/r2dbc-micrometer-spring-boot[Spring Boot Auto Configuration for R2DBC Observation] creates observations for R2DBC query invocations. - -The next sections will provide more details about logging, metrics and traces. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/process-monitoring.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/process-monitoring.adoc deleted file mode 100644 index 5b877bd10a88..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/process-monitoring.adoc +++ /dev/null @@ -1,31 +0,0 @@ -[[actuator.process-monitoring]] -== Process Monitoring -In the `spring-boot` module, you can find two classes to create files that are often useful for process monitoring: - -* `ApplicationPidFileWriter` creates a file that contains the application PID (by default, in the application directory with a file name of `application.pid`). -* `WebServerPortFileWriter` creates a file (or files) that contain the ports of the running web server (by default, in the application directory with a file name of `application.port`). - -By default, these writers are not activated, but you can enable them: - -* <> -* <> - - - -[[actuator.process-monitoring.configuration]] -=== Extending Configuration -In the `META-INF/spring.factories` file, you can activate the listener (or listeners) that writes a PID file: - -[indent=0] ----- - org.springframework.context.ApplicationListener=\ - org.springframework.boot.context.ApplicationPidFileWriter,\ - org.springframework.boot.web.context.WebServerPortFileWriter ----- - - - -[[actuator.process-monitoring.programmatically]] -=== Programmatically Enabling Process Monitoring -You can also activate a listener by invoking the `SpringApplication.addListeners(...)` method and passing the appropriate `Writer` object. -This method also lets you customize the file name and path in the `Writer` constructor. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/tracing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/tracing.adoc deleted file mode 100644 index fbe341a65ffa..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/tracing.adoc +++ /dev/null @@ -1,184 +0,0 @@ -[[actuator.micrometer-tracing]] -== Tracing -Spring Boot Actuator provides dependency management and auto-configuration for https://micrometer.io/docs/tracing[Micrometer Tracing], a facade for popular tracer libraries. - -TIP: To learn more about Micrometer Tracing capabilities, see its https://micrometer.io/docs/tracing[reference documentation]. - - - -[[actuator.micrometer-tracing.tracers]] -=== Supported Tracers -Spring Boot ships auto-configuration for the following tracers: - -* https://opentelemetry.io/[OpenTelemetry] with https://zipkin.io/[Zipkin], https://docs.wavefront.com/[Wavefront], or https://opentelemetry.io/docs/reference/specification/protocol/[OTLP] -* https://github.com/openzipkin/brave[OpenZipkin Brave] with https://zipkin.io/[Zipkin] or https://docs.wavefront.com/[Wavefront] - - - -[[actuator.micrometer-tracing.getting-started]] -=== Getting Started -We need an example application that we can use to get started with tracing. -For our purposes, the simple "`Hello World!`" web application that's covered in the "`<>`" section will suffice. -We're going to use the OpenTelemetry tracer with Zipkin as trace backend. - -To recap, our main application code looks like this: - -include::code:MyApplication[] - -NOTE: There's an added logger statement in the `home()` method, which will be important later. - -Now we have to add the following dependencies: - -* `org.springframework.boot:spring-boot-starter-actuator` -* `io.micrometer:micrometer-tracing-bridge-otel` - bridges the Micrometer Observation API to OpenTelemetry. -* `io.opentelemetry:opentelemetry-exporter-zipkin` - reports https://micrometer.io/docs/tracing#_glossary[traces] to Zipkin. - -Add the following application properties: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - tracing: - sampling: - probability: 1.0 ----- - -By default, Spring Boot samples only 10% of requests to prevent overwhelming the trace backend. -This property switches it to 100% so that every request is sent to the trace backend. - -To collect and visualize the traces, we need a running trace backend. -We use Zipkin as our trace backend here. -The https://zipkin.io/pages/quickstart[Zipkin Quickstart guide] provides instructions how to start Zipkin locally. - -After Zipkin is running, you can start your application. - -If you open a web browser to `http://localhost:8080`, you should see the following output: - -[indent=0] ----- - Hello World! ----- - -Behind the scenes, an observation has been created for the HTTP request, which in turn gets bridged to OpenTelemetry, which reports a new trace to Zipkin. - -Now open the Zipkin UI at `http://localhost:9411` and press the "Run Query" button to list all collected traces. -You should see one trace. -Press the "Show" button to see the details of that trace. - -TIP: You can include the current trace and span id in the logs by setting the configprop:logging.pattern.level[] property to `%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]` - - - -[[actuator.micrometer-tracing.propagating-traces]] -=== Propagating Traces -To automatically propagate traces over the network, use the auto-configured <> or <> to construct the client. - -WARNING: If you create the `WebClient` or the `RestTemplate` without using the auto-configured builders, automatic trace propagation won't work! - - - -[[actuator.micrometer-tracing.tracer-implementations]] -=== Tracer Implementations -As Micrometer Tracer supports multiple tracer implementations, there are multiple dependency combinations possible with Spring Boot. - -All tracer implementations need the `org.springframework.boot:spring-boot-starter-actuator` dependency. - - - -[[actuator.micrometer-tracing.tracer-implementations.otel-zipkin]] -==== OpenTelemetry With Zipkin -Tracing with OpenTelemetry and reporting to Zipkin requires the following dependencies: - -* `io.micrometer:micrometer-tracing-bridge-otel` - bridges the Micrometer Observation API to OpenTelemetry. -* `io.opentelemetry:opentelemetry-exporter-zipkin` - reports traces to Zipkin. - -Use the `management.zipkin.tracing.*` configuration properties to configure reporting to Zipkin. - - - -[[actuator.micrometer-tracing.tracer-implementations.otel-wavefront]] -==== OpenTelemetry With Wavefront -Tracing with OpenTelemetry and reporting to Wavefront requires the following dependencies: - -* `io.micrometer:micrometer-tracing-bridge-otel` - bridges the Micrometer Observation API to OpenTelemetry. -* `io.micrometer:micrometer-tracing-reporter-wavefront` - reports traces to Wavefront. - -Use the `management.wavefront.*` configuration properties to configure reporting to Wavefront. - - - -[[actuator.micrometer-tracing.tracer-implementations.otel-otlp]] -==== OpenTelemetry With OTLP -Tracing with OpenTelemetry and reporting using OTLP requires the following dependencies: - -* `io.micrometer:micrometer-tracing-bridge-otel` - bridges the Micrometer Observation API to OpenTelemetry. -* `io.opentelemetry:opentelemetry-exporter-otlp` - reports traces to a collector that can accept OTLP. - -Use the `management.otlp.tracing.*` configuration properties to configure reporting using OTLP. - - - -[[actuator.micrometer-tracing.tracer-implementations.brave-zipkin]] -==== OpenZipkin Brave With Zipkin -Tracing with OpenZipkin Brave and reporting to Zipkin requires the following dependencies: - -* `io.micrometer:micrometer-tracing-bridge-brave` - bridges the Micrometer Observation API to Brave. -* `io.zipkin.reporter2:zipkin-reporter-brave` - reports traces to Zipkin. - -NOTE: If your project doesn't use Spring MVC or Spring WebFlux, the `io.zipkin.reporter2:zipkin-sender-urlconnection` dependency is needed, too. - -Use the `management.zipkin.tracing.*` configuration properties to configure reporting to Zipkin. - - - -[[actuator.micrometer-tracing.tracer-implementations.brave-wavefront]] -==== OpenZipkin Brave With Wavefront -Tracing with OpenZipkin Brave and reporting to Wavefront requires the following dependencies: - -* `io.micrometer:micrometer-tracing-bridge-brave` - bridges the Micrometer Observation API to Brave. -* `io.micrometer:micrometer-tracing-reporter-wavefront` - reports traces to Wavefront. - -Use the `management.wavefront.*` configuration properties to configure reporting to Wavefront. - - - -[[actuator.micrometer-tracing.micrometer-observation]] -=== Integration with Micrometer Observation - -A `TracingAwareMeterObservationHandler` is automatically registered on the `ObservationRegistry`, which creates spans for every completed observation. - -[[actuator.micrometer-tracing.creating-spans]] -=== Creating Custom Spans -You can create your own spans by starting an observation. -For this, inject `ObservationRegistry` into your component: - -include::code:CustomObservation[] - -This will create an observation named "some-operation" with the tag "some-tag=some-value". - -TIP: If you want to create a span without creating a metric, you need to use the https://micrometer.io/docs/tracing#_using_micrometer_tracing_directly[lower-level `Tracer` API] from Micrometer. - - - -[[actuator.micrometer-tracing.baggage]] -=== Baggage -You can create baggage with the `Tracer` API: - -include::code:CreatingBaggage[] - -This example creates baggage named `baggage1` with the value `value1`. -The baggage is automatically propagated over the network if you're using W3C propagation. -If you're using B3 propagation, baggage is not automatically propagated. -To manually propagate baggage over the network, use the configprop:management.tracing.baggage.remote-fields[] configuration property (this works for W3C, too). -For the example above, setting this property to `baggage1` results in an HTTP header `baggage1: value1`. - -If you want to propagate the baggage to the MDC, use the configprop:management.tracing.baggage.correlation.fields[] configuration property. -For the example above, setting this property to `baggage1` results in an MDC entry named `baggage1`. - - - -[[actuator.micrometer-tracing.tests]] -=== Tests - -Tracing is not auto-configured when using `@SpringBootTest`. -See <> for more details. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/whats-next.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/whats-next.adoc deleted file mode 100644 index 42e0ed3cbd6b..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/whats-next.adoc +++ /dev/null @@ -1,5 +0,0 @@ -[[actuator.whats-next]] -== What to Read Next -You might want to read about graphing tools such as https://graphiteapp.org[Graphite]. - -Otherwise, you can continue on to read about <> or jump ahead for some in-depth information about Spring Boot's <>. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/anchor-rewrite.properties b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/anchor-rewrite.properties deleted file mode 100644 index 1b940ae7f852..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/anchor-rewrite.properties +++ /dev/null @@ -1,1046 +0,0 @@ -# Spring Boot 2.x - 2.4 Migrations -#--------------------------------------------------- -spring-boot-reference-documentation=index -legal=legal -boot-documentation=documentation -boot-documentation-about=documentation.about -boot-documentation-getting-help=documentation.getting-help -boot-documentation-upgrading=documentation.upgrading -boot-documentation-first-steps=documentation.first-steps -boot-documentation-workingwith=documentation.using -boot-documentation-learning=documentation.features -boot-documentation-production=documentation.actuator -boot-documentation-advanced=documentation.advanced -getting-started=getting-started -getting-started-introducing-spring-boot=getting-started.introducing-spring-boot -getting-started-system-requirements=getting-started.system-requirements -getting-started-system-requirements-servlet-containers=getting-started.system-requirements.servlet-containers -getting-started-installing-spring-boot=getting-started.installing -getting-started-installation-instructions-for-java=getting-started.installing.java -getting-started-maven-installation=getting-started.installing.java.maven -getting-started-gradle-installation=getting-started.installing.java.gradle -getting-started-installing-the-cli=getting-started.installing.cli -getting-started-manual-cli-installation=getting-started.installing.cli.manual-installation -getting-started-sdkman-cli-installation=getting-started.installing.cli.sdkman -getting-started-homebrew-cli-installation=getting-started.installing.cli.homebrew -getting-started-macports-cli-installation=getting-started.installing.cli.macports -getting-started-cli-command-line-completion=getting-started.installing.cli.completion -getting-started-scoop-cli-installation=getting-started.installing.cli.scoop -getting-started-cli-example=getting-started.installing.cli.quick-start -getting-started-upgrading-from-an-earlier-version=getting-started.installing.upgrading -getting-started-first-application=getting-started.first-application -getting-started-first-application-pom=getting-started.first-application.pom -getting-started-first-application-dependencies=getting-started.first-application.dependencies -getting-started-first-application-code=getting-started.first-application.code -getting-started-first-application-annotations=getting-started.first-application.code.mvc-annotations -getting-started-first-application-auto-configuration=getting-started.first-application.code.enable-auto-configuration -getting-started-first-application-main-method=getting-started.first-application.code.main-method -getting-started-first-application-run=getting-started.first-application.run -getting-started-first-application-executable-jar=getting-started.first-application.executable-jar -getting-started-whats-next=getting-started.whats-next -using-boot=using -using-boot-build-systems=using.build-systems -using-boot-dependency-management=using.build-systems.dependency-management -using-boot-maven=using.build-systems.maven -using-boot-gradle=using.build-systems.gradle -using-boot-ant=using.build-systems.ant -using-boot-starter=using.build-systems.starters -using-boot-structuring-your-code=using.structuring-your-code -using-boot-using-the-default-package=using.structuring-your-code.using-the-default-package -using-boot-locating-the-main-class=using.structuring-your-code.locating-the-main-class -using-boot-configuration-classes=using.configuration-classes -using-boot-importing-configuration=using.configuration-classes.importing-additional-configuration -using-boot-importing-xml-configuration=using.configuration-classes.importing-xml-configuration -using-boot-auto-configuration=using.auto-configuration -using-boot-replacing-auto-configuration=using.auto-configuration.replacing -using-boot-disabling-specific-auto-configuration=using.auto-configuration.disabling-specific -using-boot-spring-beans-and-dependency-injection=using.spring-beans-and-dependency-injection -using-boot-using-springbootapplication-annotation=using.using-the-springbootapplication-annotation -using-boot-running-your-application=using.running-your-application -using-boot-running-from-an-ide=using.running-your-application.from-an-ide -using-boot-running-as-a-packaged-application=using.running-your-application.as-a-packaged-application -using-boot-running-with-the-maven-plugin=using.running-your-application.with-the-maven-plugin -using-boot-running-with-the-gradle-plugin=using.running-your-application.with-the-gradle-plugin -using-boot-hot-swapping=using.running-your-application.hot-swapping -using-boot-devtools=using.devtools -using-boot-devtools-property-defaults=using.devtools.property-defaults -using-boot-devtools-restart=using.devtools.restart -using-boot-devtools-restart-logging-condition-delta=using.devtools.restart.logging-condition-delta -using-boot-devtools-restart-exclude=using.devtools.restart.excluding-resources -using-boot-devtools-restart-additional-paths=using.devtools.restart.watching-additional-paths -using-boot-devtools-restart-disable=using.devtools.restart.disable -using-boot-devtools-restart-triggerfile=using.devtools.restart.triggerfile -using-boot-devtools-customizing-classload=using.devtools.restart.customizing-the-classload -using-boot-devtools-known-restart-limitations=using.devtools.restart.limitations -using-boot-devtools-livereload=using.devtools.livereload -using-boot-devtools-globalsettings=using.devtools.globalsettings -using-spring-boot-restart-vs-reload=using.devtools.restart.restart-vs-reload - -configuring-file-system-watcher=using.devtools.globalsettings.configuring-file-system-watcher -using-boot-devtools-remote=using.devtools.remote-applications -running-remote-client-application=using.devtools.remote-applications.client -using-boot-devtools-remote-update=using.devtools.remote-applications.update -using-boot-packaging-for-production=using.packaging-for-production -using-boot-whats-next=using.whats-next -boot-features=features -boot-features-spring-application=features.spring-application -boot-features-startup-failure=features.spring-application.startup-failure -boot-features-lazy-initialization=features.spring-application.lazy-initialization -boot-features-banner=features.spring-application.banner -boot-features-customizing-spring-application=features.spring-application.customizing-spring-application -boot-features-fluent-builder-api=features.spring-application.fluent-builder-api -boot-features-application-availability=features.spring-application.application-availability -boot-features-application-availability-liveness-state=features.spring-application.application-availability.liveness -boot-features-application-availability-readiness-state=features.spring-application.application-availability.readiness -boot-features-application-availability-managing=features.spring-application.application-availability.managing -boot-features-application-events-and-listeners=features.spring-application.application-events-and-listeners -boot-features-web-environment=features.spring-application.web-environment -boot-features-application-arguments=features.spring-application.application-arguments -boot-features-command-line-runner=features.spring-application.command-line-runner -boot-features-application-exit=features.spring-application.application-exit -boot-features-application-admin=features.spring-application.admin -boot-features-application-startup-tracking=features.spring-application.startup-tracking -boot-features-external-config=features.external-config -boot-features-external-config-command-line-args=features.external-config.command-line-args -boot-features-external-config-application-json=features.external-config.application-json -boot-features-external-config-files=features.external-config.files -boot-features-external-config-application-property-files=features.external-config.files -boot-features-external-config-optional-prefix=features.external-config.files.optional-prefix -boot-features-external-config-files-wildcards=features.external-config.files.wildcard-locations -boot-features-external-config-files-profile-specific=features.external-config.files.profile-specific -boot-features-external-config-files-importing=features.external-config.files.importing -boot-features-external-config-files-importing-extensionless=features.external-config.file.importing-extensionless -boot-features-external-config-files-configtree=features.external-config.files.configtree -boot-features-external-config-placeholders-in-properties=features.external-config.files.property-placeholders -boot-features-external-config-files-multi-document=features.external-config.files.multi-document -boot-features-external-config-file-activation-properties=features.external-config.files.activation-properties -boot-features-encrypting-properties=features.external-config.encrypting -boot-features-external-config-yaml=features.external-config.yaml -boot-features-external-config-yaml-to-properties=features.external-config.yaml.mapping-to-properties -boot-features-external-config-exposing-yaml-to-spring=features.external-config.yaml.directly-loading -boot-features-external-config-loading-yaml=features.external-config.yaml.directly-loading -boot-features-external-config-random-values=features.external-config.random-values -boot-features-external-config-system-environment=features.external-config.system-environment -boot-features-external-config-typesafe-configuration-properties=features.external-config.typesafe-configuration-properties -boot-features-external-config-java-bean-binding=features.external-config.typesafe-configuration-properties.java-bean-binding -boot-features-external-config-constructor-binding=features.external-config.typesafe-configuration-properties.constructor-binding -boot-features-external-config-enabling=features.external-config.typesafe-configuration-properties.enabling-annotated-types -boot-features-external-config-using=features.external-config.typesafe-configuration-properties.using-annotated-types -boot-features-external-config-3rd-party-configuration=features.external-config.typesafe-configuration-properties.third-party-configuration -boot-features-external-config-relaxed-binding=features.external-config.typesafe-configuration-properties.relaxed-binding -boot-features-external-config-relaxed-binding-maps=features.external-config.typesafe-configuration-properties.relaxed-binding.maps -boot-features-external-config-relaxed-binding-from-environment-variables=features.external-config.typesafe-configuration-properties.relaxed-binding.environment-variables -boot-features-external-config-complex-type-merge=features.external-config.typesafe-configuration-properties.merging-complex-types -boot-features-external-config-conversion=features.external-config.typesafe-configuration-properties.conversion -boot-features-external-config-conversion-duration=features.external-config.typesafe-configuration-properties.conversion.durations -boot-features-external-config-conversion-period=features.external-config.typesafe-configuration-properties.conversion.periods -boot-features-external-config-conversion-datasize=features.external-config.typesafe-configuration-properties.conversion.data-sizes -boot-features-external-config-validation=features.external-config.typesafe-configuration-properties.validation -boot-features-external-config-vs-value=features.external-config.typesafe-configuration-properties.vs-value-annotation -boot-features-profiles=features.profiles -boot-features-adding-active-profiles=features.profiles.adding-active-profiles -boot-features-profiles-groups=features.profiles.groups -boot-features-programmatically-setting-profiles=features.profiles.programmatically-setting-profiles -boot-features-profile-specific-configuration=features.profiles.profile-specific-configuration-files -boot-features-logging=features.logging -boot-features-logging-format=features.logging.log-format -boot-features-logging-console-output=features.logging.console-output -boot-features-logging-color-coded-output=features.logging.console-output.color-coded -boot-features-logging-file-output=features.logging.file-output -boot-features-logging-file-rotation=features.logging.file-rotation -boot-features-custom-log-levels=features.logging.log-levels -boot-features-custom-log-groups=features.logging.log-groups -boot-features-log-shutdown-hook=features.logging.shutdown-hook -boot-features-custom-log-configuration=features.logging.custom-log-configuration -boot-features-logback-extensions=features.logging.logback-extensions -boot-features-logback-extensions-profile-specific=features.logging.logback-extensions.profile-specific -boot-features-logback-environment-properties=features.logging.logback-extensions.environment-properties -boot-features-internationalization=features.internationalization -boot-features-json=features.json -boot-features-json-jackson=features.json.jackson -boot-features-json-gson=features.json.gson -boot-features-json-json-b=features.json.json-b -boot-features-developing-web-applications=features.developing-web-applications -boot-features-spring-mvc=features.developing-web-applications.spring-mvc -boot-features-spring-mvc-auto-configuration=features.developing-web-applications.spring-mvc.auto-configuration -boot-features-spring-mvc-message-converters=features.developing-web-applications.spring-mvc.message-converters -boot-features-json-components=features.developing-web-applications.spring-mvc.json -boot-features-spring-message-codes=features.developing-web-applications.spring-mvc.message-codes -boot-features-spring-mvc-static-content=features.developing-web-applications.spring-mvc.static-content -boot-features-spring-mvc-welcome-page=features.developing-web-applications.spring-mvc.welcome-page -boot-features-spring-mvc-pathmatch=features.developing-web-applications.spring-mvc.content-negotiation -boot-features-spring-mvc-web-binding-initializer=features.developing-web-applications.spring-mvc.binding-initializer -boot-features-spring-mvc-template-engines=features.developing-web-applications.spring-mvc.template-engines -boot-features-error-handling=features.developing-web-applications.spring-mvc.error-handling -boot-features-error-handling-custom-error-pages=features.developing-web-applications.spring-mvc.error-handling.error-pages -boot-features-error-handling-mapping-error-pages-without-mvc=features.developing-web-applications.spring-mvc.error-handling.error-pages-without-spring-mvc -boot-features-error-handling-war-deployment=features.developing-web-applications.spring-mvc.error-handling.in-a-war-deployment -boot-features-spring-hateoas=features.spring-hateoas -boot-features-cors=features.developing-web-applications.spring-mvc.cors -boot-features-webflux=features.developing-web-applications.spring-webflux -boot-features-webflux-auto-configuration=features.developing-web-applications.spring-webflux.auto-configuration -boot-features-webflux-httpcodecs=features.developing-web-applications.spring-webflux.httpcodecs -boot-features-webflux-static-content=features.developing-web-applications.spring-webflux.static-content -boot-features-webflux-welcome-page=features.developing-web-applications.spring-webflux.welcome-page -boot-features-webflux-template-engines=features.developing-web-applications.spring-webflux.template-engines -boot-features-webflux-error-handling=features.developing-web-applications.spring-webflux.error-handling -boot-features-webflux-error-handling-custom-error-pages=features.developing-web-applications.spring-webflux.error-handling.error-pages -boot-features-webflux-web-filters=features.developing-web-applications.spring-webflux.web-filters -boot-features-jersey=features.developing-web-applications.jersey -boot-features-embedded-container=features.developing-web-applications.embedded-container -boot-features-embedded-container-servlets-filters-listeners=features.developing-web-applications.embedded-container.servlets-filters-listeners -boot-features-embedded-container-servlets-filters-listeners-beans=features.developing-web-applications.embedded-container.servlets-filters-listeners.beans -boot-features-embedded-container-context-initializer=features.developing-web-applications.embedded-container.context-initializer -boot-features-embedded-container-servlets-filters-listeners-scanning=features.developing-web-applications.embedded-container.context-initializer.scanning -boot-features-embedded-container-application-context=features.developing-web-applications.embedded-container.application-context -boot-features-customizing-embedded-containers=features.developing-web-applications.embedded-container.customizing -boot-features-programmatic-embedded-container-customization=features.developing-web-applications.embedded-container.customizing.programmatic -boot-features-customizing-configurableservletwebserverfactory-directly=features.developing-web-applications.embedded-container.customizing.direct -boot-features-jsp-limitations=features.developing-web-applications.embedded-container.jsp-limitations -boot-features-reactive-server=features.developing-web-applications.reactive-server -boot-features-reactive-server-resources=features.developing-web-applications.reactive-server-resources-configuration -boot-features-graceful-shutdown=features.graceful-shutdown -boot-features-rsocket=features.rsocket -boot-features-rsocket-strategies-auto-configuration=features.rsocket.strategies-auto-configuration -boot-features-rsocket-server-auto-configuration=features.rsocket.server-auto-configuration -boot-features-rsocket-messaging=features.rsocket.messaging -boot-features-rsocket-requester=features.rsocket.requester -boot-features-security=features.security -boot-features-security-mvc=features.security.spring-mvc -boot-features-security-webflux=features.security.spring-webflux -boot-features-security-oauth2=features.security.oauth2 -boot-features-security-oauth2-client=features.security.oauth2.client -boot-features-security-oauth2-common-providers=features.security.oauth2.client.common-providers -boot-features-security-oauth2-server=features.security.oauth2.server -boot-features-security-authorization-server=features.security.oauth2.authorization-server -boot-features-security-saml=features.security.saml2 -boot-features-security-saml2-relying-party=features.security.saml2.relying-party -boot-features-security-actuator=features.security.actuator -boot-features-security-csrf=features.security.actuator.csrf -boot-features-sql=features.sql -boot-features-configure-datasource=features.sql.datasource -boot-features-embedded-database-support=features.sql.datasource.embedded -boot-features-connect-to-production-database=features.sql.datasource.production -boot-features-connect-to-production-database-configuration=features.sql.datasource.configuration -boot-features-connect-to-production-database-connection-pool=features.sql.datasource.connection-pool -boot-features-connecting-to-a-jndi-datasource=features.sql.datasource.jndi -boot-features-using-jdbc-template=features.sql.jdbc-template -boot-features-jpa-and-spring-data=features.sql.jpa-and-spring-data -boot-features-entity-classes=features.sql.jpa-and-spring-data.entity-classes -boot-features-spring-data-jpa-repositories=features.sql.jpa-and-spring-data.repositories -boot-features-creating-and-dropping-jpa-databases=features.sql.jpa-and-spring-data.creating-and-dropping -boot-features-jpa-in-web-environment=features.sql.jpa-and-spring-data.open-entity-manager-in-view -boot-features-data-jdbc=features.sql.jdbc -boot-features-sql-h2-console=features.sql.h2-web-console -boot-features-sql-h2-console-custom-path=features.sql.h2-web-console.custom-path -boot-features-jooq=features.sql.jooq -boot-features-jooq-codegen=features.sql.jooq.codegen -boot-features-jooq-dslcontext=features.sql.jooq.dslcontext -boot-features-jooq-sqldialect=features.sql.jooq.sqldialect -boot-features-jooq-customizing=features.sql.jooq.customizing -boot-features-r2dbc=features.sql.r2dbc -boot-features-r2dbc-embedded-database=features.sql.r2dbc.embedded -boot-features-r2dbc-using-database-client=features.sql.r2dbc.using-database-client -boot-features-spring-data-r2dbc-repositories=features.sql.r2dbc.repositories -boot-features-nosql=features.nosql -boot-features-redis=features.nosql.redis -boot-features-connecting-to-redis=features.nosql.redis.connecting -boot-features-mongodb=features.nosql.mongodb -boot-features-connecting-to-mongodb=features.nosql.mongodb.connecting -boot-features-mongo-template=features.nosql.mongodb.template -boot-features-spring-data-mongodb-repositories=features.nosql.mongodb.repositories -boot-features-spring-data-mongo-repositories=features.nosql.mongodb.repositories -boot-features-neo4j=features.nosql.neo4j -boot-features-connecting-to-neo4j=features.nosql.neo4j.connecting -boot-features-spring-data-neo4j-repositories=features.nosql.neo4j.repositories -boot-features-elasticsearch=features.nosql.elasticsearch -boot-features-connecting-to-elasticsearch-rest=features.nosql.elasticsearch.connecting-using-rest -boot-features-connecting-to-elasticsearch-reactive-rest=features.nosql.elasticsearch.connecting-using-reactive-rest -boot-features-connecting-to-elasticsearch-spring-data=features.nosql.elasticsearch.connecting-using-spring-data -boot-features-spring-data-elasticsearch-repositories=features.nosql.elasticsearch.repositories -boot-features-cassandra=features.nosql.cassandra -boot-features-connecting-to-cassandra=features.nosql.cassandra.connecting -boot-features-spring-data-cassandra-repositories=features.nosql.cassandra.repositories -boot-features-couchbase=features.nosql.couchbase -boot-features-connecting-to-couchbase=features.nosql.couchbase.connecting -boot-features-spring-data-couchbase-repositories=features.nosql.couchbase.repositories -boot-features-ldap=features.nosql.ldap -boot-features-ldap-connecting=features.nosql.ldap.connecting -boot-features-ldap-spring-data-repositories=features.nosql.ldap.repositories -boot-features-ldap-embedded=features.nosql.ldap.embedded -boot-features-influxdb=features.nosql.influxdb -boot-features-connecting-to-influxdb=features.nosql.influxdb.connecting -boot-features-caching=features.caching -boot-features-caching-provider=features.caching.provider -boot-features-caching-provider-generic=features.caching.provider.generic -boot-features-caching-provider-jcache=features.caching.provider.jcache -boot-features-caching-provider-hazelcast=features.caching.provider.hazelcast -boot-features-caching-provider-infinispan=features.caching.provider.infinispan -boot-features-caching-provider-couchbase=features.caching.provider.couchbase -boot-features-caching-provider-redis=features.caching.provider.redis -boot-features-caching-provider-caffeine=features.caching.provider.caffeine -boot-features-caching-provider-simple=features.caching.provider.simple -boot-features-caching-provider-none=features.caching.provider.none -boot-features-messaging=features.messaging -boot-features-jms=features.messaging.jms -boot-features-activemq=features.messaging.jms.activemq -boot-features-artemis=features.messaging.jms.artemis -boot-features-jms-jndi=features.messaging.jms.jndi -boot-features-using-jms-sending=features.messaging.jms.sending -boot-features-using-jms-receiving=features.messaging.jms.receiving -boot-features-amqp=features.messaging.amqp -boot-features-rabbitmq=features.messaging.amqp.rabbitmq -boot-features-using-amqp-sending=features.messaging.amqp.sending -boot-features-using-amqp-receiving=features.messaging.amqp.receiving -boot-features-kafka=features.messaging.kafka -boot-features-kafka-sending-a-message=features.messaging.kafka.sending -boot-features-kafka-receiving-a-message=features.messaging.kafka.receiving -boot-features-kafka-streams=features.messaging.kafka.streams -boot-features-kafka-extra-props=features.messaging.kafka.additional-properties -boot-features-embedded-kafka=features.messaging.kafka.embedded -boot-features-resttemplate=features.resttemplate -boot-features-resttemplate-customization=features.resttemplate.customization -boot-features-webclient=features.webclient -boot-features-webclient-runtime=features.webclient.runtime -boot-features-webclient-customization=features.webclient.customization -boot-features-validation=features.validation -boot-features-email=features.email -boot-features-jta=features.jta -boot-features-jta-atomikos=features.jta.atomikos -boot-features-jta-javaee=features.jta.javaee -boot-features-jta-mixed-jms=features.jta.mixing-xa-and-non-xa-connections -boot-features-jta-supporting-alternative-embedded=features.jta.supporting-alternative-embedded-transaction-manager -boot-features-hazelcast=features.hazelcast -boot-features-quartz=features.quartz -boot-features-task-execution-scheduling=features.task-execution-and-scheduling -boot-features-integration=features.spring-integration -boot-features-session=features.spring-session -boot-features-jmx=features.jmx -boot-features-testing=features.testing -boot-features-test-scope-dependencies=features.testing.test-scope-dependencies -boot-features-testing-spring-applications=features.testing.spring-applications -boot-features-testing-spring-boot-applications=features.testing.spring-boot-applications -boot-features-testing-spring-boot-applications-detecting-web-app-type=features.testing.spring-boot-applications.detecting-web-app-type -boot-features-testing-spring-boot-applications-detecting-config=features.testing.spring-boot-applications.detecting-configuration -boot-features-testing-spring-boot-applications-excluding-config=features.testing.spring-boot-applications.excluding-configuration -boot-features-testing-spring-boot-application-arguments=features.testing.spring-boot-applications.using-application-arguments -boot-features-testing-spring-boot-applications-testing-with-mock-environment=features.testing.spring-boot-applications.with-mock-environment -boot-features-testing-spring-boot-applications-testing-with-running-server=features.testing.spring-boot-applications.with-running-server -boot-features-testing-spring-boot-applications-customizing-web-test-client=features.testing.spring-boot-applications.customizing-web-test-client -boot-features-testing-spring-boot-applications-jmx=features.testing.spring-boot-applications.jmx -boot-features-testing-spring-boot-applications-metrics=features.testing.spring-boot-applications.metrics -boot-features-testing-spring-boot-applications-mocking-beans=features.testing.spring-boot-applications.mocking-beans -boot-features-testing-spring-boot-applications-testing-autoconfigured-tests=features.testing.spring-boot-applications.autoconfigured-tests -boot-features-testing-spring-boot-applications-testing-autoconfigured-json-tests=features.testing.spring-boot-applications.json-tests -boot-features-testing-spring-boot-applications-testing-autoconfigured-mvc-tests=features.testing.spring-boot-applications.spring-mvc-tests -boot-features-testing-spring-boot-applications-testing-autoconfigured-webflux-tests=features.testing.spring-boot-applications.spring-webflux-tests -boot-features-testing-spring-boot-applications-testing-autoconfigured-cassandra-test=features.testing.spring-boot-applications.autoconfigured-spring-data-cassandra -boot-features-testing-spring-boot-applications-testing-autoconfigured-jpa-test=features.testing.spring-boot-applications.autoconfigured-spring-data-jpa -boot-features-testing-spring-boot-applications-testing-autoconfigured-jdbc-test=features.testing.spring-boot-applications.autoconfigured-jdbc -boot-features-testing-spring-boot-applications-testing-autoconfigured-data-jdbc-test=features.testing.spring-boot-applications.autoconfigured-spring-data-jdbc -boot-features-testing-spring-boot-applications-testing-autoconfigured-jooq-test=features.testing.spring-boot-applications.autoconfigured-jooq -boot-features-testing-spring-boot-applications-testing-autoconfigured-mongo-test=features.testing.spring-boot-applications.autoconfigured-spring-data-mongodb -boot-features-testing-spring-boot-applications-testing-autoconfigured-neo4j-test=features.testing.spring-boot-applications.autoconfigured-spring-data-neo4j -boot-features-testing-spring-boot-applications-testing-autoconfigured-redis-test=features.testing.spring-boot-applications.autoconfigured-spring-data-redis -boot-features-testing-spring-boot-applications-testing-autoconfigured-ldap-test=features.testing.spring-boot-applications.autoconfigured-spring-data-ldap -boot-features-testing-spring-boot-applications-testing-autoconfigured-rest-client=features.testing.spring-boot-applications.autoconfigured-rest-client -boot-features-testing-spring-boot-applications-testing-autoconfigured-rest-docs=features.testing.spring-boot-applications.autoconfigured-spring-restdocs -boot-features-testing-spring-boot-applications-testing-autoconfigured-rest-docs-mock-mvc=features.testing.spring-boot-applications.autoconfigured-spring-restdocs.with-mock-mvc -boot-features-testing-spring-boot-applications-testing-autoconfigured-rest-docs-web-test-client=features.testing.spring-boot-applications.autoconfigured-spring-restdocs.with-web-test-client -boot-features-testing-spring-boot-applications-testing-autoconfigured-rest-docs-rest-assured=features.testing.spring-boot-applications.autoconfigured-spring-restdocs.with-rest-assured -boot-features-testing-spring-boot-applications-testing-autoconfigured-webservices=features.testing.spring-boot-applications.autoconfigured-webservices -boot-features-testing-spring-boot-applications-testing-auto-configured-additional-auto-config=features.testing.spring-boot-applications.additional-autoconfiguration-and-slicing -boot-features-testing-spring-boot-applications-testing-user-configuration=features.testing.spring-boot-applications.user-configuration-and-slicing -boot-features-testing-spring-boot-applications-with-spock=features.testing.spring-boot-applications.spock -boot-features-test-utilities=features.testing.utilities -boot-features-configfileapplicationcontextinitializer-test-utility=features.testing.utilities.config-data-application-context-initializer -boot-features-test-property-values=features.testing.utilities.test-property-values -boot-features-output-capture-test-utility=features.testing.utilities.output-capture -boot-features-rest-templates-test-utility=features.testing.utilities.test-rest-template -boot-features-websockets=features.websockets -boot-features-webservices=features.webservices -boot-features-webservices-template=features.webservices.template -boot-features-developing-auto-configuration=features.developing-auto-configuration -boot-features-understanding-auto-configured-beans=features.developing-auto-configuration.understanding-auto-configured-beans -boot-features-locating-auto-configuration-candidates=features.developing-auto-configuration.locating-auto-configuration-candidates -boot-features-condition-annotations=features.developing-auto-configuration.condition-annotations -boot-features-class-conditions=features.developing-auto-configuration.condition-annotations.class-conditions -boot-features-bean-conditions=features.developing-auto-configuration.condition-annotations.bean-conditions -boot-features-property-conditions=features.developing-auto-configuration.condition-annotations.property-conditions -boot-features-resource-conditions=features.developing-auto-configuration.condition-annotations.resource-conditions -boot-features-web-application-conditions=features.developing-auto-configuration.condition-annotations.web-application-conditions -boot-features-spel-conditions=features.developing-auto-configuration.condition-annotations.spel-conditions -boot-features-test-autoconfig=features.developing-auto-configuration.testing -boot-features-test-autoconfig-simulating-web-context=features.developing-auto-configuration.testing.simulating-a-web-context -boot-features-test-autoconfig-overriding-classpath=features.developing-auto-configuration.testing.overriding-classpath -boot-features-custom-starter=features.developing-auto-configuration.custom-starter -boot-features-custom-starter-naming=features.developing-auto-configuration.custom-starter.naming -boot-features-custom-starter-configuration-keys=features.developing-auto-configuration.custom-starter.configuration-keys -boot-features-custom-starter-module-autoconfigure=features.developing-auto-configuration.custom-starter.autoconfigure-module -boot-features-custom-starter-module-starter=features.developing-auto-configuration.custom-starter.starter-module -boot-features-kotlin=features.kotlin -boot-features-kotlin-requirements=features.kotlin.requirements -boot-features-kotlin-null-safety=features.kotlin.null-safety -boot-features-kotlin-api=features.kotlin.api -boot-features-kotlin-api-runapplication=features.kotlin.api.run-application -boot-features-kotlin-api-extensions=features.kotlin.api.extensions -boot-features-kotlin-dependency-management=features.kotlin.dependency-management -boot-features-kotlin-configuration-properties=features.kotlin.configuration-properties -boot-features-kotlin-testing=features.kotlin.testing -boot-features-kotlin-resources=features.kotlin.resources -boot-features-kotlin-resources-further-reading=features.kotlin.resources.further-reading -boot-features-kotlin-resources-examples=features.kotlin.resources.examples -boot-features-container-images=features.container-images -boot-layering-docker-images=features.container-images.layering -boot-features-container-images-building=features.container-images.building -boot-features-container-images-docker=features.container-images.building.dockerfiles -boot-features-container-images-buildpacks=features.container-images.building.buildpacks -boot-features-whats-next=features.whats-next -production-ready=actuator -production-ready-enabling=actuator.enabling -production-ready-endpoints=actuator.endpoints -production-ready-endpoints-enabling-endpoints=actuator.endpoints.enabling -production-ready-endpoints-exposing-endpoints=actuator.endpoints.exposing -production-ready-endpoints-security=actuator.endpoints.security -production-ready-endpoints-caching=actuator.endpoints.caching -production-ready-endpoints-hypermedia=actuator.endpoints.hypermedia -production-ready-endpoints-cors=actuator.endpoints.cors -production-ready-endpoints-custom=actuator.endpoints.implementing-custom -production-ready-endpoints-custom-input=actuator.endpoints.implementing-custom.input -production-ready-endpoints-custom-input-conversion=actuator.endpoints.implementing-custom.input.conversion -production-ready-endpoints-custom-web=actuator.endpoints.implementing-custom.web -production-ready-endpoints-custom-web-predicate=actuator.endpoints.implementing-custom.web.request-predicates -production-ready-endpoints-custom-web-predicate-path=actuator.endpoints.implementing-custom.web.path-predicates -production-ready-endpoints-custom-web-predicate-http-method=actuator.endpoints.implementing-custom.web.method-predicates -production-ready-endpoints-custom-web-predicate-consumes=actuator.endpoints.implementing-custom.web.consumes-predicates -production-ready-endpoints-custom-web-predicate-produces=actuator.endpoints.implementing-custom.web.produces-predicates -production-ready-endpoints-custom-web-response-status=actuator.endpoints.implementing-custom.web.response-status -production-ready-endpoints-custom-web-range-requests=actuator.endpoints.implementing-custom.web.range-requests -production-ready-endpoints-custom-web-security=actuator.endpoints.implementing-custom.web.security -production-ready-endpoints-custom-servlet=actuator.endpoints.implementing-custom.servlet -production-ready-endpoints-custom-controller=actuator.endpoints.implementing-custom.controller -production-ready-health=actuator.endpoints.health -production-ready-health-indicators=actuator.endpoints.health.auto-configured-health-indicators -production-ready-health-indicators-writing=actuator.endpoints.health.writing-custom-health-indicators -reactive-health-indicators=actuator.endpoints.health.reactive-health-indicators -reactive-health-indicators-autoconfigured=actuator.endpoints.health.auto-configured-reactive-health-indicators -production-ready-health-groups=actuator.endpoints.health.groups -production-ready-health-datasource=actuator.endpoints.health.datasource -production-ready-kubernetes-probes=actuator.endpoints.kubernetes-probes -production-ready-kubernetes-probes-external-state=actuator.endpoints.kubernetes-probes.external-state -production-ready-kubernetes-probes-lifecycle=actuator.endpoints.kubernetes-probes.lifecycle -production-ready-application-info=actuator.endpoints.info -production-ready-application-info-autoconfigure=actuator.endpoints.info.auto-configured-info-contributors -production-ready-application-info-env=actuator.endpoints.info.custom-application-information -production-ready-application-info-git=actuator.endpoints.info.git-commit-information -production-ready-application-info-build=actuator.endpoints.info.build-information -production-ready-application-info-custom=actuator.endpoints.info.writing-custom-info-contributors -production-ready-monitoring=actuator.monitoring -production-ready-customizing-management-server-context-path=actuator.monitoring.customizing-management-server-context-path -production-ready-customizing-management-server-port=actuator.monitoring.customizing-management-server-port -production-ready-management-specific-ssl=actuator.monitoring.management-specific-ssl -production-ready-customizing-management-server-address=actuator.monitoring.customizing-management-server-address -production-ready-disabling-http-endpoints=actuator.monitoring.disabling-http-endpoints -production-ready-jmx=actuator.jmx -production-ready-custom-mbean-names=actuator.jmx.custom-mbean-names -production-ready-disable-jmx-endpoints=actuator.jmx.disable-jmx-endpoints -production-ready-loggers=actuator.loggers -production-ready-logger-configuration=actuator.loggers.configure -production-ready-metrics=actuator.metrics -production-ready-metrics-getting-started=actuator.metrics.getting-started -production-ready-metrics-export=actuator.metrics.export -production-ready-metrics-export-appoptics=actuator.metrics.export.appoptics -production-ready-metrics-export-atlas=actuator.metrics.export.atlas -production-ready-metrics-export-datadog=actuator.metrics.export.datadog -production-ready-metrics-export-dynatrace=actuator.metrics.export.dynatrace -production-ready-metrics-export-elastic=actuator.metrics.export.elastic -production-ready-metrics-export-ganglia=actuator.metrics.export.ganglia -production-ready-metrics-export-graphite=actuator.metrics.export.graphite -production-ready-metrics-export-humio=actuator.metrics.export.humio -production-ready-metrics-export-influx=actuator.metrics.export.influx -production-ready-metrics-export-jmx=actuator.metrics.export.jmx -production-ready-metrics-export-kairos=actuator.metrics.export.kairos -production-ready-metrics-export-newrelic=actuator.metrics.export.newrelic -production-ready-metrics-export-prometheus=actuator.metrics.export.prometheus -production-ready-metrics-export-signalfx=actuator.metrics.export.signalfx -production-ready-metrics-export-simple=actuator.metrics.export.simple -production-ready-metrics-export-stackdriver=actuator.metrics.export.stackdriver -production-ready-metrics-export-statsd=actuator.metrics.export.statsd -production-ready-metrics-export-wavefront=actuator.metrics.export.wavefront -production-ready-metrics-meter=actuator.metrics.supported -production-ready-metrics-jvm=actuator.metrics.supported.jvm -production-ready-metrics-system=actuator.metrics.supported.system -production-ready-metrics-logger=actuator.metrics.supported.logger -production-ready-metrics-spring-mvc=actuator.metrics.supported.spring-mvc -production-ready-metrics-web-flux=actuator.metrics.supported.spring-webflux -production-ready-metrics-jersey-server=actuator.metrics.supported.jersey -production-ready-metrics-http-clients=actuator.metrics.supported.http-clients -production-ready-metrics-tomcat=actuator.metrics.supported.tomcat -production-ready-metrics-cache=actuator.metrics.supported.cache -production-ready-metrics-jdbc=actuator.metrics.supported.jdbc -production-ready-metrics-hibernate=actuator.metrics.supported.hibernate -production-ready-metrics-data-repository=actuator.metrics.supported.spring-data-repository -production-ready-metrics-rabbitmq=actuator.metrics.supported.rabbitmq -production-ready-metrics-integration=actuator.metrics.supported.spring-integration -production-ready-metrics-kafka=actuator.metrics.supported.kafka -production-ready-metrics-mongodb=actuator.metrics.supported.mongodb -production-ready-metrics-mongodb-command=actuator.metrics.supported.mongodb.command -production-ready-metrics-mongodb-connectionpool=actuator.metrics.supported.mongodb.connection-pool -production-ready-metrics-timed-annotation=actuator.metrics.supported.timed-annotation -production-ready-metrics-custom=actuator.metrics.registering-custom -production-ready-metrics-customizing=actuator.metrics.customizing -production-ready-metrics-common-tags=actuator.metrics.customizing.common-tags -production-ready-metrics-per-meter-properties=actuator.metrics.customizing.per-meter-properties -production-ready-metrics-endpoint=actuator.metrics.endpoint -production-ready-auditing=actuator.auditing -production-ready-auditing-custom=actuator.auditing.custom -production-ready-http-tracing=actuator.tracing -production-ready-http-tracing-custom=actuator.tracing.custom -production-ready-process-monitoring=actuator.process-monitoring -production-ready-process-monitoring-configuration=actuator.process-monitoring.configuration -production-ready-process-monitoring-programmatically=actuator.process-monitoring.programmatically -production-ready-cloudfoundry=actuator.cloud-foundry -production-ready-cloudfoundry-disable=actuator.cloud-foundry.disable -production-ready-cloudfoundry-ssl=actuator.cloud-foundry.ssl -production-ready-custom-context-path=actuator.cloud-foundry.custom-context-path -production-ready-whats-next=actuator.whats-next -deployment=deployment -containers-deployment=deployment.containers -cloud-deployment=deployment.cloud -cloud-deployment-cloud-foundry=deployment.cloud.cloud-foundry -cloud-deployment-cloud-foundry-services=deployment.cloud.cloud-foundry.binding-to-services -cloud-deployment-kubernetes=deployment.cloud.kubernetes -cloud-deployment-kubernetes-container-lifecycle=deployment.cloud.kubernetes.container-lifecycle -cloud-deployment-heroku=deployment.cloud.heroku -cloud-deployment-openshift=deployment.cloud.openshift -cloud-deployment-aws=deployment.cloud.aws -cloud-deployment-aws-beanstalk=deployment.cloud.aws.beanstalk -cloud-deployment-aws-tomcat-platform=deployment.cloud.aws.beanstalk.tomcat-platform -cloud-deployment-aws-java-se-platform=deployment.cloud.aws.beanstalk.java-se-platform -cloud-deployment-aws-summary=deployment.cloud.aws.summary -cloud-deployment-boxfuse=deployment.cloud.boxfuse -cloud-deployment-gae=deployment.cloud.google -deployment-install=deployment.installing -deployment-install-supported-operating-systems=deployment.installing.supported-operating-systems -deployment-service=deployment.installing.nix-services -deployment-initd-service=deployment.installing.nix-services.init-d -deployment-initd-service-securing=deployment.installing.nix-services.init-d.securing -deployment-systemd-service=deployment.installing.nix-services.system-d -deployment-script-customization=deployment.installing.nix-services.script-customization -deployment-script-customization-when-it-written=deployment.installing.nix-services.script-customization.when-written -deployment-script-customization-when-it-runs=deployment.installing.nix-services.script-customization.when-running -deployment-windows=deployment.installing.windows-services -deployment-whats-next=deployment.whats-next -cli=cli -cli-installation=cli.installation -cli-using-the-cli=cli.using-the-cli -cli-run=cli.using-the-cli.run -cli-deduced-grab-annotations=cli.using-the-cli.run.deduced-grab-annotations -cli-default-grab-deduced-coordinates=cli.using-the-cli.run.deduced-grab-coordinates -cli-default-import-statements=cli.using-the-cli.run.default-import-statements -cli-automatic-main-method=cli.using-the-cli.run.automatic-main-method -cli-default-grab-deduced-coordinates-custom-dependency-management=cli.using-the-cli.run.custom-dependency-management -cli-multiple-source-files=cli.using-the-cli.multiple-source-files -cli-jar=cli.using-the-cli.packaging -cli-init=cli.using-the-cli.initialize-new-project -cli-shell=cli.using-the-cli.embedded-shell -cli-install-uninstall=cli.using-the-cli.extensions -cli-groovy-beans-dsl=cli.groovy-beans-dsl -cli-maven-settings=cli.maven-setting -cli-whats-next=cli.whats-next -build-tool-plugins=build-tool-plugins -build-tool-plugins-maven-plugin=build-tool-plugins.maven -build-tool-plugins-gradle-plugin=build-tool-plugins.gradle -build-tool-plugins-antlib=build-tool-plugins.antlib -spring-boot-ant-tasks=build-tool-plugins.antlib.tasks -spring-boot-ant-exejar=build-tool-plugins.antlib.tasks.exejar -spring-boot-ant-exejar-examples=build-tool-plugins.antlib.tasks.examples -spring-boot-ant-findmainclass=build-tool-plugins.antlib.findmainclass -spring-boot-ant-findmainclass-examples=build-tool-plugins.antlib.findmainclass.examples -build-tool-plugins-other-build-systems=build-tool-plugins.other-build-systems -build-tool-plugins-repackaging-archives=build-tool-plugins.other-build-systems.repackaging-archives -build-tool-plugins-nested-libraries=build-tool-plugins.other-build-systems.nested-libraries -build-tool-plugins-find-a-main-class=build-tool-plugins.other-build-systems.finding-main-class -build-tool-plugins-repackage-implementation=build-tool-plugins.other-build-systems.example-repackage-implementation -build-tool-plugins-whats-next=build-tool-plugins.whats-next -howto=howto -howto-spring-boot-application=howto.application -howto-failure-analyzer=howto.application.failure-analyzer -howto-troubleshoot-auto-configuration=howto.application.troubleshoot-auto-configuration -howto-customize-the-environment-or-application-context=howto.application.customize-the-environment-or-application-context -howto-build-an-application-context-hierarchy=howto.application.context-hierarchy -howto-create-a-non-web-application=howto.application.non-web-application -howto-properties-and-configuration=howto.properties-and-configuration -howto-automatic-expansion=howto.properties-and-configuration.expand-properties -howto-automatic-expansion-maven=howto.properties-and-configuration.expand-properties.maven -howto-automatic-expansion-gradle=howto.properties-and-configuration.expand-properties.gradle -howto-externalize-configuration=howto.properties-and-configuration.externalize-configuration -howto-change-the-location-of-external-properties=howto.properties-and-configuration.external-properties-location -howto-use-short-command-line-arguments=howto.properties-and-configuration.short-command-line-arguments -howto-use-yaml-for-external-properties=howto.properties-and-configuration.yaml -howto-set-active-spring-profiles=howto.properties-and-configuration.set-active-spring-profiles -howto-change-configuration-depending-on-the-environment=howto.properties-and-configuration.change-configuration-depending-on-the-environment -howto-discover-build-in-options-for-external-properties=howto.properties-and-configuration.discover-build-in-options-for-external-properties -howto-embedded-web-servers=howto.webserver -howto-use-another-web-server=howto.webserver.use-another -howto-disable-web-server=howto.webserver.disable -howto-change-the-http-port=howto.webserver.change-port -howto-user-a-random-unassigned-http-port=howto.webserver.use-random-port -howto-discover-the-http-port-at-runtime=howto.webserver.discover-port -how-to-enable-http-response-compression=howto.webserver.enable-response-compression -howto-configure-ssl=howto.webserver.configure-ssl -howto-configure-http2=howto.webserver.configure-http2 -howto-configure-http2-tomcat=howto.webserver.configure-http2.tomcat -howto-configure-http2-jetty=howto.webserver.configure-http2.jetty -howto-configure-http2-netty=howto.webserver.configure-http2.netty -howto-configure-http2-undertow=howto.webserver.configure-http2.undertow -howto-configure-webserver=howto.webserver.configure -howto-add-a-servlet-filter-or-listener=howto.webserver.add-servlet-filter-listener -howto-add-a-servlet-filter-or-listener-as-spring-bean=howto.webserver.add-servlet-filter-listener.spring-bean -howto-disable-registration-of-a-servlet-or-filter=howto.webserver.add-servlet-filter-listener.spring-bean.disable -howto-add-a-servlet-filter-or-listener-using-scanning=howto.webserver.add-servlet-filter-listener.using-scanning -howto-configure-accesslogs=howto.webserver.configure-access-logs -howto-use-behind-a-proxy-server=howto.webserver.use-behind-a-proxy-server -howto-customize-tomcat-behind-a-proxy-server=howto.webserver.use-behind-a-proxy-server.tomcat -howto-enable-multiple-connectors-in-tomcat=howto.webserver.enable-multiple-connectors-in-tomcat -howto-use-tomcat-legacycookieprocessor=howto.webserver.use-tomcat-legacycookieprocessor -howto-enable-tomcat-mbean-registry=howto.webserver.enable-tomcat-mbean-registry -howto-enable-multiple-listeners-in-undertow=howto.webserver.enable-multiple-listeners-in-undertow -howto-create-websocket-endpoints-using-serverendpoint=howto.webserver.create-websocket-endpoints-using-serverendpoint -howto-spring-mvc=howto.spring-mvc -howto-write-a-json-rest-service=howto.spring-mvc.write-json-rest-service -howto-write-an-xml-rest-service=howto.spring-mvc.write-xml-rest-service -howto-customize-the-jackson-objectmapper=howto.spring-mvc.customize-jackson-objectmapper -howto-customize-the-responsebody-rendering=howto.spring-mvc.customize-responsebody-rendering -howto-multipart-file-upload-configuration=howto.spring-mvc.multipart-file-uploads -howto-switch-off-the-spring-mvc-dispatcherservlet=howto.spring-mvc.switch-off-dispatcherservlet -howto-switch-off-default-mvc-configuration=howto.spring-mvc.switch-off-default-configuration -howto-customize-view-resolvers=howto.spring-mvc.customize-view-resolvers -howto-use-test-with-spring-security=howto.spring-mvc.testing.with-spring-security -howto-jersey=howto.jersey -howto-jersey-spring-security=howto.jersey.spring-security -howto-jersey-alongside-another-web-framework=howto.jersey.alongside-another-web-framework -howto-http-clients=howto.http-clients -howto-http-clients-proxy-configuration=howto.http-clients.rest-template-proxy-configuration -howto-webclient-reactor-netty-customization=howto.http-clients.webclient-reactor-netty-customization -howto-logging=howto.logging -howto-configure-logback-for-logging=howto.logging.logback -howto-configure-logback-for-logging-fileonly=howto.logging.logback.file-only-output -howto-configure-log4j-for-logging=howto.logging.log4j -howto-configure-log4j-for-logging-yaml-or-json-config=howto.logging.log4j.yaml-or-json-config -howto-data-access=howto.data-access -howto-configure-a-datasource=howto.data-access.configure-custom-datasource -howto-two-datasources=howto.data-access.configure-two-datasources -howto-use-spring-data-repositories=howto.data-access.spring-data-repositories -howto-separate-entity-definitions-from-spring-configuration=howto.data-access.separate-entity-definitions-from-spring-configuration -howto-configure-jpa-properties=howto.data-access.jpa-properties -howto-configure-hibernate-naming-strategy=howto.data-access.configure-hibernate-naming-strategy -howto-configure-hibernate-second-level-caching=howto.data-access.configure-hibernate-second-level-caching -howto-use-dependency-injection-hibernate-components=howto.data-access.dependency-injection-in-hibernate-components -howto-use-custom-entity-manager=howto.data-access.use-custom-entity-manager -howto-use-multiple-entity-managers=howto.data-access.use-multiple-entity-managers -howto-use-two-entity-managers=howto.data-access.use-multiple-entity-managers -howto-use-traditional-persistence-xml=howto.data-access.use-traditional-persistence-xml -howto-use-spring-data-jpa--and-mongo-repositories=howto.data-access.use-spring-data-jpa-and-mongo-repositories -howto-use-customize-spring-datas-web-support=howto.data-access.customize-spring-data-web-support -howto-use-exposing-spring-data-repositories-rest-endpoint=howto.data-access.exposing-spring-data-repositories-as-rest -howto-configure-a-component-that-is-used-by-JPA=howto.data-access.configure-a-component-that-is-used-by-jpa -howto-configure-jOOQ-with-multiple-datasources=howto.data-access.configure-jooq-with-multiple-datasources -howto-database-initialization=howto.data-initialization -howto-initialize-a-database-using-jpa=howto.data-initialization.using-jpa -howto-initialize-a-database-using-hibernate=howto.data-initialization.using-hibernate -howto-initialize-a-database-using-basic-scripts=howto.data-initialization.using-basic-sql-scripts -howto-initialize-a-spring-batch-database=howto.data-initialization.batch -howto-use-a-higher-level-database-migration-tool=howto.data-initialization.migration-tool -howto-execute-flyway-database-migrations-on-startup=howto.data-initialization.migration-tool.flyway -howto-execute-liquibase-database-migrations-on-startup=howto.data-initialization.migration-tool.liquibase -howto-initialize-a-database-configuring-dependencies=howto.data-initialization.dependencies -howto-initialize-a-database-configuring-dependencies-initializer-detection=howto.data-initialization.dependencies.initializer-detection -howto-initialize-a-database-configuring-dependencies-depends-on-initialization-detection=howto.data-initialization.dependencies.depends-on-initialization-detection -howto-messaging=howto.messaging -howto-jms-disable-transaction=howto.messaging.disable-transacted-jms-session -howto-batch-applications=howto.batch -howto-spring-batch-specifying-a-data-source=howto.batch.specifying-a-data-source -howto-spring-batch-running-jobs-on-startup=howto.batch.running-jobs-on-startup -howto-spring-batch-running-command-line=howto.batch.running-from-the-command-line -howto-spring-batch-storing-job-repository=howto.batch.storing-job-repository -howto-actuator=howto.actuator -howto-change-the-http-port-or-address-of-the-actuator-endpoints=howto.actuator.change-http-port-or-address -howto-customize-the-whitelabel-error-page=howto.actuator.customize-whitelabel-error-page -howto-sanitize-sensitive-values=howto.actuator.sanitize-sensitive-values -howto-sanitize-sensible-values=howto.actuator.sanitize-sensitive-values -howto-map-health-indicators-to-metrics=howto.actuator.map-health-indicators-to-metrics -howto-security=howto.security -howto-switch-off-spring-boot-security-configuration=howto.security.switch-off-spring-boot-configuration -howto-change-the-user-details-service-and-add-user-accounts=howto.security.change-user-details-service-and-add-user-accounts -howto-enable-https=howto.security.enable-https -howto-hotswapping=howto.hotswapping -howto-reload-static-content=howto.hotswapping.reload-static-content -howto-reload-thymeleaf-template-content=howto.hotswapping.reload-templates -howto-reload-thymeleaf-content=howto.hotswapping.reload-templates.thymeleaf -howto-reload-freemarker-content=howto.hotswapping.reload-templates.freemarker -howto-reload-groovy-template-content=howto.hotswapping.reload-templates.groovy -howto-reload-fast-restart=howto.hotswapping.fast-application-restarts -howto-reload-java-classes-without-restarting=howto.hotswapping.reload-java-classes-without-restarting -howto-build=howto.build -howto-build-info=howto.build.generate-info -howto-git-info=howto.build.generate-git-info -howto-customize-dependency-versions=howto.build.customize-dependency-versions -howto-create-an-executable-jar-with-maven=howto.build.create-an-executable-jar-with-maven -howto-create-an-additional-executable-jar=howto.build.use-a-spring-boot-application-as-dependency -howto-extract-specific-libraries-when-an-executable-jar-runs=howto.build.extract-specific-libraries-when-an-executable-jar-runs -howto-create-a-nonexecutable-jar=howto.build.create-a-nonexecutable-jar -howto-remote-debug-maven-run=howto.build.remote-debug-maven -howto-build-an-executable-archive-with-ant=howto.build.build-an-executable-archive-with-ant-without-using-spring-boot-antlib -howto-traditional-deployment=howto.traditional-deployment -howto-create-a-deployable-war-file=howto.traditional-deployment.war -howto-convert-an-existing-application-to-spring-boot=howto.traditional-deployment.convert-existing-application -howto-weblogic=howto.traditional-deployment.weblogic -howto-use-jedis-instead-of-lettuce=howto.nosql.jedis-instead-of-lettuce -howto-testcontainers=howto.testing.testcontainers -features.caching=io.caching -features.caching.provider=io.caching.provider -features.caching.provider.generic=io.caching.provider.generic -features.caching.provider.jcache=io.caching.provider.jcache -features.caching.provider.hazelcast=io.caching.provider.hazelcast -features.caching.provider.infinispan=io.caching.provider.infinispan -features.caching.provider.couchbase=io.caching.provider.couchbase -features.caching.provider.redis=io.caching.provider.redis -features.caching.provider.caffeine=io.caching.provider.caffeine -features.caching.provider.simple=io.caching.provider.simple -features.caching.provider.none=io.caching.provider.none -features.jta=io.jta -features.jta.javaee=io.jta.jakartaee -features.jta.mixing-xa-and-non-xa-connections=io.jta.mixing-xa-and-non-xa-connections -features.jta.supporting-alternative-embedded-transaction-manager=io.jta.supporting-embedded-transaction-manager -features.email=io.email -features.quartz=io.quartz -features.resttemplate=io.rest-client.resttemplate -features.resttemplate.customization=io.rest-client.resttemplate.customization -features.webclient=io.rest-client.webclient -features.webclient.runtime=io.rest-client.webclient.runtime -features.webclient.customization=io.rest-client.webclient.customization -features.validation=io.validation -features.webservices=io.webservices -features.webservices.template=io.webservices.template -features.messaging=messaging -features.messaging.amqp=messaging.amqp -features.messaging.amqp.rabbit=messaging.amqp.rabbit -features.messaging.amqp.sending=messaging.amqp.sending -features.messaging.amqp.receiving=messaging.amqp.receiving -features.messaging.jms.activemq=messaging.jms.activemq -features.messaging.jms.artemis=messaging.jms.artemis -features.messaging.jms.jndi=messaging.jms.jndi -features.messaging.jms.sending=messaging.jms.sending -features.messaging.jms.receiving=messaging.jms.receiving -features.messaging.kafka=messaging.kafka -features.messaging.kafka.sending=messaging.kafka.sending -features.messaging.kafka.receiving=messaging.kafka.receiving -features.messaging.kafka.streams=messaging.kafka.streams -features.messaging.kafka.additional-properties=messaging.kafka.additional-properties -features.messaging.kafka.embedded=messaging.kafka.embedded -features.rsocket=messaging.rsocket -features.rsocket.strategies-auto-configuration=messaging.rsocket.strategies-auto-configuration -features.rsocket.server-auto-configuration=messaging.rsocket.server-auto-configuration -features.rsocket.messaging=messaging.rsocket.messaging -features.rsocket.requester=messaging.rsocket.requester -features.spring-integration=messaging.spring-integration -features.websockets=messaging.websockets -features.developing-web-applications=web -features.graceful-shutdown=web.graceful-shutdown -features.developing-web-applications.spring-webflux=web.reactive.webflux -features.developing-web-applications.spring-webflux.auto-configuration=web.reactive.webflux.auto-configuration -features.developing-web-applications.spring-webflux.httpcodecs=web.reactive.webflux.httpcodecs -features.developing-web-applications.spring-webflux.static-context=web.reactive.webflux.static-content -features.developing-web-applications.spring-webflux.welcome-page=web.reactive.webflux.welcome-page -features.developing-web-applications.spring-webflux.template-engines=web.reactive.webflux.template-engines -features.developing-web-applications.spring-webflux.error-handling=web.reactive.webflux.error-handling -features.developing-web-applications.spring-webflux.error-pages=web.reactive.webflux.error-handling.error-pages -features.developing-web-applications.spring-webflux.web-filters=web.reactive.webflux.web-filters -features.developing-web-applications.spring-mvc=web.servlet.spring-mvc -features.developing-web-applications.spring-mvc.auto-configuration=web.servlet.spring-mvc.auto-configuration -features.developing-web-applications.spring-mvc.message-converters=web.servlet.spring-mvc.message-converters -features.developing-web-applications.spring-mvc.json=web.servlet.spring-mvc.json -features.developing-web-applications.spring-mvc.message-codes=web.servlet.spring-mvc.message-codes -features.developing-web-applications.spring-mvc.static-content=web.servlet.spring-mvc.static-content -features.developing-web-applications.spring-mvc.welcome-page=web.servlet.spring-mvc.welcome-page -features.developing-web-applications.spring-mvc.favicon=web.servlet.spring-mvc.favicon -features.developing-web-applications.spring-mvc.content-negotiation=web.servlet.spring-mvc.content-negotiation -features.developing-web-applications.spring-mvc.binding-initializer=web.servlet.spring-mvc.binding-initializer -features.developing-web-applications.spring-mvc.template-engines=web.servlet.spring-mvc.template-engines -features.developing-web-applications.spring-mvc.error-handling=web.servlet.spring-mvc.error-handling -features.developing-web-applications.spring-mvc.error-handling.error-pages=web.servlet.spring-mvc.error-handling.error-pages -features.developing-web-applications.spring-mvc.error-handling.error-pages-without-spring-mvc=web.servlet.spring-mvc.error-handling.error-pages-without-spring-mvc -features.developing-web-applications.spring-mvc.error-handling.in-a-war-deployment=web.servlet.spring-mvc.error-handling.in-a-war-deployment -features.developing-web-applications.spring-mvc.cors=web.servlet.spring-mvc.cors -features.developing-web-applications.jersey=web.servlet.jersey -features.developing-web-applications.embedded-container=web.servlet.embedded-container -features.developing-web-applications.embedded-container.servlets-filters-listeners=web.servlet.embedded-container.servlets-filters-listeners -features.developing-web-applications.embedded-container.servlets-filters-listeners.beans=web.servlet.embedded-container.servlets-filters-listeners.beans -features.developing-web-applications.embedded-container.context-initializer=web.servlet.embedded-container.context-initializer -features.developing-web-applications.embedded-container.context-initializer.scanning=web.servlet.embedded-container.context-initializer.scanning -features.developing-web-applications.embedded-container.application-context=web.servlet.embedded-container.application-context -features.developing-web-applications.embedded-container.customizing=web.servlet.embedded-container.customizing -features.developing-web-applications.embedded-container.customizing.programmatic=web.servlet.embedded-container.customizing.programmatic -features.developing-web-applications.embedded-container.customizing.direct=web.servlet.embedded-container.customizing.direct -features.developing-web-applications.embedded-container.jsp-limitations=web.servlet.embedded-container.jsp-limitations -features.developing-web-applications.reactive-server=web.reactive.reactive-server -features.developing-web-applications.reactive-server-resources-configuration=web.reactive.reactive-server-resources-configuration -features.spring-hateoas=web.spring-hateoas -features.security=web.security -features.security.spring-mvc=web.security.spring-mvc -features.security.spring-webflux=web.security.spring-webflux -features.security.oauth2=web.security.oauth2 -features.security.oauth2.client=web.security.oauth2.client -features.security.oauth2.client.common-providers=web.security.oauth2.client.common-providers -features.security.oauth2.server=web.security.oauth2.server -features.security.authorization-server=web.security.oauth2.authorization-server -features.security.saml2=web.security.saml2 -features.security.saml2.relying-party=web.security.saml2.relying-party -features.security.actuator=actuator.endpoints.security -features.security.actuator.csrf=actuator.endpoints.security.csrf -features.spring-session=web.spring-session -features.nosql=data.nosql -features.nosql.redis=data.nosql.redis -features.nosql.redis.connecting=data.nosql.redis.connecting -features.nosql.mongodb=data.nosql.mongodb -features.nosql.mongodb.connecting=data.nosql.mongodb.connecting -features.nosql.mongodb.template=data.nosql.mongodb.template -features.nosql.mongodb.repositories=data.nosql.mongodb.repositories -features.nosql.neo4j=data.nosql.neo4j -features.nosql.neo4j.connecting=data.nosql.neo4j.connecting -features.nosql.neo4j.repositories=data.nosql.neo4j.repositories -features.nosql.elasticsearch=data.nosql.elasticsearch -features.nosql.elasticsearch.connecting-using-rest=data.nosql.elasticsearch.connecting-using-rest -features.nosql.elasticsearch.connecting-using-reactive-rest=data.nosql.elasticsearch.connecting-using-reactive-rest -features.nosql.elasticsearch.connecting-using-spring-data=data.nosql.elasticsearch.connecting-using-spring-data -features.nosql.elasticsearch.repositories=data.nosql.elasticsearch.repositories -features.nosql.cassandra=data.nosql.cassandra -features.nosql.cassandra.connecting=data.nosql.cassandra.connecting -features.nosql.cassandra.repositories=data.nosql.cassandra.repositories -features.nosql.couchbase=data.nosql.couchbase -features.nosql.couchbase.connecting=data.nosql.couchbase.connecting -features.nosql.couchbase.repositories=data.nosql.couchbase.repositories -features.nosql.ldap=data.nosql.ldap -features.nosql.ldap.connecting=data.nosql.ldap.connecting -features.nosql.ldap.repositories=data.nosql.ldap.repositories -features.nosql.ldap.embedded=data.nosql.ldap.embedded -features.nosql.influxdb=data.nosql.influxdb -features.nosql.influxdb.connecting=data.nosql.influxdb.connecting -features.sql=data.sql -features.sql.datasource=data.sql.datasource -features.sql.datasource.embedded=data.sql.datasource.embedded -features.sql.datasource.production=data.sql.datasource.production -features.sql.datasource.configuration=data.sql.datasource.configuration -features.sql.datasource.connection-pool=data.sql.datasource.connection-pool -features.sql.datasource.jndi=data.sql.datasource.jndi -features.sql.jdbc-template=data.sql.jdbc-template -features.sql.jpa-and-spring-data=data.sql.jpa-and-spring-data -features.sql.jpa-and-spring-data.entity-classes=data.sql.jpa-and-spring-data.entity-classes -features.sql.jpa-and-spring-data.repositories=data.sql.jpa-and-spring-data.repositories -features.sql.jpa-and-spring-data.envers-repositories=data.sql.jpa-and-spring-data.envers-repositories -features.sql.jpa-and-spring-data.creating-and-dropping=data.sql.jpa-and-spring-data.creating-and-dropping -features.sql.jpa-and-spring-data.open-entity-manager-in-view=data.sql.jpa-and-spring-data.open-entity-manager-in-view -features.sql.jdbc=data.sql.jdbc -features.sql.h2-web-console=data.sql.h2-web-console -features.sql.h2-web-console.custom-path=data.sql.h2-web-console.custom-path -features.sql.jooq=data.sql.jooq -features.sql.jooq.codegen=data.sql.jooq.codegen -features.sql.jooq.dslcontext=data.sql.jooq.dslcontext -features.sql.jooq.sqldialect=data.sql.jooq.sqldialect -features.sql.jooq.customizing=data.sql.jooq.customizing -features.sql.r2dbc=data.sql.r2dbc -features.sql.r2dbc.embedded=data.sql.r2dbc.embedded -features.sql.r2dbc.using-database-client=data.sql.r2dbc.using-database-client -features.sql.r2dbc.repositories=data.sql.r2dbc.repositories -features.container-images.building=container-images.efficient-images -features.container-images.building.buildpacks=container-images.buildpacks -features.container-images.building.dockerfiles=container-images.dockerfiles -features.container-images=container-images.efficient-images -features.container-images.layering=container-images.efficient-images.layering -features.jmx=actuator.jmx -deployment.containers=container-images.efficient-images.unpacking -# Appendix restructuring, see gh-27003 -common-application-properties=appendix.application-properties -common-application-properties-core=appendix.application-properties.core -common-application-properties-cache=appendix.application-properties.cache -common-application-properties-mail=appendix.application-properties.mail -common-application-properties-json=appendix.application-properties.json -common-application-properties-data=appendix.application-properties.data -common-application-properties-transaction=appendix.application-properties.transaction -common-application-properties-data-migration=appendix.application-properties.data-migration -common-application-properties-integration=appendix.application-properties.integration -common-application-properties-web=appendix.application-properties.web -common-application-properties-templating=appendix.application-properties.templating -common-application-properties-server=appendix.application-properties.server -common-application-properties-security=appendix.application-properties.security -common-application-properties-rsocket=appendix.application-properties.rsocket -common-application-properties-actuator=appendix.application-properties.actuator -common-application-properties-devtools=appendix.application-properties.devtools -common-application-properties-testing=appendix.application-properties.testing - -application-properties=appendix.application-properties -application-properties.core=appendix.application-properties.core -application-properties.cache=appendix.application-properties.cache -application-properties.mail=appendix.application-properties.mail -application-properties.json=appendix.application-properties.json -application-properties.data=appendix.application-properties.data -application-properties.transaction=appendix.application-properties.transaction -application-properties.data-migration=appendix.application-properties.data-migration -application-properties.integration=appendix.application-properties.integration -application-properties.web=appendix.application-properties.web -application-properties.templating=appendix.application-properties.templating -application-properties.server=appendix.application-properties.server -application-properties.security=appendix.application-properties.security -application-properties.rsocket=appendix.application-properties.rsocket -application-properties.actuator=appendix.application-properties.actuator -application-properties.devtools=appendix.application-properties.devtools -application-properties.testing=appendix.application-properties.testing - -core-properties=appendix.application-properties.core -cache-properties=appendix.application-properties.cache -mail-properties=appendix.application-properties.mail -json-properties=appendix.application-properties.json -data-properties=appendix.application-properties.data -transaction-properties=appendix.application-properties.transaction -data-migration-properties=appendix.application-properties.data-migration -integration-properties=appendix.application-properties.integration -web-properties=appendix.application-properties.web -templating-properties=appendix.application-properties.templating -server-properties=appendix.application-properties.server -security-properties=appendix.application-properties.security -rsocket-properties=appendix.application-properties.rsocket -actuator-properties=appendix.application-properties.actuator -devtools-properties=appendix.application-properties.devtools -testing-properties=appendix.application-properties.testing - -configuration-metadata=appendix.configuration-metadata -configuration-metadata-format=appendix.configuration-metadata.format -configuration-metadata-group-attributes=appendix.configuration-metadata.format.group -configuration-metadata-property-attributes=appendix.configuration-metadata.format.property -configuration-metadata-hints-attributes=appendix.configuration-metadata.format.hints -configuration-metadata-repeated-items=appendix.configuration-metadata.format.repeated-items -configuration-metadata-providing-manual-hints=appendix.configuration-metadata.manual-hints -configuration-metadata-providing-manual-hints-value-hint=appendix.configuration-metadata.manual-hints.value-hint -configuration-metadata-providing-manual-hints-value-providers=appendix.configuration-metadata.manual-hints.value-providers -configuration-metadata-providing-manual-hints-any=appendix.configuration-metadata.manual-hints.value-providers.any -configuration-metadata-providing-manual-hints-class-reference=appendix.configuration-metadata.manual-hints.value-providers.class-reference -configuration-metadata-providing-manual-hints-handle-as=appendix.configuration-metadata.manual-hints.value-providers.handle-as -configuration-metadata-providing-manual-hints-logger-name=appendix.configuration-metadata.manual-hints.value-providers.logger-name -configuration-metadata-providing-manual-hints-spring-bean-reference=appendix.configuration-metadata.manual-hints.value-providers.spring-bean-reference -configuration-metadata-providing-manual-hints-spring-profile-name=appendix.configuration-metadata.manual-hints.value-providers.spring-profile-name -configuration-metadata-annotation-processor=appendix.configuration-metadata.annotation-processor -configuration-metadata-annotation-processor-setup=appendix.configuration-metadata.annotation-processor.configuring -configuration-metadata-annotation-processor-metadata-generation=appendix.configuration-metadata.annotation-processor.automatic-metadata-generation -configuration-metadata-annotation-processor-metadata-generation-nested=appendix.configuration-metadata.annotation-processor.automatic-metadata-generation.nested-properties -configuration-metadata-additional-metadata=appendix.configuration-metadata.annotation-processor.adding-additional-metadata - -configuration-metadata.format=appendix.configuration-metadata.format -configuration-metadata.format.group=appendix.configuration-metadata.format.group -configuration-metadata.format.property=appendix.configuration-metadata.format.property -configuration-metadata.format.hints=appendix.configuration-metadata.format.hints -configuration-metadata.format.repeated-items=appendix.configuration-metadata.format.repeated-items -configuration-metadata.manual-hints=appendix.configuration-metadata.manual-hints -configuration-metadata.manual-hints.value-hint=appendix.configuration-metadata.manual-hints.value-hint -configuration-metadata.manual-hints.value-providers=appendix.configuration-metadata.manual-hints.value-providers -configuration-metadata.manual-hints.value-providers.any=appendix.configuration-metadata.manual-hints.value-providers.any -configuration-metadata.manual-hints.value-providers.class-reference=appendix.configuration-metadata.manual-hints.value-providers.class-reference -configuration-metadata.manual-hints.value-providers.handle-as=appendix.configuration-metadata.manual-hints.value-providers.handle-as -configuration-metadata.manual-hints.value-providers.logger-name=appendix.configuration-metadata.manual-hints.value-providers.logger-name -configuration-metadata.manual-hints.value-providers.spring-bean-reference=appendix.configuration-metadata.manual-hints.value-providers.spring-bean-reference -configuration-metadata.manual-hints.value-providers.spring-profile-name=appendix.configuration-metadata.manual-hints.value-providers.spring-profile-name -configuration-metadata.annotation-processor=appendix.configuration-metadata.annotation-processor -configuration-metadata.annotation-processor.configuring=appendix.configuration-metadata.annotation-processor.configuring -configuration-metadata.annotation-processor.automatic-metadata-generation=appendix.configuration-metadata.annotation-processor.automatic-metadata-generation -configuration-metadata.annotation-processor.automatic-metadata-generation.nested-properties=appendix.configuration-metadata.annotation-processor.automatic-metadata-generation.nested-properties -configuration-metadata.annotation-processor.adding-additional-metadata=appendix.configuration-metadata.annotation-processor.adding-additional-metadata - -auto-configuration-classes=appendix.auto-configuration-classes -auto-configuration-classes-from-autoconfigure-module=appendix.auto-configuration-classes.core -auto-configuration-classes-from-actuator=appendix.auto-configuration-classes.actuator - -auto-configuration-classes.core=appendix.auto-configuration-classes.core -auto-configuration-classes.actuator=appendix.auto-configuration-classes.actuator - -test-auto-configuration=appendix.test-auto-configuration -test-auto-configuration-slices=appendix.test-auto-configuration.slices - -test-auto-configuration.slices=appendix.test-auto-configuration.slices - -executable-jar=appendix.executable-jar -executable-jar-nested-jars=appendix.executable-jar.nested-jars -executable-jar-jar-file-structure=appendix.executable-jar.nested-jars.jar-structure -executable-jar-war-file-structure=appendix.executable-jar.nested-jars.war-structure -executable-jar-war-index-files=appendix.executable-jar.nested-jars.index-files -executable-jar-war-index-files-classpath=appendix.executable-jar.nested-jars.classpath-index -executable-jar-war-index-files-layers=appendix.executable-jar.nested-jars.layer-index -executable-jar-jarfile=appendix.executable-jar.jarfile-class -executable-jar-jarfile-compatibility=appendix.executable-jar.jarfile-class.compatibility -executable-jar-launching=appendix.executable-jar.launching -executable-jar-launcher-manifest=appendix.executable-jar.launching.manifest -executable-jar-property-launcher-features=appendix.executable-jar.property-launcher -executable-jar-restrictions=appendix.executable-jar.restrictions -executable-jar-alternatives=appendix.executable-jar.alternatives - -executable-jar.nested-jars=appendix.executable-jar.nested-jars -executable-jar.nested-jars.jar-structure=appendix.executable-jar.nested-jars.jar-structure -executable-jar.nested-jars.war-structure=appendix.executable-jar.nested-jars.war-structure -executable-jar.nested-jars.index-files=appendix.executable-jar.nested-jars.index-files -executable-jar.nested-jars.classpath-index=appendix.executable-jar.nested-jars.classpath-index -executable-jar.nested-jars.layer-index=appendix.executable-jar.nested-jars.layer-index -executable-jar.jarfile-class=appendix.executable-jar.jarfile-class -executable-jar.jarfile-class.compatibility=appendix.executable-jar.jarfile-class.compatibility -executable-jar.launching=appendix.executable-jar.launching -executable-jar.launching.manifest=appendix.executable-jar.launching.manifest -executable-jar.property-launcher=appendix.executable-jar.property-launcher -executable-jar.restrictions=appendix.executable-jar.restrictions -executable-jar.alternatives=appendix.executable-jar.alternatives - -dependency-versions=appendix.dependency-versions -dependency-versions-coordinates=appendix.dependency-versions.coordinates -dependency-versions-properties=appendix.dependency-versions.properties - -dependency-versions.coordinates=appendix.dependency-versions.coordinates -dependency-versions.properties=appendix.dependency-versions.properties - -# gh-30405 -web.servlet.spring-mvc.json=features.json.jackson.custom-serializers-and-deserializers - -# gh-28597 -data.nosql.elasticsearch.connecting-using-rest.webclient=data.nosql.elasticsearch.connecting-using-rest.reactiveclient - -# Spring Boot 2.7 - 3.0 migrations -getting-started.first-application.code.enable-auto-configuration=getting-started.first-application.code.spring-boot-application -actuator.tracing=actuator.http-exchanges -actuator.tracing.custom=actuator.http-exchanges.custom - -# Spring Boot 3.0 - 3.1 migrations -howto.testing.testcontainers=features.testing.testcontainers -howto.testing.testcontainers.dynamic-properties=features.testing.testcontainers.dynamic-properties - -# gh-32905 -container-images.efficient-images.unpacking=deployment.efficient.unpacking - -# gh-35917 -howto.actuator.sanitize-sensitive-values=actuator.endpoints.sanitization -howto.actuator.sanitize-sensitive-values.customizing-sanitization=howto.actuator.customizing-sanitization - -# gh-28453 -deployment.installing.supported-operating-systems=deployment.installing -deployment.installing.nix-services=deployment.installing -deployment.installing.nix-services.init-d=deployment.installing.init-d -deployment.installing.nix-services.init-d.securing=deployment.installing.init-d.securing -deployment.installing.nix-services.system-d=deployment.installing.system-d -deployment.installing.nix-services.script-customization=deployment.installing.init-d.script-customization -deployment.installing.nix-services.script-customization.when-written=deployment.installing.init-d.script-customization.when-written -deployment.installing.nix-services.script-customization.when-running=deployment.installing.init-d.script-customization.when-running -deployment.installing.nix-services.script-customization.when-running.conf-file=deployment.installing.init-d.script-customization.when-running.conf-file - -# gh-35856 -features.testing.testcontainers.at-development-time=features.testcontainers.at-development-time -features.testing.testcontainers.at-development-time.dynamic-properties=features.testcontainers.at-development-time.dynamic-properties -features.testing.testcontainers.at-development-time.importing-container-declarations=features.testcontainers.at-development-time.importing-container-declarations -features.testing.testcontainers.at-development-time.devtools=features.testcontainers.at-development-time.devtools - -# gh-40503 -howto.data-initialization.using-jpa=howto.data-initialization.using-hibernate diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/application-properties.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/application-properties.adoc deleted file mode 100644 index 7c51da9f53fc..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/application-properties.adoc +++ /dev/null @@ -1,50 +0,0 @@ -[appendix] -[[appendix.application-properties]] -= Common Application Properties -include::attributes.adoc[] - - - -Various properties can be specified inside your `application.properties` file, inside your `application.yaml` file, or as command line switches. -This appendix provides a list of common Spring Boot properties and references to the underlying classes that consume them. - -TIP: Spring Boot provides various conversion mechanism with advanced value formatting, make sure to review <>. - -NOTE: Property contributions can come from additional jar files on your classpath, so you should not consider this an exhaustive list. -Also, you can define your own properties. - - - -include::application-properties/core.adoc[] - -include::application-properties/cache.adoc[] - -include::application-properties/mail.adoc[] - -include::application-properties/json.adoc[] - -include::application-properties/data.adoc[] - -include::application-properties/transaction.adoc[] - -include::application-properties/data-migration.adoc[] - -include::application-properties/integration.adoc[] - -include::application-properties/web.adoc[] - -include::application-properties/templating.adoc[] - -include::application-properties/server.adoc[] - -include::application-properties/security.adoc[] - -include::application-properties/rsocket.adoc[] - -include::application-properties/actuator.adoc[] - -include::application-properties/devtools.adoc[] - -include::application-properties/docker-compose.adoc[] - -include::application-properties/testing.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/attributes.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/attributes.adoc deleted file mode 100644 index f17f32c7eef0..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/attributes.adoc +++ /dev/null @@ -1,115 +0,0 @@ -:doctype: book -:idprefix: -:idseparator: - -:toc: left -:toclevels: 4 -:tabsize: 4 -:numbered: -:sectanchors: -:sectnums: -:hide-uri-scheme: -:docinfo: shared,private -:attribute-missing: warn -:chomp: default headers packages -:artifact-release-type: snapshot -:github-tag: main -:spring-boot-version: current -:github-repo: spring-projects/spring-boot -:github-raw: https://raw.githubusercontent.com/{github-repo}/{github-tag} -:github-issues: https://github.com/{github-repo}/issues/ -:github-wiki: https://github.com/{github-repo}/wiki -:docs-java: {docdir}/../main/java/org/springframework/boot/docs -:docs-kotlin: {docdir}/../main/kotlin/org/springframework/boot/docs -:docs-groovy: {docdir}/../main/groovy/org/springframework/boot/docs -:docs-resources: {docdir}/../main/resources -:spring-boot-code: https://github.com/{github-repo}/tree/{github-tag} -:spring-boot-api: https://docs.spring.io/spring-boot/docs/{spring-boot-version}/api -:spring-boot-docs: https://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference -:spring-boot-latest-code: https://github.com/{github-repo}/tree/main -:spring-boot-current-docs: https://docs.spring.io/spring-boot/docs/current/reference/ -:spring-boot-actuator-restapi-docs: https://docs.spring.io/spring-boot/docs/{spring-boot-version}/actuator-api/htmlsingle -:spring-boot-actuator-restapi-pdfdocs: https://docs.spring.io/spring-boot/docs/{spring-boot-version}/actuator-api/pdf/spring-boot-actuator-web-api.pdf -:spring-boot-maven-plugin-docs: https://docs.spring.io/spring-boot/docs/{spring-boot-version}/maven-plugin/reference/htmlsingle/ -:spring-boot-maven-plugin-pdfdocs: https://docs.spring.io/spring-boot/docs/{spring-boot-version}/maven-plugin/reference/pdf/spring-boot-maven-plugin-reference.pdf -:spring-boot-maven-plugin-api: https://docs.spring.io/spring-boot/docs/{spring-boot-version}/maven-plugin/api/ -:spring-boot-gradle-plugin-docs: https://docs.spring.io/spring-boot/docs/{spring-boot-version}/gradle-plugin/reference/htmlsingle/ -:spring-boot-gradle-plugin-pdfdocs: https://docs.spring.io/spring-boot/docs/{spring-boot-version}/gradle-plugin/reference/pdf/spring-boot-gradle-plugin-reference.pdf -:spring-boot-gradle-plugin-api: https://docs.spring.io/spring-boot/docs/{spring-boot-version}/gradle-plugin/api/ -:spring-boot-module-code: {spring-boot-code}/spring-boot-project/spring-boot/src/main/java/org/springframework/boot -:spring-boot-module-api: {spring-boot-api}/org/springframework/boot -:spring-boot-autoconfigure-module-code: {spring-boot-code}/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure -:spring-boot-autoconfigure-module-api: {spring-boot-api}/org/springframework/boot/autoconfigure -:spring-boot-actuator-module-code: {spring-boot-code}/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate -:spring-boot-actuator-module-api: {spring-boot-api}/org/springframework/boot/actuate -:spring-boot-actuator-autoconfigure-module-code: {spring-boot-code}/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure -:spring-boot-actuator-autoconfigure-module-api: : {spring-boot-api}/org/springframework/boot/actuate/autoconfigure -:spring-boot-cli-module-code: {spring-boot-code}/spring-boot-project/spring-boot-cli/src/main/java/org/springframework/boot/cli -:spring-boot-cli-module-api: {spring-boot-api}/org/springframework/boot/cli -:spring-boot-devtools-module-code: {spring-boot-code}/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools -:spring-boot-devtools-module-api: {spring-boot-api}/org/springframework/boot/devtools -:spring-boot-for-apache-geode: https://github.com/spring-projects/spring-boot-data-geode -:spring-boot-for-apache-geode-docs: https://docs.spring.io/spring-boot-data-geode-build/2.0.x/reference/html5/ -:spring-boot-test-module-code: {spring-boot-code}/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test -:spring-boot-test-module-api: {spring-boot-api}/org/springframework/boot/test -:spring-boot-test-autoconfigure-module-code: {spring-boot-code}/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure -:spring-boot-test-autoconfigure-module-api: {spring-boot-api}/org/springframework/boot/test/autoconfigure -:spring-amqp-api: https://docs.spring.io/spring-amqp/docs/{spring-amqp-version}/api/org/springframework/amqp -:spring-batch: https://spring.io/projects/spring-batch -:spring-batch-api: https://docs.spring.io/spring-batch/docs/{spring-batch-version}/api/org/springframework/batch -:spring-batch-docs: https://docs.spring.io/spring-batch/docs/{spring-batch-version}/reference/html/ -:spring-data: https://spring.io/projects/spring-data -:spring-data-cassandra: https://spring.io/projects/spring-data-cassandra -:spring-data-commons-api: https://docs.spring.io/spring-data/commons/docs/{spring-data-commons-version}/api/org/springframework/data -:spring-data-couchbase: https://spring.io/projects/spring-data-couchbase -:spring-data-couchbase-docs: https://docs.spring.io/spring-data/couchbase/docs/{spring-data-couchbase-version}/reference/html/ -:spring-data-elasticsearch: https://spring.io/projects/spring-data-elasticsearch -:spring-data-elasticsearch-docs: https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/ -:spring-data-envers: https://spring.io/projects/spring-data-envers -:spring-data-gemfire: https://spring.io/projects/spring-data-gemfire -:spring-data-geode: https://spring.io/projects/spring-data-geode -:spring-data-jpa: https://spring.io/projects/spring-data-jpa -:spring-data-jpa-api: https://docs.spring.io/spring-data/jpa/docs/{spring-data-jpa-version}/api/org/springframework/data/jpa -:spring-data-jpa-docs: https://docs.spring.io/spring-data/jpa/docs/{spring-data-jpa-version}/reference/html -:spring-data-jdbc-docs: https://docs.spring.io/spring-data/jdbc/docs/{spring-data-jdbc-version}/reference/html/ -:spring-data-ldap: https://spring.io/projects/spring-data-ldap -:spring-data-mongodb: https://spring.io/projects/spring-data-mongodb -:spring-data-mongodb-api: https://docs.spring.io/spring-data/mongodb/docs/{spring-data-mongodb-version}/api/org/springframework/data/mongodb -:spring-data-neo4j: https://spring.io/projects/spring-data-neo4j -:spring-data-neo4j-docs: https://docs.spring.io/spring-data/neo4j/docs/{spring-data-neo4j-version}/reference/html/ -:spring-data-r2dbc-api: https://docs.spring.io/spring-data/r2dbc/docs/{spring-data-r2dbc-version}/api/org/springframework/data/r2dbc -:spring-data-r2dbc-docs: https://docs.spring.io/spring-data/r2dbc/docs/{spring-data-r2dbc-version}/reference/html/ -:spring-data-redis: https://spring.io/projects/spring-data-redis -:spring-data-rest-api: https://docs.spring.io/spring-data/rest/docs/{spring-data-rest-version}/api/org/springframework/data/rest -:spring-framework: https://spring.io/projects/spring-framework -:spring-framework-api: https://docs.spring.io/spring-framework/docs/{spring-framework-version}/javadoc-api/org/springframework -:spring-framework-docs: https://docs.spring.io/spring-framework/reference/{spring-framework-version-antora} -:spring-graphql: https://spring.io/projects/spring-graphql -:spring-graphql-api: https://docs.spring.io/spring-graphql/docs/{spring-graphql-version}/api/ -:spring-graphql-docs: https://docs.spring.io/spring-graphql/docs/{spring-graphql-version}/reference/html/ -:spring-integration: https://spring.io/projects/spring-integration -:spring-integration-docs: https://docs.spring.io/spring-integration/docs/{spring-integration-version}/reference/html/ -:spring-kafka-docs: https://docs.spring.io/spring-kafka/docs/{spring-kafka-version}/reference/html/ -:spring-restdocs: https://spring.io/projects/spring-restdocs -:spring-security: https://spring.io/projects/spring-security -:spring-security-docs: https://docs.spring.io/spring-security/reference/{spring-security-version-antora} -:spring-authorization-server: https://spring.io/projects/spring-authorization-server -:spring-authorization-server-docs: https://docs.spring.io/spring-authorization-server/docs/{spring-authorization-server-version}/reference/html -:spring-session: https://spring.io/projects/spring-session -:spring-webservices-docs: https://docs.spring.io/spring-ws/docs/{spring-webservices-version}/reference/html/ -:ant-docs: https://ant.apache.org/manual -:dependency-management-plugin-code: https://github.com/spring-gradle-plugins/dependency-management-plugin -:dynatrace-docs: https://docs.dynatrace.com/docs/shortlink -:gradle-docs: https://docs.gradle.org/current/userguide -:hibernate-docs: https://docs.jboss.org/hibernate/orm/{hibernate-version}/userguide/html_single/Hibernate_User_Guide.html -:java-api: https://docs.oracle.com/en/java/javase/17/docs/api -:jooq-docs: https://www.jooq.org/doc/{jooq-version}/manual-single-page -:junit5-docs: https://junit.org/junit5/docs/current/user-guide -:kotlin-docs: https://kotlinlang.org/docs/reference/ -:lettuce-docs: https://lettuce.io/core/{lettuce-version}/reference/index.html -:micrometer-docs: https://docs.micrometer.io/micrometer/reference -:micrometer-concepts-docs: {micrometer-docs}/concepts -:micrometer-implementation-docs: {micrometer-docs}/implementations -:tomcat-docs: https://tomcat.apache.org/tomcat-{tomcat-version}-doc -:graal-version: 22.3 -:graal-native-image-docs: https://www.graalvm.org/{graal-version}/reference-manual/native-image -:liberica-nik-download: https://bell-sw.com/pages/downloads/native-image-kit/#/nik-22-17 diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/authors.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/authors.adoc deleted file mode 100644 index 3f3392961aec..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/authors.adoc +++ /dev/null @@ -1 +0,0 @@ -Phillip Webb; Dave Syer; Josh Long; Stéphane Nicoll; Rob Winch; Andy Wilkinson; Marcel Overdijk; Christian Dupuis; Sébastien Deleuze; Michael Simons; Vedran Pavić; Jay Bryant; Madhura Bhave; Eddú Meléndez; Scott Frederick; Moritz Halbritter diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/auto-configuration-classes.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/auto-configuration-classes.adoc deleted file mode 100644 index 13252737c083..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/auto-configuration-classes.adoc +++ /dev/null @@ -1,16 +0,0 @@ -[appendix] -[[appendix.auto-configuration-classes]] -= Auto-configuration Classes -include::attributes.adoc[] - - - -This appendix contains details of all of the auto-configuration classes provided by Spring Boot, with links to documentation and source code. -Remember to also look at the conditions report in your application for more details of which features are switched on. -(To do so, start the app with `--debug` or `-Ddebug` or, in an Actuator application, use the `conditions` endpoint). - - - -include::auto-configuration-classes/core.adoc[] - -include::auto-configuration-classes/actuator.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/auto-configuration-classes/actuator.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/auto-configuration-classes/actuator.adoc deleted file mode 100644 index 517465ea7bf5..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/auto-configuration-classes/actuator.adoc +++ /dev/null @@ -1,5 +0,0 @@ -[[appendix.auto-configuration-classes.actuator]] -== spring-boot-actuator-autoconfigure -The following auto-configuration classes are from the `spring-boot-actuator-autoconfigure` module: - -include::documented-auto-configuration-classes/spring-boot-actuator-autoconfigure.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/auto-configuration-classes/core.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/auto-configuration-classes/core.adoc deleted file mode 100644 index 0b70c76a13cd..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/auto-configuration-classes/core.adoc +++ /dev/null @@ -1,5 +0,0 @@ -[[appendix.auto-configuration-classes.core]] -== spring-boot-autoconfigure -The following auto-configuration classes are from the `spring-boot-autoconfigure` module: - -include::documented-auto-configuration-classes/spring-boot-autoconfigure.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/build-tool-plugins.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/build-tool-plugins.adoc deleted file mode 100644 index 1fff3b6bf05c..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/build-tool-plugins.adoc +++ /dev/null @@ -1,22 +0,0 @@ -[[build-tool-plugins]] -= Build Tool Plugins -include::attributes.adoc[] - - - -Spring Boot provides build tool plugins for Maven and Gradle. -The plugins offer a variety of features, including the packaging of executable jars. -This section provides more details on both plugins as well as some help should you need to extend an unsupported build system. -If you are just getting started, you might want to read "`<>`" from the "`<>`" section first. - - - -include::build-tool-plugins/maven.adoc[] - -include::build-tool-plugins/gradle.adoc[] - -include::build-tool-plugins/antlib.adoc[] - -include::build-tool-plugins/other-build-systems.adoc[] - -include::build-tool-plugins/whats-next.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/build-tool-plugins/antlib.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/build-tool-plugins/antlib.adoc deleted file mode 100644 index a7be882b6be6..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/build-tool-plugins/antlib.adoc +++ /dev/null @@ -1,148 +0,0 @@ -[[build-tool-plugins.antlib]] -== Spring Boot AntLib Module -The Spring Boot AntLib module provides basic Spring Boot support for Apache Ant. -You can use the module to create executable jars. -To use the module, you need to declare an additional `spring-boot` namespace in your `build.xml`, as shown in the following example: - -[source,xml,indent=0,subs="verbatim"] ----- - - ... - ----- - -You need to remember to start Ant using the `-lib` option, as shown in the following example: - -[source,shell,indent=0,subs="verbatim,attributes"] ----- - $ ant -lib ----- - -TIP: The "`Using Spring Boot`" section includes a more complete example of <>. - - - -[[build-tool-plugins.antlib.tasks]] -=== Spring Boot Ant Tasks -Once the `spring-boot-antlib` namespace has been declared, the following additional tasks are available: - -* <> -* <> - - - -[[build-tool-plugins.antlib.tasks.exejar]] -==== Using the "`exejar`" Task -You can use the `exejar` task to create a Spring Boot executable jar. -The following attributes are supported by the task: - -[cols="1,2,2"] -|==== -| Attribute | Description | Required - -| `destfile` -| The destination jar file to create -| Yes - -| `classes` -| The root directory of Java class files -| Yes - -| `start-class` -| The main application class to run -| No _(the default is the first class found that declares a `main` method)_ -|==== - -The following nested elements can be used with the task: - -[cols="1,4"] -|==== -| Element | Description - -| `resources` -| One or more {ant-docs}/Types/resources.html#collection[Resource Collections] describing a set of {ant-docs}/Types/resources.html[Resources] that should be added to the content of the created +jar+ file. - -| `lib` -| One or more {ant-docs}/Types/resources.html#collection[Resource Collections] that should be added to the set of jar libraries that make up the runtime dependency classpath of the application. -|==== - - - -[[build-tool-plugins.antlib.tasks.examples]] -==== Examples -This section shows two examples of Ant tasks. - -.Specify +start-class+ -[source,xml,indent=0,subs="verbatim"] ----- - - - - - - - - ----- - -.Detect +start-class+ -[source,xml,indent=0,subs="verbatim"] ----- - - - - - ----- - - - -[[build-tool-plugins.antlib.findmainclass]] -=== Using the "`findmainclass`" Task -The `findmainclass` task is used internally by `exejar` to locate a class declaring a `main`. -If necessary, you can also use this task directly in your build. -The following attributes are supported: - -[cols="1,2,2"] -|==== -| Attribute | Description | Required - -| `classesroot` -| The root directory of Java class files -| Yes _(unless `mainclass` is specified)_ - -| `mainclass` -| Can be used to short-circuit the `main` class search -| No - -| `property` -| The Ant property that should be set with the result -| No _(result will be logged if unspecified)_ -|==== - - - -[[build-tool-plugins.antlib.findmainclass.examples]] -==== Examples -This section contains three examples of using `findmainclass`. - -.Find and log -[source,xml,indent=0,subs="verbatim"] ----- - ----- - -.Find and set -[source,xml,indent=0,subs="verbatim"] ----- - ----- - -.Override and set -[source,xml,indent=0,subs="verbatim"] ----- - ----- diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/build-tool-plugins/gradle.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/build-tool-plugins/gradle.adoc deleted file mode 100644 index fa3c28c2dc98..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/build-tool-plugins/gradle.adoc +++ /dev/null @@ -1,8 +0,0 @@ -[[build-tool-plugins.gradle]] -== Spring Boot Gradle Plugin -The Spring Boot Gradle Plugin provides Spring Boot support in Gradle, letting you package executable jar or war archives, run Spring Boot applications, and use the dependency management provided by `spring-boot-dependencies`. -It requires Gradle 7.x (7.5 or later) or 8.x. -See the plugin's documentation to learn more: - -* Reference ({spring-boot-gradle-plugin-docs}[HTML] and {spring-boot-gradle-plugin-pdfdocs}[PDF]) -* {spring-boot-gradle-plugin-api}[API] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/build-tool-plugins/maven.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/build-tool-plugins/maven.adoc deleted file mode 100644 index 2db61aa9caad..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/build-tool-plugins/maven.adoc +++ /dev/null @@ -1,9 +0,0 @@ -[[build-tool-plugins.maven]] -== Spring Boot Maven Plugin -The Spring Boot Maven Plugin provides Spring Boot support in Maven, letting you package executable jar or war archives and run an application "`in-place`". -To use it, you must use Maven 3.6.3 or later. - -See the plugin's documentation to learn more: - -* Reference ({spring-boot-maven-plugin-docs}[HTML] and {spring-boot-maven-plugin-pdfdocs}[PDF]) -* {spring-boot-maven-plugin-api}[API] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/build-tool-plugins/whats-next.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/build-tool-plugins/whats-next.adoc deleted file mode 100644 index 2bdb01de04aa..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/build-tool-plugins/whats-next.adoc +++ /dev/null @@ -1,6 +0,0 @@ -[[build-tool-plugins.whats-next]] -== What to Read Next -If you are interested in how the build tool plugins work, you can look at the {spring-boot-code}/spring-boot-project/spring-boot-tools[`spring-boot-tools`] module on GitHub. -More technical details of the executable jar format are covered in <>. - -If you have specific build-related questions, see the "`<>`" guides. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/cli.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/cli.adoc deleted file mode 100644 index fbf719066c1c..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/cli.adoc +++ /dev/null @@ -1,12 +0,0 @@ -[[cli]] -= Spring Boot CLI -include::attributes.adoc[] - - -The Spring Boot CLI is a command line tool that you can use to bootstrap a new project from https://start.spring.io or encode a password. - - - -include::cli/installation.adoc[] - -include::cli/using-the-cli.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/cli/installation.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/cli/installation.adoc deleted file mode 100644 index e4c1cdd9b1d9..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/cli/installation.adoc +++ /dev/null @@ -1,4 +0,0 @@ -[[cli.installation]] -== Installing the CLI -The Spring Boot CLI (Command-Line Interface) can be installed manually by using SDKMAN! (the SDK Manager) or by using Homebrew or MacPorts if you are an OSX user. -See _<>_ in the "`Getting started`" section for comprehensive installation instructions. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/cli/using-the-cli.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/cli/using-the-cli.adoc deleted file mode 100644 index 2544ccf50fda..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/cli/using-the-cli.adoc +++ /dev/null @@ -1,174 +0,0 @@ -[[cli.using-the-cli]] -== Using the CLI -Once you have installed the CLI, you can run it by typing `spring` and pressing Enter at the command line. -If you run `spring` without any arguments, a help screen is displayed, as follows: - -[source,shell,indent=0,subs="verbatim"] ----- - $ spring - usage: spring [--help] [--version] - [] - - Available commands are: - - init [options] [location] - Initialize a new project using Spring Initializr (start.spring.io) - - encodepassword [options] - Encode a password for use with Spring Security - - shell - Start a nested shell - - Common options: - - --debug Verbose mode - Print additional status information for the command you are running - - - See 'spring help ' for more information on a specific command. ----- - -You can type `spring help` to get more details about any of the supported commands, as shown in the following example: - -[source,shell,indent=0,subs="verbatim"] ----- - $ spring help init - spring init - Initialize a new project using Spring Initializr (start.spring.io) - - usage: spring init [options] [location] - - Option Description - ------ ----------- - -a, --artifact-id Project coordinates; infer archive name (for - example 'test') - -b, --boot-version Spring Boot version (for example '1.2.0.RELEASE') - --build Build system to use (for example 'maven' or - 'gradle') (default: maven) - -d, --dependencies Comma-separated list of dependency identifiers to - include in the generated project - --description Project description - -f, --force Force overwrite of existing files - --format Format of the generated content (for example - 'build' for a build file, 'project' for a - project archive) (default: project) - -g, --group-id Project coordinates (for example 'org.test') - -j, --java-version Language level (for example '1.8') - -l, --language Programming language (for example 'java') - --list List the capabilities of the service. Use it to - discover the dependencies and the types that are - available - -n, --name Project name; infer application name - -p, --packaging Project packaging (for example 'jar') - --package-name Package name - -t, --type Project type. Not normally needed if you use -- - build and/or --format. Check the capabilities of - the service (--list) for more details - --target URL of the service to use (default: https://start. - spring.io) - -v, --version Project version (for example '0.0.1-SNAPSHOT') - -x, --extract Extract the project archive. Inferred if a - location is specified without an extension - - examples: - - To list all the capabilities of the service: - $ spring init --list - - To creates a default project: - $ spring init - - To create a web my-app.zip: - $ spring init -d=web my-app.zip - - To create a web/data-jpa gradle project unpacked: - $ spring init -d=web,jpa --build=gradle my-dir ----- - -The `version` command provides a quick way to check which version of Spring Boot you are using, as follows: - -[source,shell,indent=0,subs="verbatim,attributes"] ----- - $ spring version - Spring CLI v{spring-boot-version} ----- - - - -[[cli.using-the-cli.initialize-new-project]] -=== Initialize a New Project -The `init` command lets you create a new project by using https://start.spring.io without leaving the shell, as shown in the following example: - -[source,shell,indent=0,subs="verbatim"] ----- - $ spring init --dependencies=web,data-jpa my-project - Using service at https://start.spring.io - Project extracted to '/Users/developer/example/my-project' ----- - -The preceding example creates a `my-project` directory with a Maven-based project that uses `spring-boot-starter-web` and `spring-boot-starter-data-jpa`. -You can list the capabilities of the service by using the `--list` flag, as shown in the following example: - -[source,shell,indent=0,subs="verbatim"] ----- - $ spring init --list - ======================================= - Capabilities of https://start.spring.io - ======================================= - - Available dependencies: - ----------------------- - actuator - Actuator: Production ready features to help you monitor and manage your application - ... - web - Web: Support for full-stack web development, including Tomcat and spring-webmvc - websocket - Websocket: Support for WebSocket development - ws - WS: Support for Spring Web Services - - Available project types: - ------------------------ - gradle-build - Gradle Config [format:build, build:gradle] - gradle-project - Gradle Project [format:project, build:gradle] - maven-build - Maven POM [format:build, build:maven] - maven-project - Maven Project [format:project, build:maven] (default) - - ... ----- - -The `init` command supports many options. -See the `help` output for more details. -For instance, the following command creates a Gradle project that uses Java 17 and `war` packaging: - -[source,shell,indent=0,subs="verbatim"] ----- - $ spring init --build=gradle --java-version=17 --dependencies=websocket --packaging=war sample-app.zip - Using service at https://start.spring.io - Content saved to 'sample-app.zip' ----- - - - -[[cli.using-the-cli.embedded-shell]] -=== Using the Embedded Shell -Spring Boot includes command-line completion scripts for the BASH and zsh shells. -If you do not use either of these shells (perhaps you are a Windows user), you can use the `shell` command to launch an integrated shell, as shown in the following example: - -[source,shell,indent=0,subs="verbatim,quotes,attributes"] ----- - $ spring shell - *Spring Boot* (v{spring-boot-version}) - Hit TAB to complete. Type \'help' and hit RETURN for help, and \'exit' to quit. ----- - -From inside the embedded shell, you can run other commands directly: - -[source,shell,indent=0,subs="verbatim,attributes"] ----- - $ version - Spring CLI v{spring-boot-version} ----- - -The embedded shell supports ANSI color output as well as `tab` completion. -If you need to run a native command, you can use the `!` prefix. -To exit the embedded shell, press `ctrl-c`. - - diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata.adoc deleted file mode 100644 index 193fd065b504..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata.adoc +++ /dev/null @@ -1,20 +0,0 @@ -[appendix] -[[appendix.configuration-metadata]] -= Configuration Metadata -include::attributes.adoc[] - - - -Spring Boot jars include metadata files that provide details of all supported configuration properties. -The files are designed to let IDE developers offer contextual help and "`code completion`" as users are working with `application.properties` or `application.yaml` files. - -The majority of the metadata file is generated automatically at compile time by processing all items annotated with `@ConfigurationProperties`. -However, it is possible to <> for corner cases or more advanced use cases. - - - -include::configuration-metadata/format.adoc[] - -include::configuration-metadata/manual-hints.adoc[] - -include::configuration-metadata/annotation-processor.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata/manual-hints.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata/manual-hints.adoc deleted file mode 100644 index a77666e60c48..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata/manual-hints.adoc +++ /dev/null @@ -1,366 +0,0 @@ -[[appendix.configuration-metadata.manual-hints]] -== Providing Manual Hints -To improve the user experience and further assist the user in configuring a given property, you can provide additional metadata that: - -* Describes the list of potential values for a property. -* Associates a provider, to attach a well defined semantic to a property, so that a tool can discover the list of potential values based on the project's context. - - - -[[appendix.configuration-metadata.manual-hints.value-hint]] -=== Value Hint -The `name` attribute of each hint refers to the `name` of a property. -In the <>, we provide five values for the `spring.jpa.hibernate.ddl-auto` property: `none`, `validate`, `update`, `create`, and `create-drop`. -Each value may have a description as well. - -If your property is of type `Map`, you can provide hints for both the keys and the values (but not for the map itself). -The special `.keys` and `.values` suffixes must refer to the keys and the values, respectively. - -Assume a `my.contexts` maps magic `String` values to an integer, as shown in the following example: - -include::code:MyProperties[] - -The magic values are (in this example) are `sample1` and `sample2`. -In order to offer additional content assistance for the keys, you could add the following JSON to <>: - -[source,json,indent=0,subs="verbatim"] ----- - {"hints": [ - { - "name": "my.contexts.keys", - "values": [ - { - "value": "sample1" - }, - { - "value": "sample2" - } - ] - } - ]} ----- - -TIP: We recommend that you use an `Enum` for those two values instead. -If your IDE supports it, this is by far the most effective approach to auto-completion. - - - -[[appendix.configuration-metadata.manual-hints.value-providers]] -=== Value Providers -Providers are a powerful way to attach semantics to a property. -In this section, we define the official providers that you can use for your own hints. -However, your favorite IDE may implement some of these or none of them. -Also, it could eventually provide its own. - -NOTE: As this is a new feature, IDE vendors must catch up with how it works. -Adoption times naturally vary. - -The following table summarizes the list of supported providers: - -[cols="2,4"] -|=== -| Name | Description - -| `any` -| Permits any additional value to be provided. - -| `class-reference` -| Auto-completes the classes available in the project. - Usually constrained by a base class that is specified by the `target` parameter. - -| `handle-as` -| Handles the property as if it were defined by the type defined by the mandatory `target` parameter. - -| `logger-name` -| Auto-completes valid logger names and <>. - Typically, package and class names available in the current project can be auto-completed as well as defined groups. - -| `spring-bean-reference` -| Auto-completes the available bean names in the current project. - Usually constrained by a base class that is specified by the `target` parameter. - -| `spring-profile-name` -| Auto-completes the available Spring profile names in the project. -|=== - -TIP: Only one provider can be active for a given property, but you can specify several providers if they can all manage the property _in some way_. -Make sure to place the most powerful provider first, as the IDE must use the first one in the JSON section that it can handle. -If no provider for a given property is supported, no special content assistance is provided, either. - - - -[[appendix.configuration-metadata.manual-hints.value-providers.any]] -==== Any -The special **any** provider value permits any additional values to be provided. -Regular value validation based on the property type should be applied if this is supported. - -This provider is typically used if you have a list of values and any extra values should still be considered as valid. - -The following example offers `on` and `off` as auto-completion values for `system.state`: - -[source,json,indent=0,subs="verbatim"] ----- - {"hints": [ - { - "name": "system.state", - "values": [ - { - "value": "on" - }, - { - "value": "off" - } - ], - "providers": [ - { - "name": "any" - } - ] - } - ]} ----- - -Note that, in the preceding example, any other value is also allowed. - - - -[[appendix.configuration-metadata.manual-hints.value-providers.class-reference]] -==== Class Reference -The **class-reference** provider auto-completes classes available in the project. -This provider supports the following parameters: - -[cols="1,1,2,4"] -|=== -| Parameter | Type | Default value | Description - -| `target` -| `String` (`Class`) -| _none_ -| The fully qualified name of the class that should be assignable to the chosen value. - Typically used to filter out-non candidate classes. - Note that this information can be provided by the type itself by exposing a class with the appropriate upper bound. - -| `concrete` -| `boolean` -| true -| Specify whether only concrete classes are to be considered as valid candidates. -|=== - - -The following metadata snippet corresponds to the standard `server.servlet.jsp.class-name` property that defines the `JspServlet` class name to use: - -[source,json,indent=0,subs="verbatim"] ----- - {"hints": [ - { - "name": "server.servlet.jsp.class-name", - "providers": [ - { - "name": "class-reference", - "parameters": { - "target": "jakarta.servlet.http.HttpServlet" - } - } - ] - } - ]} ----- - - - -[[appendix.configuration-metadata.manual-hints.value-providers.handle-as]] -==== Handle As -The **handle-as** provider lets you substitute the type of the property to a more high-level type. -This typically happens when the property has a `java.lang.String` type, because you do not want your configuration classes to rely on classes that may not be on the classpath. -This provider supports the following parameters: - -[cols="1,1,2,4"] -|=== -| Parameter | Type | Default value | Description - -| **`target`** -| `String` (`Class`) -| _none_ -| The fully qualified name of the type to consider for the property. - This parameter is mandatory. -|=== - -The following types can be used: - -* Any `java.lang.Enum`: Lists the possible values for the property. - (We recommend defining the property with the `Enum` type, as no further hint should be required for the IDE to auto-complete the values) -* `java.nio.charset.Charset`: Supports auto-completion of charset/encoding values (such as `UTF-8`) -* `java.util.Locale`: auto-completion of locales (such as `en_US`) -* `org.springframework.util.MimeType`: Supports auto-completion of content type values (such as `text/plain`) -* `org.springframework.core.io.Resource`: Supports auto-completion of Spring’s Resource abstraction to refer to a file on the filesystem or on the classpath (such as `classpath:/sample.properties`) - -TIP: If multiple values can be provided, use a `Collection` or _Array_ type to teach the IDE about it. - -The following metadata snippet corresponds to the standard `spring.liquibase.change-log` property that defines the path to the changelog to use. -It is actually used internally as a `org.springframework.core.io.Resource` but cannot be exposed as such, because we need to keep the original String value to pass it to the Liquibase API. - -[source,json,indent=0,subs="verbatim"] ----- - {"hints": [ - { - "name": "spring.liquibase.change-log", - "providers": [ - { - "name": "handle-as", - "parameters": { - "target": "org.springframework.core.io.Resource" - } - } - ] - } - ]} ----- - - - -[[appendix.configuration-metadata.manual-hints.value-providers.logger-name]] -==== Logger Name -The **logger-name** provider auto-completes valid logger names and <>. -Typically, package and class names available in the current project can be auto-completed. -If groups are enabled (default) and if a custom logger group is identified in the configuration, auto-completion for it should be provided. -Specific frameworks may have extra magic logger names that can be supported as well. - -This provider supports the following parameters: - -[cols="1,1,2,4"] -|=== -| Parameter | Type | Default value | Description - -| `group` -| `boolean` -| `true` -| Specify whether known groups should be considered. -|=== - -Since a logger name can be any arbitrary name, this provider should allow any value but could highlight valid package and class names that are not available in the project's classpath. - -The following metadata snippet corresponds to the standard `logging.level` property. -Keys are _logger names_, and values correspond to the standard log levels or any custom level. -As Spring Boot defines a few logger groups out-of-the-box, dedicated value hints have been added for those. - -[source,json,indent=0,subs="verbatim"] ----- - {"hints": [ - { - "name": "logging.level.keys", - "values": [ - { - "value": "root", - "description": "Root logger used to assign the default logging level." - }, - { - "value": "sql", - "description": "SQL logging group including Hibernate SQL logger." - }, - { - "value": "web", - "description": "Web logging group including codecs." - } - ], - "providers": [ - { - "name": "logger-name" - } - ] - }, - { - "name": "logging.level.values", - "values": [ - { - "value": "trace" - }, - { - "value": "debug" - }, - { - "value": "info" - }, - { - "value": "warn" - }, - { - "value": "error" - }, - { - "value": "fatal" - }, - { - "value": "off" - } - - ], - "providers": [ - { - "name": "any" - } - ] - } - ]} ----- - - - -[[appendix.configuration-metadata.manual-hints.value-providers.spring-bean-reference]] -==== Spring Bean Reference -The **spring-bean-reference** provider auto-completes the beans that are defined in the configuration of the current project. -This provider supports the following parameters: - -[cols="1,1,2,4"] -|=== -| Parameter | Type | Default value | Description - -| `target` -| `String` (`Class`) -| _none_ -| The fully qualified name of the bean class that should be assignable to the candidate. - Typically used to filter out non-candidate beans. -|=== - -The following metadata snippet corresponds to the standard `spring.jmx.server` property that defines the name of the `MBeanServer` bean to use: - -[source,json,indent=0,subs="verbatim"] ----- - {"hints": [ - { - "name": "spring.jmx.server", - "providers": [ - { - "name": "spring-bean-reference", - "parameters": { - "target": "javax.management.MBeanServer" - } - } - ] - } - ]} ----- - -NOTE: The binder is not aware of the metadata. -If you provide that hint, you still need to transform the bean name into an actual Bean reference using by the `ApplicationContext`. - - - -[[appendix.configuration-metadata.manual-hints.value-providers.spring-profile-name]] -==== Spring Profile Name -The **spring-profile-name** provider auto-completes the Spring profiles that are defined in the configuration of the current project. - -The following metadata snippet corresponds to the standard `spring.profiles.active` property that defines the name of the Spring profile(s) to enable: - -[source,json,indent=0,subs="verbatim"] ----- - {"hints": [ - { - "name": "spring.profiles.active", - "providers": [ - { - "name": "spring-profile-name" - } - ] - } - ]} ----- diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/container-images.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/container-images.adoc deleted file mode 100644 index 651b15a4e780..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/container-images.adoc +++ /dev/null @@ -1,13 +0,0 @@ -[[container-images]] -= Container Images -include::attributes.adoc[] - -Spring Boot applications can be containerized <>, or by <>. - -include::container-images/efficient-images.adoc[] - -include::container-images/dockerfiles.adoc[] - -include::container-images/cloud-native-buildpacks.adoc[] - -include::container-images/whats-next.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/container-images/cloud-native-buildpacks.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/container-images/cloud-native-buildpacks.adoc deleted file mode 100644 index 020eac38324c..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/container-images/cloud-native-buildpacks.adoc +++ /dev/null @@ -1,19 +0,0 @@ -[[container-images.buildpacks]] -== Cloud Native Buildpacks -Dockerfiles are just one way to build docker images. -Another way to build docker images is directly from your Maven or Gradle plugin, using buildpacks. -If you’ve ever used an application platform such as Cloud Foundry or Heroku then you’ve probably used a buildpack. -Buildpacks are the part of the platform that takes your application and converts it into something that the platform can actually run. -For example, Cloud Foundry’s Java buildpack will notice that you’re pushing a `.jar` file and automatically add a relevant JRE. - -With Cloud Native Buildpacks, you can create Docker compatible images that you can run anywhere. -Spring Boot includes buildpack support directly for both Maven and Gradle. -This means you can just type a single command and quickly get a sensible image into your locally running Docker daemon. - -See the individual plugin documentation on how to use buildpacks with {spring-boot-maven-plugin-docs}#build-image[Maven] and {spring-boot-gradle-plugin-docs}#build-image[Gradle]. - -NOTE: The https://github.com/paketo-buildpacks/spring-boot[Paketo Spring Boot buildpack] supports the `layers.idx` file, so any customization that is applied to it will be reflected in the image created by the buildpack. - -NOTE: In order to achieve reproducible builds and container image caching, Buildpacks can manipulate the application resources metadata (such as the file "last modified" information). -You should ensure that your application does not rely on that metadata at runtime. -Spring Boot can use that information when serving static resources, but this can be disabled with configprop:spring.web.resources.cache.use-last-modified[]. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/container-images/dockerfiles.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/container-images/dockerfiles.adoc deleted file mode 100644 index 1c3d77cff254..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/container-images/dockerfiles.adoc +++ /dev/null @@ -1,63 +0,0 @@ -[[container-images.dockerfiles]] -== Dockerfiles -While it is possible to convert a Spring Boot fat jar into a docker image with just a few lines in the Dockerfile, we will use the <> to create an optimized docker image. -When you create a jar containing the layers index file, the `spring-boot-jarmode-layertools` jar will be added as a dependency to your jar. -With this jar on the classpath, you can launch your application in a special mode which allows the bootstrap code to run something entirely different from your application, for example, something that extracts the layers. - -CAUTION: The `layertools` mode can not be used with a <> that includes a launch script. -Disable launch script configuration when building a jar file that is intended to be used with `layertools`. - -Here’s how you can launch your jar with a `layertools` jar mode: - -[source,shell,indent=0,subs="verbatim"] ----- -$ java -Djarmode=layertools -jar my-app.jar ----- - -This will provide the following output: - -[subs="verbatim"] ----- -Usage: - java -Djarmode=layertools -jar my-app.jar - -Available commands: - list List layers from the jar that can be extracted - extract Extracts layers from the jar for image creation - help Help about any command ----- - -The `extract` command can be used to easily split the application into layers to be added to the dockerfile. -Here is an example of a Dockerfile using `jarmode`. - -[source,dockerfile,indent=0,subs="verbatim"] ----- -FROM eclipse-temurin:17-jre as builder -WORKDIR application -ARG JAR_FILE=target/*.jar -COPY ${JAR_FILE} application.jar -RUN java -Djarmode=layertools -jar application.jar extract - -FROM eclipse-temurin:17-jre -WORKDIR application -COPY --from=builder application/dependencies/ ./ -COPY --from=builder application/spring-boot-loader/ ./ -COPY --from=builder application/snapshot-dependencies/ ./ -COPY --from=builder application/application/ ./ -ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"] ----- - -Assuming the above `Dockerfile` is in the current directory, your docker image can be built with `docker build .`, or optionally specifying the path to your application jar, as shown in the following example: - -[source,shell,indent=0,subs="verbatim"] ----- - $ docker build --build-arg JAR_FILE=path/to/myapp.jar . ----- - -This is a multi-stage dockerfile. -The builder stage extracts the directories that are needed later. -Each of the `COPY` commands relates to the layers extracted by the jarmode. - -Of course, a Dockerfile can be written without using the jarmode. -You can use some combination of `unzip` and `mv` to move things to the right layer but jarmode simplifies that. - diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/container-images/efficient-images.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/container-images/efficient-images.adoc deleted file mode 100644 index ad70d6a75eb5..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/container-images/efficient-images.adoc +++ /dev/null @@ -1,47 +0,0 @@ -[[container-images.efficient-images]] -== Efficient Container Images -It is easily possible to package a Spring Boot fat jar as a docker image. -However, there are various downsides to copying and running the fat jar as is in the docker image. -There’s always a certain amount of overhead when running a fat jar without unpacking it, and in a containerized environment this can be noticeable. -The other issue is that putting your application's code and all its dependencies in one layer in the Docker image is sub-optimal. -Since you probably recompile your code more often than you upgrade the version of Spring Boot you use, it’s often better to separate things a bit more. -If you put jar files in the layer before your application classes, Docker often only needs to change the very bottom layer and can pick others up from its cache. - - -[[container-images.efficient-images.layering]] -=== Layering Docker Images -To make it easier to create optimized Docker images, Spring Boot supports adding a layer index file to the jar. -It provides a list of layers and the parts of the jar that should be contained within them. -The list of layers in the index is ordered based on the order in which the layers should be added to the Docker/OCI image. -Out-of-the-box, the following layers are supported: - -* `dependencies` (for regular released dependencies) -* `spring-boot-loader` (for everything under `org/springframework/boot/loader`) -* `snapshot-dependencies` (for snapshot dependencies) -* `application` (for application classes and resources) - -The following shows an example of a `layers.idx` file: - -[source,yaml,indent=0,subs="verbatim"] ----- - - "dependencies": - - BOOT-INF/lib/library1.jar - - BOOT-INF/lib/library2.jar - - "spring-boot-loader": - - org/springframework/boot/loader/JarLauncher.class - - org/springframework/boot/loader/jar/JarEntry.class - - "snapshot-dependencies": - - BOOT-INF/lib/library3-SNAPSHOT.jar - - "application": - - META-INF/MANIFEST.MF - - BOOT-INF/classes/a/b/C.class ----- - -This layering is designed to separate code based on how likely it is to change between application builds. -Library code is less likely to change between builds, so it is placed in its own layers to allow tooling to re-use the layers from cache. -Application code is more likely to change between builds so it is isolated in a separate layer. - -Spring Boot also supports layering for war files with the help of a `layers.idx`. - -For Maven, see the {spring-boot-maven-plugin-docs}#repackage-layers[packaging layered jar or war section] for more details on adding a layer index to the archive. -For Gradle, see the {spring-boot-gradle-plugin-docs}#packaging-layered-archives[packaging layered jar or war section] of the Gradle plugin documentation. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/container-images/whats-next.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/container-images/whats-next.adoc deleted file mode 100644 index 9d0645f4a579..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/container-images/whats-next.adoc +++ /dev/null @@ -1,5 +0,0 @@ -[[container-images.whats-next]] -== What to Read Next -Once you've learned how to build efficient container images, you can read about <>, such as Kubernetes. - - diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data.adoc deleted file mode 100644 index 3015216815b8..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data.adoc +++ /dev/null @@ -1,11 +0,0 @@ -[[data]] -= Data -include::attributes.adoc[] - -Spring Boot integrates with a number of data technologies, both SQL and NoSQL. - -include::data/sql.adoc[] - -include::data/nosql.adoc[] - -include::data/whats-next.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc deleted file mode 100644 index 1335dbfa99ab..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc +++ /dev/null @@ -1,684 +0,0 @@ -[[data.nosql]] -== Working with NoSQL Technologies -Spring Data provides additional projects that help you access a variety of NoSQL technologies, including: - -* {spring-data-cassandra}[Cassandra] -* {spring-data-couchbase}[Couchbase] -* {spring-data-elasticsearch}[Elasticsearch] -* {spring-data-gemfire}[GemFire] or {spring-data-geode}[Geode] -* {spring-data-ldap}[LDAP] -* {spring-data-mongodb}[MongoDB] -* {spring-data-neo4j}[Neo4J] -* {spring-data-redis}[Redis] - -Of these, Spring Boot provides auto-configuration for Cassandra, Couchbase, Elasticsearch, LDAP, MongoDB, Neo4J and Redis. -Additionally, {spring-boot-for-apache-geode}[Spring Boot for Apache Geode] provides {spring-boot-for-apache-geode-docs}#geode-repositories[auto-configuration for Apache Geode]. -You can make use of the other projects, but you must configure them yourself. -See the appropriate reference documentation at {spring-data}. - -Spring Boot also provides auto-configuration for the InfluxDB client. - - - -[[data.nosql.redis]] -=== Redis -https://redis.io/[Redis] is a cache, message broker, and richly-featured key-value store. -Spring Boot offers basic auto-configuration for the https://github.com/lettuce-io/lettuce-core/[Lettuce] and https://github.com/xetorthio/jedis/[Jedis] client libraries and the abstractions on top of them provided by https://github.com/spring-projects/spring-data-redis[Spring Data Redis]. - -There is a `spring-boot-starter-data-redis` "`Starter`" for collecting the dependencies in a convenient way. -By default, it uses https://github.com/lettuce-io/lettuce-core/[Lettuce]. -That starter handles both traditional and reactive applications. - -TIP: We also provide a `spring-boot-starter-data-redis-reactive` "`Starter`" for consistency with the other stores with reactive support. - - - -[[data.nosql.redis.connecting]] -==== Connecting to Redis -You can inject an auto-configured `RedisConnectionFactory`, `StringRedisTemplate`, or vanilla `RedisTemplate` instance as you would any other Spring Bean. -The following listing shows an example of such a bean: - -include::code:MyBean[] - -By default, the instance tries to connect to a Redis server at `localhost:6379`. -You can specify custom connection details using `spring.data.redis.*` properties, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - data: - redis: - host: "localhost" - port: 6379 - database: 0 - username: "user" - password: "secret" ----- - -TIP: You can also register an arbitrary number of beans that implement `LettuceClientConfigurationBuilderCustomizer` for more advanced customizations. -`ClientResources` can also be customized using `ClientResourcesBuilderCustomizer`. -If you use Jedis, `JedisClientConfigurationBuilderCustomizer` is also available. -Alternatively, you can register a bean of type `RedisStandaloneConfiguration`, `RedisSentinelConfiguration`, or `RedisClusterConfiguration` to take full control over the configuration. - -If you add your own `@Bean` of any of the auto-configured types, it replaces the default (except in the case of `RedisTemplate`, when the exclusion is based on the bean name, `redisTemplate`, not its type). - -By default, a pooled connection factory is auto-configured if `commons-pool2` is on the classpath. - -The auto-configured `RedisConnectionFactory` can be configured to use SSL for communication with the server by setting the properties as shown in this example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - data: - redis: - ssl: - enabled: true ----- - -Custom SSL trust material can be configured in an <> and applied to the `RedisConnectionFactory` as shown in this example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - data: - redis: - ssl: - bundle: "example" ----- - - -[[data.nosql.mongodb]] -=== MongoDB -https://www.mongodb.com/[MongoDB] is an open-source NoSQL document database that uses a JSON-like schema instead of traditional table-based relational data. -Spring Boot offers several conveniences for working with MongoDB, including the `spring-boot-starter-data-mongodb` and `spring-boot-starter-data-mongodb-reactive` "`Starters`". - - - -[[data.nosql.mongodb.connecting]] -==== Connecting to a MongoDB Database -To access MongoDB databases, you can inject an auto-configured `org.springframework.data.mongodb.MongoDatabaseFactory`. -By default, the instance tries to connect to a MongoDB server at `mongodb://localhost/test`. -The following example shows how to connect to a MongoDB database: - -include::code:MyBean[] - -If you have defined your own `MongoClient`, it will be used to auto-configure a suitable `MongoDatabaseFactory`. - -The auto-configured `MongoClient` is created using a `MongoClientSettings` bean. -If you have defined your own `MongoClientSettings`, it will be used without modification and the `spring.data.mongodb` properties will be ignored. -Otherwise a `MongoClientSettings` will be auto-configured and will have the `spring.data.mongodb` properties applied to it. -In either case, you can declare one or more `MongoClientSettingsBuilderCustomizer` beans to fine-tune the `MongoClientSettings` configuration. -Each will be called in order with the `MongoClientSettings.Builder` that is used to build the `MongoClientSettings`. - -You can set the configprop:spring.data.mongodb.uri[] property to change the URL and configure additional settings such as the _replica set_, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - data: - mongodb: - uri: "mongodb://user:secret@mongoserver1.example.com:27017,mongoserver2.example.com:23456/test" ----- - -Alternatively, you can specify connection details using discrete properties. -For example, you might declare the following settings in your `application.properties`: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - data: - mongodb: - host: "mongoserver1.example.com" - port: 27017 - additional-hosts: - - "mongoserver2.example.com:23456" - database: "test" - username: "user" - password: "secret" ----- - -The auto-configured `MongoClient` can be configured to use SSL for communication with the server by setting the properties as shown in this example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - data: - mongodb: - uri: "mongodb://user:secret@mongoserver1.example.com:27017,mongoserver2.example.com:23456/test" - ssl: - enabled: true ----- - -Custom SSL trust material can be configured in an <> and applied to the `MongoClient` as shown in this example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - data: - mongodb: - uri: "mongodb://user:secret@mongoserver1.example.com:27017,mongoserver2.example.com:23456/test" - ssl: - bundle: "example" ----- - - -[TIP] -==== -If `spring.data.mongodb.port` is not specified, the default of `27017` is used. -You could delete this line from the example shown earlier. - -You can also specify the port as part of the host address by using the `host:port` syntax. -This format should be used if you need to change the port of an `additional-hosts` entry. -==== - -TIP: If you do not use Spring Data MongoDB, you can inject a `MongoClient` bean instead of using `MongoDatabaseFactory`. -If you want to take complete control of establishing the MongoDB connection, you can also declare your own `MongoDatabaseFactory` or `MongoClient` bean. - -NOTE: If you are using the reactive driver, Netty is required for SSL. -The auto-configuration configures this factory automatically if Netty is available and the factory to use has not been customized already. - - - -[[data.nosql.mongodb.template]] -==== MongoTemplate -{spring-data-mongodb}[Spring Data MongoDB] provides a {spring-data-mongodb-api}/core/MongoTemplate.html[`MongoTemplate`] class that is very similar in its design to Spring's `JdbcTemplate`. -As with `JdbcTemplate`, Spring Boot auto-configures a bean for you to inject the template, as follows: - -include::code:MyBean[] - -See the {spring-data-mongodb-api}/core/MongoOperations.html[`MongoOperations` Javadoc] for complete details. - - - -[[data.nosql.mongodb.repositories]] -==== Spring Data MongoDB Repositories -Spring Data includes repository support for MongoDB. -As with the JPA repositories discussed earlier, the basic principle is that queries are constructed automatically, based on method names. - -In fact, both Spring Data JPA and Spring Data MongoDB share the same common infrastructure. -You could take the JPA example from earlier and, assuming that `City` is now a MongoDB data class rather than a JPA `@Entity`, it works in the same way, as shown in the following example: - -include::code:CityRepository[] - -Repositories and documents are found through scanning. -By default, the <> are scanned. -You can customize the locations to look for repositories and documents by using `@EnableMongoRepositories` and `@EntityScan` respectively. - -TIP: For complete details of Spring Data MongoDB, including its rich object mapping technologies, see its {spring-data-mongodb}[reference documentation]. - - - -[[data.nosql.neo4j]] -=== Neo4j -https://neo4j.com/[Neo4j] is an open-source NoSQL graph database that uses a rich data model of nodes connected by first class relationships, which is better suited for connected big data than traditional RDBMS approaches. -Spring Boot offers several conveniences for working with Neo4j, including the `spring-boot-starter-data-neo4j` "`Starter`". - - - -[[data.nosql.neo4j.connecting]] -==== Connecting to a Neo4j Database -To access a Neo4j server, you can inject an auto-configured `org.neo4j.driver.Driver`. -By default, the instance tries to connect to a Neo4j server at `localhost:7687` using the Bolt protocol. -The following example shows how to inject a Neo4j `Driver` that gives you access, amongst other things, to a `Session`: - -include::code:MyBean[] - -You can configure various aspects of the driver using `spring.neo4j.*` properties. -The following example shows how to configure the uri and credentials to use: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - neo4j: - uri: "bolt://my-server:7687" - authentication: - username: "neo4j" - password: "secret" ----- - -The auto-configured `Driver` is created using `ConfigBuilder`. -To fine-tune its configuration, declare one or more `ConfigBuilderCustomizer` beans. -Each will be called in order with the `ConfigBuilder` that is used to build the `Driver`. - - - -[[data.nosql.neo4j.repositories]] -==== Spring Data Neo4j Repositories -Spring Data includes repository support for Neo4j. -For complete details of Spring Data Neo4j, see the {spring-data-neo4j-docs}[reference documentation]. - -Spring Data Neo4j shares the common infrastructure with Spring Data JPA as many other Spring Data modules do. -You could take the JPA example from earlier and define `City` as Spring Data Neo4j `@Node` rather than JPA `@Entity` and the repository abstraction works in the same way, as shown in the following example: - -include::code:CityRepository[] - -The `spring-boot-starter-data-neo4j` "`Starter`" enables the repository support as well as transaction management. -Spring Boot supports both classic and reactive Neo4j repositories, using the `Neo4jTemplate` or `ReactiveNeo4jTemplate` beans. -When Project Reactor is available on the classpath, the reactive style is also auto-configured. - -Repositories and entities are found through scanning. -By default, the <> are scanned. -You can customize the locations to look for repositories and entities by using `@EnableNeo4jRepositories` and `@EntityScan` respectively. - -[NOTE] -==== -In an application using the reactive style, a `ReactiveTransactionManager` is not auto-configured. -To enable transaction management, the following bean must be defined in your configuration: - -include::code:MyNeo4jConfiguration[] -==== - - - -[[data.nosql.elasticsearch]] -=== Elasticsearch -https://www.elastic.co/products/elasticsearch[Elasticsearch] is an open source, distributed, RESTful search and analytics engine. -Spring Boot offers basic auto-configuration for Elasticsearch clients. - -Spring Boot supports several clients: - -* The official low-level REST client -* The official Java API client -* The `ReactiveElasticsearchClient` provided by Spring Data Elasticsearch - -Spring Boot provides a dedicated "`Starter`", `spring-boot-starter-data-elasticsearch`. - - - -[[data.nosql.elasticsearch.connecting-using-rest]] -==== Connecting to Elasticsearch Using REST clients -Elasticsearch ships two different REST clients that you can use to query a cluster: the https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/java-rest-low.html[low-level client] from the `org.elasticsearch.client:elasticsearch-rest-client` module and the https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html[Java API client] from the `co.elastic.clients:elasticsearch-java` module. -Additionally, Spring Boot provides support for a reactive client from the `org.springframework.data:spring-data-elasticsearch` module. -By default, the clients will target `http://localhost:9200`. -You can use `spring.elasticsearch.*` properties to further tune how the clients are configured, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - elasticsearch: - uris: "https://search.example.com:9200" - socket-timeout: "10s" - username: "user" - password: "secret" ----- - -[[data.nosql.elasticsearch.connecting-using-rest.restclient]] -===== Connecting to Elasticsearch Using RestClient -If you have `elasticsearch-rest-client` on the classpath, Spring Boot will auto-configure and register a `RestClient` bean. -In addition to the properties described previously, to fine-tune the `RestClient` you can register an arbitrary number of beans that implement `RestClientBuilderCustomizer` for more advanced customizations. -To take full control over the clients' configuration, define a `RestClientBuilder` bean. - - - -Additionally, if `elasticsearch-rest-client-sniffer` is on the classpath, a `Sniffer` is auto-configured to automatically discover nodes from a running Elasticsearch cluster and set them on the `RestClient` bean. -You can further tune how `Sniffer` is configured, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - elasticsearch: - restclient: - sniffer: - interval: "10m" - delay-after-failure: "30s" ----- - - -[[data.nosql.elasticsearch.connecting-using-rest.javaapiclient]] -===== Connecting to Elasticsearch Using ElasticsearchClient -If you have `co.elastic.clients:elasticsearch-java` on the classpath, Spring Boot will auto-configure and register an `ElasticsearchClient` bean. - -The `ElasticsearchClient` uses a transport that depends upon the previously described `RestClient`. -Therefore, the properties described previously can be used to configure the `ElasticsearchClient`. -Furthermore, you can define a `TransportOptions` bean to take further control of the behavior of the transport. - - - -[[data.nosql.elasticsearch.connecting-using-rest.reactiveclient]] -===== Connecting to Elasticsearch using ReactiveElasticsearchClient -{spring-data-elasticsearch}[Spring Data Elasticsearch] ships `ReactiveElasticsearchClient` for querying Elasticsearch instances in a reactive fashion. -If you have Spring Data Elasticsearch and Reactor on the classpath, Spring Boot will auto-configure and register a `ReactiveElasticsearchClient`. - -The `ReactiveElasticsearchclient` uses a transport that depends upon the previously described `RestClient`. -Therefore, the properties described previously can be used to configure the `ReactiveElasticsearchClient`. -Furthermore, you can define a `TransportOptions` bean to take further control of the behavior of the transport. - - - -[[data.nosql.elasticsearch.connecting-using-spring-data]] -==== Connecting to Elasticsearch by Using Spring Data -To connect to Elasticsearch, an `ElasticsearchClient` bean must be defined, -auto-configured by Spring Boot or manually provided by the application (see previous sections). -With this configuration in place, an -`ElasticsearchTemplate` can be injected like any other Spring bean, -as shown in the following example: - -include::code:MyBean[] - -In the presence of `spring-data-elasticsearch` and Reactor, Spring Boot can also auto-configure a <> and a `ReactiveElasticsearchTemplate` as beans. -They are the reactive equivalent of the other REST clients. - - - -[[data.nosql.elasticsearch.repositories]] -==== Spring Data Elasticsearch Repositories -Spring Data includes repository support for Elasticsearch. -As with the JPA repositories discussed earlier, the basic principle is that queries are constructed for you automatically based on method names. - -In fact, both Spring Data JPA and Spring Data Elasticsearch share the same common infrastructure. -You could take the JPA example from earlier and, assuming that `City` is now an Elasticsearch `@Document` class rather than a JPA `@Entity`, it works in the same way. - -Repositories and documents are found through scanning. -By default, the <> are scanned. -You can customize the locations to look for repositories and documents by using `@EnableElasticsearchRepositories` and `@EntityScan` respectively. - -TIP: For complete details of Spring Data Elasticsearch, see the {spring-data-elasticsearch-docs}[reference documentation]. - -Spring Boot supports both classic and reactive Elasticsearch repositories, using the `ElasticsearchRestTemplate` or `ReactiveElasticsearchTemplate` beans. -Most likely those beans are auto-configured by Spring Boot given the required dependencies are present. - -If you wish to use your own template for backing the Elasticsearch repositories, you can add your own `ElasticsearchRestTemplate` or `ElasticsearchOperations` `@Bean`, as long as it is named `"elasticsearchTemplate"`. -Same applies to `ReactiveElasticsearchTemplate` and `ReactiveElasticsearchOperations`, with the bean name `"reactiveElasticsearchTemplate"`. - -You can choose to disable the repositories support with the following property: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - data: - elasticsearch: - repositories: - enabled: false ----- - - - -[[data.nosql.cassandra]] -=== Cassandra -https://cassandra.apache.org/[Cassandra] is an open source, distributed database management system designed to handle large amounts of data across many commodity servers. -Spring Boot offers auto-configuration for Cassandra and the abstractions on top of it provided by https://github.com/spring-projects/spring-data-cassandra[Spring Data Cassandra]. -There is a `spring-boot-starter-data-cassandra` "`Starter`" for collecting the dependencies in a convenient way. - - - -[[data.nosql.cassandra.connecting]] -==== Connecting to Cassandra -You can inject an auto-configured `CassandraTemplate` or a Cassandra `CqlSession` instance as you would with any other Spring Bean. -The `spring.cassandra.*` properties can be used to customize the connection. -Generally, you provide `keyspace-name` and `contact-points` as well the local datacenter name, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - cassandra: - keyspace-name: "mykeyspace" - contact-points: "cassandrahost1:9042,cassandrahost2:9042" - local-datacenter: "datacenter1" ----- - -If the port is the same for all your contact points you can use a shortcut and only specify the host names, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - cassandra: - keyspace-name: "mykeyspace" - contact-points: "cassandrahost1,cassandrahost2" - local-datacenter: "datacenter1" ----- - -TIP: Those two examples are identical as the port default to `9042`. -If you need to configure the port, use `spring.cassandra.port`. - -The auto-configured `CqlSession` can be configured to use SSL for communication with the server by setting the properties as shown in this example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - cassandra: - keyspace-name: "mykeyspace" - contact-points: "cassandrahost1,cassandrahost2" - local-datacenter: "datacenter1" - ssl: - enabled: true ----- - -Custom SSL trust material can be configured in an <> and applied to the `CqlSession` as shown in this example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - cassandra: - keyspace-name: "mykeyspace" - contact-points: "cassandrahost1,cassandrahost2" - local-datacenter: "datacenter1" - ssl: - bundle: "example" ----- - - -[NOTE] -==== -The Cassandra driver has its own configuration infrastructure that loads an `application.conf` at the root of the classpath. - -Spring Boot does not look for such a file by default but can load one using `spring.cassandra.config`. -If a property is both present in `+spring.cassandra.*+` and the configuration file, the value in `+spring.cassandra.*+` takes precedence. - -For more advanced driver customizations, you can register an arbitrary number of beans that implement `DriverConfigLoaderBuilderCustomizer`. -The `CqlSession` can be customized with a bean of type `CqlSessionBuilderCustomizer`. -==== - -NOTE: If you use `CqlSessionBuilder` to create multiple `CqlSession` beans, keep in mind the builder is mutable so make sure to inject a fresh copy for each session. - -The following code listing shows how to inject a Cassandra bean: - -include::code:MyBean[] - -If you add your own `@Bean` of type `CassandraTemplate`, it replaces the default. - - - -[[data.nosql.cassandra.repositories]] -==== Spring Data Cassandra Repositories -Spring Data includes basic repository support for Cassandra. -Currently, this is more limited than the JPA repositories discussed earlier and needs `@Query` annotated finder methods. - -Repositories and entities are found through scanning. -By default, the <> are scanned. -You can customize the locations to look for repositories and entities by using `@EnableCassandraRepositories` and `@EntityScan` respectively. - -TIP: For complete details of Spring Data Cassandra, see the https://docs.spring.io/spring-data/cassandra/docs/[reference documentation]. - - - -[[data.nosql.couchbase]] -=== Couchbase -https://www.couchbase.com/[Couchbase] is an open-source, distributed, multi-model NoSQL document-oriented database that is optimized for interactive applications. -Spring Boot offers auto-configuration for Couchbase and the abstractions on top of it provided by https://github.com/spring-projects/spring-data-couchbase[Spring Data Couchbase]. -There are `spring-boot-starter-data-couchbase` and `spring-boot-starter-data-couchbase-reactive` "`Starters`" for collecting the dependencies in a convenient way. - - - -[[data.nosql.couchbase.connecting]] -==== Connecting to Couchbase -You can get a `Cluster` by adding the Couchbase SDK and some configuration. -The `spring.couchbase.*` properties can be used to customize the connection. -Generally, you provide the https://github.com/couchbaselabs/sdk-rfcs/blob/master/rfc/0011-connection-string.md[connection string], username, and password, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - couchbase: - connection-string: "couchbase://192.168.1.123" - username: "user" - password: "secret" ----- - -It is also possible to customize some of the `ClusterEnvironment` settings. -For instance, the following configuration changes the timeout to open a new `Bucket` and enables SSL support with a reference to a configured <>: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - couchbase: - env: - timeouts: - connect: "3s" - ssl: - bundle: "example" ----- - -TIP: Check the `spring.couchbase.env.*` properties for more details. -To take more control, one or more `ClusterEnvironmentBuilderCustomizer` beans can be used. - - - -[[data.nosql.couchbase.repositories]] -==== Spring Data Couchbase Repositories -Spring Data includes repository support for Couchbase. - -Repositories and documents are found through scanning. -By default, the <> are scanned. -You can customize the locations to look for repositories and documents by using `@EnableCouchbaseRepositories` and `@EntityScan` respectively. - -For complete details of Spring Data Couchbase, see the {spring-data-couchbase-docs}[reference documentation]. - -You can inject an auto-configured `CouchbaseTemplate` instance as you would with any other Spring Bean, provided a `CouchbaseClientFactory` bean is available. -This happens when a `Cluster` is available, as described above, and a bucket name has been specified: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - data: - couchbase: - bucket-name: "my-bucket" ----- - -The following examples shows how to inject a `CouchbaseTemplate` bean: - -include::code:MyBean[] - -There are a few beans that you can define in your own configuration to override those provided by the auto-configuration: - -* A `CouchbaseMappingContext` `@Bean` with a name of `couchbaseMappingContext`. -* A `CustomConversions` `@Bean` with a name of `couchbaseCustomConversions`. -* A `CouchbaseTemplate` `@Bean` with a name of `couchbaseTemplate`. - -To avoid hard-coding those names in your own config, you can reuse `BeanNames` provided by Spring Data Couchbase. -For instance, you can customize the converters to use, as follows: - -include::code:MyCouchbaseConfiguration[] - - - -[[data.nosql.ldap]] -=== LDAP -https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol[LDAP] (Lightweight Directory Access Protocol) is an open, vendor-neutral, industry standard application protocol for accessing and maintaining distributed directory information services over an IP network. -Spring Boot offers auto-configuration for any compliant LDAP server as well as support for the embedded in-memory LDAP server from https://ldap.com/unboundid-ldap-sdk-for-java/[UnboundID]. - -LDAP abstractions are provided by https://github.com/spring-projects/spring-data-ldap[Spring Data LDAP]. -There is a `spring-boot-starter-data-ldap` "`Starter`" for collecting the dependencies in a convenient way. - - - -[[data.nosql.ldap.connecting]] -==== Connecting to an LDAP Server -To connect to an LDAP server, make sure you declare a dependency on the `spring-boot-starter-data-ldap` "`Starter`" or `spring-ldap-core` and then declare the URLs of your server in your application.properties, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - ldap: - urls: "ldap://myserver:1235" - username: "admin" - password: "secret" ----- - -If you need to customize connection settings, you can use the `spring.ldap.base` and `spring.ldap.base-environment` properties. - -An `LdapContextSource` is auto-configured based on these settings. -If a `DirContextAuthenticationStrategy` bean is available, it is associated to the auto-configured `LdapContextSource`. -If you need to customize it, for instance to use a `PooledContextSource`, you can still inject the auto-configured `LdapContextSource`. -Make sure to flag your customized `ContextSource` as `@Primary` so that the auto-configured `LdapTemplate` uses it. - - - -[[data.nosql.ldap.repositories]] -==== Spring Data LDAP Repositories -Spring Data includes repository support for LDAP. - -Repositories and documents are found through scanning. -By default, the <> are scanned. -You can customize the locations to look for repositories and documents by using `@EnableLdapRepositories` and `@EntityScan` respectively. - -For complete details of Spring Data LDAP, see the https://docs.spring.io/spring-data/ldap/docs/1.0.x/reference/html/[reference documentation]. - -You can also inject an auto-configured `LdapTemplate` instance as you would with any other Spring Bean, as shown in the following example: - - -include::code:MyBean[] - - - -[[data.nosql.ldap.embedded]] -==== Embedded In-memory LDAP Server -For testing purposes, Spring Boot supports auto-configuration of an in-memory LDAP server from https://ldap.com/unboundid-ldap-sdk-for-java/[UnboundID]. -To configure the server, add a dependency to `com.unboundid:unboundid-ldapsdk` and declare a configprop:spring.ldap.embedded.base-dn[] property, as follows: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - ldap: - embedded: - base-dn: "dc=spring,dc=io" ----- - -[NOTE] -==== -It is possible to define multiple base-dn values, however, since distinguished names usually contain commas, they must be defined using the correct notation. - -In yaml files, you can use the yaml list notation. In properties files, you must include the index as part of the property name: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring.ldap.embedded.base-dn: - - "dc=spring,dc=io" - - "dc=vmware,dc=com" ----- -==== - -By default, the server starts on a random port and triggers the regular LDAP support. -There is no need to specify a configprop:spring.ldap.urls[] property. - -If there is a `schema.ldif` file on your classpath, it is used to initialize the server. -If you want to load the initialization script from a different resource, you can also use the configprop:spring.ldap.embedded.ldif[] property. - -By default, a standard schema is used to validate `LDIF` files. -You can turn off validation altogether by setting the configprop:spring.ldap.embedded.validation.enabled[] property. -If you have custom attributes, you can use configprop:spring.ldap.embedded.validation.schema[] to define your custom attribute types or object classes. - - - -[[data.nosql.influxdb]] -=== InfluxDB -https://www.influxdata.com/[InfluxDB] is an open-source time series database optimized for fast, high-availability storage and retrieval of time series data in fields such as operations monitoring, application metrics, Internet-of-Things sensor data, and real-time analytics. - - - -[[data.nosql.influxdb.connecting]] -==== Connecting to InfluxDB -Spring Boot auto-configures an `InfluxDB` instance, provided the `influxdb-java` client is on the classpath and the URL of the database is set, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - influx: - url: "https://172.0.0.1:8086" ----- - -If the connection to InfluxDB requires a user and password, you can set the `spring.influx.user` and `spring.influx.password` properties accordingly. - -InfluxDB relies on OkHttp. -If you need to tune the http client `InfluxDB` uses behind the scenes, you can register an `InfluxDbOkHttpClientBuilderProvider` bean. - -If you need more control over the configuration, consider registering an `InfluxDbCustomizer` bean. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/sql.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/sql.adoc deleted file mode 100644 index a460857d2ab7..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/sql.adoc +++ /dev/null @@ -1,514 +0,0 @@ -[[data.sql]] -== SQL Databases -The {spring-framework}[Spring Framework] provides extensive support for working with SQL databases, from direct JDBC access using `JdbcTemplate` to complete "`object relational mapping`" technologies such as Hibernate. -{spring-data}[Spring Data] provides an additional level of functionality: creating `Repository` implementations directly from interfaces and using conventions to generate queries from your method names. - - - -[[data.sql.datasource]] -=== Configure a DataSource -Java's `javax.sql.DataSource` interface provides a standard method of working with database connections. -Traditionally, a `DataSource` uses a `URL` along with some credentials to establish a database connection. - -TIP: See <> for more advanced examples, typically to take full control over the configuration of the DataSource. - - - -[[data.sql.datasource.embedded]] -==== Embedded Database Support -It is often convenient to develop applications by using an in-memory embedded database. -Obviously, in-memory databases do not provide persistent storage. -You need to populate your database when your application starts and be prepared to throw away data when your application ends. - -TIP: The "`How-to`" section includes a <>. - -Spring Boot can auto-configure embedded https://www.h2database.com[H2], https://hsqldb.org/[HSQL], and https://db.apache.org/derby/[Derby] databases. -You need not provide any connection URLs. -You need only include a build dependency to the embedded database that you want to use. -If there are multiple embedded databases on the classpath, set the configprop:spring.datasource.embedded-database-connection[] configuration property to control which one is used. -Setting the property to `none` disables auto-configuration of an embedded database. - -[NOTE] -==== -If you are using this feature in your tests, you may notice that the same database is reused by your whole test suite regardless of the number of application contexts that you use. -If you want to make sure that each context has a separate embedded database, you should set `spring.datasource.generate-unique-name` to `true`. -==== - -For example, the typical POM dependencies would be as follows: - -[source,xml,indent=0,subs="verbatim"] ----- - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.hsqldb - hsqldb - runtime - ----- - -NOTE: You need a dependency on `spring-jdbc` for an embedded database to be auto-configured. -In this example, it is pulled in transitively through `spring-boot-starter-data-jpa`. - -TIP: If, for whatever reason, you do configure the connection URL for an embedded database, take care to ensure that the database's automatic shutdown is disabled. -If you use H2, you should use `DB_CLOSE_ON_EXIT=FALSE` to do so. -If you use HSQLDB, you should ensure that `shutdown=true` is not used. -Disabling the database's automatic shutdown lets Spring Boot control when the database is closed, thereby ensuring that it happens once access to the database is no longer needed. - - - -[[data.sql.datasource.production]] -==== Connection to a Production Database -Production database connections can also be auto-configured by using a pooling `DataSource`. - - - -[[data.sql.datasource.configuration]] -==== DataSource Configuration -DataSource configuration is controlled by external configuration properties in `+spring.datasource.*+`. -For example, you might declare the following section in `application.properties`: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - datasource: - url: "jdbc:mysql://localhost/test" - username: "dbuser" - password: "dbpass" ----- - -NOTE: You should at least specify the URL by setting the configprop:spring.datasource.url[] property. -Otherwise, Spring Boot tries to auto-configure an embedded database. - -TIP: Spring Boot can deduce the JDBC driver class for most databases from the URL. -If you need to specify a specific class, you can use the configprop:spring.datasource.driver-class-name[] property. - -NOTE: For a pooling `DataSource` to be created, we need to be able to verify that a valid `Driver` class is available, so we check for that before doing anything. -In other words, if you set `spring.datasource.driver-class-name=com.mysql.jdbc.Driver`, then that class has to be loadable. - -See {spring-boot-autoconfigure-module-code}/jdbc/DataSourceProperties.java[`DataSourceProperties`] for more of the supported options. -These are the standard options that work regardless of <>. -It is also possible to fine-tune implementation-specific settings by using their respective prefix (`+spring.datasource.hikari.*+`, `+spring.datasource.tomcat.*+`, `+spring.datasource.dbcp2.*+`, and `+spring.datasource.oracleucp.*+`). -See the documentation of the connection pool implementation you are using for more details. - -For instance, if you use the {tomcat-docs}/jdbc-pool.html#Common_Attributes[Tomcat connection pool], you could customize many additional settings, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - datasource: - tomcat: - max-wait: 10000 - max-active: 50 - test-on-borrow: true ----- - -This will set the pool to wait 10000ms before throwing an exception if no connection is available, limit the maximum number of connections to 50 and validate the connection before borrowing it from the pool. - - - -[[data.sql.datasource.connection-pool]] -==== Supported Connection Pools -Spring Boot uses the following algorithm for choosing a specific implementation: - -. We prefer https://github.com/brettwooldridge/HikariCP[HikariCP] for its performance and concurrency. -If HikariCP is available, we always choose it. -. Otherwise, if the Tomcat pooling `DataSource` is available, we use it. -. Otherwise, if https://commons.apache.org/proper/commons-dbcp/[Commons DBCP2] is available, we use it. -. If none of HikariCP, Tomcat, and DBCP2 are available and if Oracle UCP is available, we use it. - -NOTE: If you use the `spring-boot-starter-jdbc` or `spring-boot-starter-data-jpa` "`starters`", you automatically get a dependency to `HikariCP`. - -You can bypass that algorithm completely and specify the connection pool to use by setting the configprop:spring.datasource.type[] property. -This is especially important if you run your application in a Tomcat container, as `tomcat-jdbc` is provided by default. - -Additional connection pools can always be configured manually, using `DataSourceBuilder`. -If you define your own `DataSource` bean, auto-configuration does not occur. -The following connection pools are supported by `DataSourceBuilder`: - -* HikariCP -* Tomcat pooling `Datasource` -* Commons DBCP2 -* Oracle UCP & `OracleDataSource` -* Spring Framework's `SimpleDriverDataSource` -* H2 `JdbcDataSource` -* PostgreSQL `PGSimpleDataSource` -* C3P0 - - - -[[data.sql.datasource.jndi]] -==== Connection to a JNDI DataSource -If you deploy your Spring Boot application to an Application Server, you might want to configure and manage your DataSource by using your Application Server's built-in features and access it by using JNDI. - -The configprop:spring.datasource.jndi-name[] property can be used as an alternative to the configprop:spring.datasource.url[], configprop:spring.datasource.username[], and configprop:spring.datasource.password[] properties to access the `DataSource` from a specific JNDI location. -For example, the following section in `application.properties` shows how you can access a JBoss AS defined `DataSource`: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - datasource: - jndi-name: "java:jboss/datasources/customers" ----- - - - -[[data.sql.jdbc-template]] -=== Using JdbcTemplate -Spring's `JdbcTemplate` and `NamedParameterJdbcTemplate` classes are auto-configured, and you can `@Autowire` them directly into your own beans, as shown in the following example: - -include::code:MyBean[] - -You can customize some properties of the template by using the `spring.jdbc.template.*` properties, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - jdbc: - template: - max-rows: 500 ----- - -NOTE: The `NamedParameterJdbcTemplate` reuses the same `JdbcTemplate` instance behind the scenes. -If more than one `JdbcTemplate` is defined and no primary candidate exists, the `NamedParameterJdbcTemplate` is not auto-configured. - - - -[[data.sql.jpa-and-spring-data]] -=== JPA and Spring Data JPA -The Java Persistence API is a standard technology that lets you "`map`" objects to relational databases. -The `spring-boot-starter-data-jpa` POM provides a quick way to get started. -It provides the following key dependencies: - -* Hibernate: One of the most popular JPA implementations. -* Spring Data JPA: Helps you to implement JPA-based repositories. -* Spring ORM: Core ORM support from the Spring Framework. - -TIP: We do not go into too many details of JPA or {spring-data}[Spring Data] here. -You can follow the https://spring.io/guides/gs/accessing-data-jpa/["`Accessing Data with JPA`"] guide from https://spring.io and read the {spring-data-jpa}[Spring Data JPA] and https://hibernate.org/orm/documentation/[Hibernate] reference documentation. - - - -[[data.sql.jpa-and-spring-data.entity-classes]] -==== Entity Classes -Traditionally, JPA "`Entity`" classes are specified in a `persistence.xml` file. -With Spring Boot, this file is not necessary and "`Entity Scanning`" is used instead. -By default the <> are scanned. - -Any classes annotated with `@Entity`, `@Embeddable`, or `@MappedSuperclass` are considered. -A typical entity class resembles the following example: - -include::code:City[] - -TIP: You can customize entity scanning locations by using the `@EntityScan` annotation. -See the "`<>`" how-to. - - - -[[data.sql.jpa-and-spring-data.repositories]] -==== Spring Data JPA Repositories -{spring-data-jpa}[Spring Data JPA] repositories are interfaces that you can define to access data. -JPA queries are created automatically from your method names. -For example, a `CityRepository` interface might declare a `findAllByState(String state)` method to find all the cities in a given state. - -For more complex queries, you can annotate your method with Spring Data's {spring-data-jpa-api}/repository/Query.html[`Query`] annotation. - -Spring Data repositories usually extend from the {spring-data-commons-api}/repository/Repository.html[`Repository`] or {spring-data-commons-api}/repository/CrudRepository.html[`CrudRepository`] interfaces. -If you use auto-configuration, the <> are searched for repositories. - -TIP: You can customize the locations to look for repositories using `@EnableJpaRepositories`. - -The following example shows a typical Spring Data repository interface definition: - -include::code:CityRepository[] - -Spring Data JPA repositories support three different modes of bootstrapping: default, deferred, and lazy. -To enable deferred or lazy bootstrapping, set the configprop:spring.data.jpa.repositories.bootstrap-mode[] property to `deferred` or `lazy` respectively. -When using deferred or lazy bootstrapping, the auto-configured `EntityManagerFactoryBuilder` will use the context's `AsyncTaskExecutor`, if any, as the bootstrap executor. -If more than one exists, the one named `applicationTaskExecutor` will be used. - -[NOTE] -==== -When using deferred or lazy bootstrapping, make sure to defer any access to the JPA infrastructure after the application context bootstrap phase. -You can use `SmartInitializingSingleton` to invoke any initialization that requires the JPA infrastructure. -For JPA components (such as converters) that are created as Spring beans, use `ObjectProvider` to delay the resolution of dependencies, if any. -==== - -TIP: We have barely scratched the surface of Spring Data JPA. -For complete details, see the {spring-data-jpa-docs}[Spring Data JPA reference documentation]. - - - -[[data.sql.jpa-and-spring-data.envers-repositories]] -==== Spring Data Envers Repositories -If {spring-data-envers}[Spring Data Envers] is available, JPA repositories are auto-configured to support typical Envers queries. - -To use Spring Data Envers, make sure your repository extends from `RevisionRepository` as shown in the following example: - -include::code:CountryRepository[] - -NOTE: For more details, check the {spring-data-jpa-docs}/#envers[Spring Data Envers reference documentation]. - - - -[[data.sql.jpa-and-spring-data.creating-and-dropping]] -==== Creating and Dropping JPA Databases -By default, JPA databases are automatically created *only* if you use an embedded database (H2, HSQL, or Derby). -You can explicitly configure JPA settings by using `+spring.jpa.*+` properties. -For example, to create and drop tables you can add the following line to your `application.properties`: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - jpa: - hibernate.ddl-auto: "create-drop" ----- - -NOTE: Hibernate's own internal property name for this (if you happen to remember it better) is `hibernate.hbm2ddl.auto`. -You can set it, along with other Hibernate native properties, by using `+spring.jpa.properties.*+` (the prefix is stripped before adding them to the entity manager). -The following line shows an example of setting JPA properties for Hibernate: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - jpa: - properties: - hibernate: - "globally_quoted_identifiers": "true" ----- - -The line in the preceding example passes a value of `true` for the `hibernate.globally_quoted_identifiers` property to the Hibernate entity manager. - -By default, the DDL execution (or validation) is deferred until the `ApplicationContext` has started. - - - -[[data.sql.jpa-and-spring-data.open-entity-manager-in-view]] -==== Open EntityManager in View -If you are running a web application, Spring Boot by default registers {spring-framework-api}/orm/jpa/support/OpenEntityManagerInViewInterceptor.html[`OpenEntityManagerInViewInterceptor`] to apply the "`Open EntityManager in View`" pattern, to allow for lazy loading in web views. -If you do not want this behavior, you should set `spring.jpa.open-in-view` to `false` in your `application.properties`. - - - -[[data.sql.jdbc]] -=== Spring Data JDBC -Spring Data includes repository support for JDBC and will automatically generate SQL for the methods on `CrudRepository`. -For more advanced queries, a `@Query` annotation is provided. - -Spring Boot will auto-configure Spring Data's JDBC repositories when the necessary dependencies are on the classpath. -They can be added to your project with a single dependency on `spring-boot-starter-data-jdbc`. -If necessary, you can take control of Spring Data JDBC's configuration by adding the `@EnableJdbcRepositories` annotation or an `AbstractJdbcConfiguration` subclass to your application. - -TIP: For complete details of Spring Data JDBC, see the {spring-data-jdbc-docs}[reference documentation]. - - - -[[data.sql.h2-web-console]] -=== Using H2's Web Console -The https://www.h2database.com[H2 database] provides a https://www.h2database.com/html/quickstart.html#h2_console[browser-based console] that Spring Boot can auto-configure for you. -The console is auto-configured when the following conditions are met: - -* You are developing a servlet-based web application. -* `com.h2database:h2` is on the classpath. -* You are using <>. - -TIP: If you are not using Spring Boot's developer tools but would still like to make use of H2's console, you can configure the configprop:spring.h2.console.enabled[] property with a value of `true`. - -NOTE: The H2 console is only intended for use during development, so you should take care to ensure that `spring.h2.console.enabled` is not set to `true` in production. - - - -[[data.sql.h2-web-console.custom-path]] -==== Changing the H2 Console's Path -By default, the console is available at `/h2-console`. -You can customize the console's path by using the configprop:spring.h2.console.path[] property. - - - -[[data.sql.h2-web-console.spring-security]] -==== Accessing the H2 Console in a Secured Application -H2 Console uses frames and, as it is intended for development only, does not implement CSRF protection measures. -If your application uses Spring Security, you need to configure it to - -* disable CSRF protection for requests against the console, -* set the header `X-Frame-Options` to `SAMEORIGIN` on responses from the console. - -More information on {spring-security-docs}/features/exploits/csrf.html[CSRF] and the header {spring-security-docs}/features/exploits/headers.html#headers-frame-options[X-Frame-Options] can be found in the Spring Security Reference Guide. - -In simple setups, a `SecurityFilterChain` like the following can be used: - -include::code:DevProfileSecurityConfiguration[tag=!customizer] - -WARNING: The H2 console is only intended for use during development. -In production, disabling CSRF protection or allowing frames for a website may create severe security risks. - -TIP: `PathRequest.toH2Console()` returns the correct request matcher also when the console's path has been customized. - - - -[[data.sql.jooq]] -=== Using jOOQ -jOOQ Object Oriented Querying (https://www.jooq.org/[jOOQ]) is a popular product from https://www.datageekery.com/[Data Geekery] which generates Java code from your database and lets you build type-safe SQL queries through its fluent API. -Both the commercial and open source editions can be used with Spring Boot. - - - -[[data.sql.jooq.codegen]] -==== Code Generation -In order to use jOOQ type-safe queries, you need to generate Java classes from your database schema. -You can follow the instructions in the {jooq-docs}/#jooq-in-7-steps-step3[jOOQ user manual]. -If you use the `jooq-codegen-maven` plugin and you also use the `spring-boot-starter-parent` "`parent POM`", you can safely omit the plugin's `` tag. -You can also use Spring Boot-defined version variables (such as `h2.version`) to declare the plugin's database dependency. -The following listing shows an example: - -[source,xml,indent=0,subs="verbatim"] ----- - - org.jooq - jooq-codegen-maven - - ... - - - - com.h2database - h2 - ${h2.version} - - - - - org.h2.Driver - jdbc:h2:~/yourdatabase - - - ... - - - ----- - - - -[[data.sql.jooq.dslcontext]] -==== Using DSLContext -The fluent API offered by jOOQ is initiated through the `org.jooq.DSLContext` interface. -Spring Boot auto-configures a `DSLContext` as a Spring Bean and connects it to your application `DataSource`. -To use the `DSLContext`, you can inject it, as shown in the following example: - -include::code:MyBean[tag=!method] - -TIP: The jOOQ manual tends to use a variable named `create` to hold the `DSLContext`. - -You can then use the `DSLContext` to construct your queries, as shown in the following example: - -include::code:MyBean[tag=method] - - - -[[data.sql.jooq.sqldialect]] -==== jOOQ SQL Dialect -Unless the configprop:spring.jooq.sql-dialect[] property has been configured, Spring Boot determines the SQL dialect to use for your datasource. -If Spring Boot could not detect the dialect, it uses `DEFAULT`. - -NOTE: Spring Boot can only auto-configure dialects supported by the open source version of jOOQ. - - - -[[data.sql.jooq.customizing]] -==== Customizing jOOQ -More advanced customizations can be achieved by defining your own `DefaultConfigurationCustomizer` bean that will be invoked prior to creating the `org.jooq.Configuration` `@Bean`. -This takes precedence to anything that is applied by the auto-configuration. - -You can also create your own `org.jooq.Configuration` `@Bean` if you want to take complete control of the jOOQ configuration. - - - -[[data.sql.r2dbc]] -=== Using R2DBC -The Reactive Relational Database Connectivity (https://r2dbc.io[R2DBC]) project brings reactive programming APIs to relational databases. -R2DBC's `io.r2dbc.spi.Connection` provides a standard method of working with non-blocking database connections. -Connections are provided by using a `ConnectionFactory`, similar to a `DataSource` with jdbc. - -`ConnectionFactory` configuration is controlled by external configuration properties in `+spring.r2dbc.*+`. -For example, you might declare the following section in `application.properties`: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - r2dbc: - url: "r2dbc:postgresql://localhost/test" - username: "dbuser" - password: "dbpass" ----- - -TIP: You do not need to specify a driver class name, since Spring Boot obtains the driver from R2DBC's Connection Factory discovery. - -NOTE: At least the url should be provided. -Information specified in the URL takes precedence over individual properties, that is `name`, `username`, `password` and pooling options. - -TIP: The "`How-to`" section includes a <>. - -To customize the connections created by a `ConnectionFactory`, that is, set specific parameters that you do not want (or cannot) configure in your central database configuration, you can use a `ConnectionFactoryOptionsBuilderCustomizer` `@Bean`. -The following example shows how to manually override the database port while the rest of the options are taken from the application configuration: - -include::code:MyR2dbcConfiguration[] - -The following examples show how to set some PostgreSQL connection options: - -include::code:MyPostgresR2dbcConfiguration[] - -When a `ConnectionFactory` bean is available, the regular JDBC `DataSource` auto-configuration backs off. -If you want to retain the JDBC `DataSource` auto-configuration, and are comfortable with the risk of using the blocking JDBC API in a reactive application, add `@Import(DataSourceAutoConfiguration.class)` on a `@Configuration` class in your application to re-enable it. - - - -[[data.sql.r2dbc.embedded]] -==== Embedded Database Support -Similarly to <>, Spring Boot can automatically configure an embedded database for reactive usage. -You need not provide any connection URLs. -You need only include a build dependency to the embedded database that you want to use, as shown in the following example: - -[source,xml,indent=0,subs="verbatim"] ----- - - io.r2dbc - r2dbc-h2 - runtime - ----- - -[NOTE] -==== -If you are using this feature in your tests, you may notice that the same database is reused by your whole test suite regardless of the number of application contexts that you use. -If you want to make sure that each context has a separate embedded database, you should set `spring.r2dbc.generate-unique-name` to `true`. -==== - - - -[[data.sql.r2dbc.using-database-client]] -==== Using DatabaseClient -A `DatabaseClient` bean is auto-configured, and you can `@Autowire` it directly into your own beans, as shown in the following example: - -include::code:MyBean[] - - - -[[data.sql.r2dbc.repositories]] -==== Spring Data R2DBC Repositories -https://spring.io/projects/spring-data-r2dbc[Spring Data R2DBC] repositories are interfaces that you can define to access data. -Queries are created automatically from your method names. -For example, a `CityRepository` interface might declare a `findAllByState(String state)` method to find all the cities in a given state. - -For more complex queries, you can annotate your method with Spring Data's {spring-data-r2dbc-api}/repository/Query.html[`Query`] annotation. - -Spring Data repositories usually extend from the {spring-data-commons-api}/repository/Repository.html[`Repository`] or {spring-data-commons-api}/repository/CrudRepository.html[`CrudRepository`] interfaces. -If you use auto-configuration, the <> are searched for repositories. - -The following example shows a typical Spring Data repository interface definition: - -include::code:CityRepository[] - -TIP: We have barely scratched the surface of Spring Data R2DBC. For complete details, see the {spring-data-r2dbc-docs}[Spring Data R2DBC reference documentation]. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/whats-next.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/whats-next.adoc deleted file mode 100644 index 70791bdeb794..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/whats-next.adoc +++ /dev/null @@ -1,4 +0,0 @@ -[[data.whats-next]] -== What to Read Next -You should now have a feeling for how to use Spring Boot with various data technologies. -From here, you can read about Spring Boot's support for various <> and how to enable them in your application. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/dependency-versions.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/dependency-versions.adoc deleted file mode 100644 index c2ca180dea02..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/dependency-versions.adoc +++ /dev/null @@ -1,14 +0,0 @@ -[appendix] -[[appendix.dependency-versions]] -= Dependency Versions -include::attributes.adoc[] - - - -This appendix provides details of the dependencies that are managed by Spring Boot. - - - -include::dependency-versions/coordinates.adoc[] - -include::dependency-versions/properties.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/dependency-versions/properties.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/dependency-versions/properties.adoc deleted file mode 100644 index 5d8932917647..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/dependency-versions/properties.adoc +++ /dev/null @@ -1,8 +0,0 @@ -[[appendix.dependency-versions.properties]] -== Version Properties - -The following table provides all properties that can be used to override the versions managed by Spring Boot. -Browse the {spring-boot-code}/spring-boot-project/spring-boot-dependencies/build.gradle[`spring-boot-dependencies` build.gradle] for a complete list of dependencies. -You can learn how to customize these versions in your application in the <>. - -include::documented-properties.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment.adoc deleted file mode 100644 index ecc8190c00a7..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment.adoc +++ /dev/null @@ -1,19 +0,0 @@ -[[deployment]] -= Deploying Spring Boot Applications -include::attributes.adoc[] - - - -Spring Boot's flexible packaging options provide a great deal of choice when it comes to deploying your application. -You can deploy Spring Boot applications to a variety of cloud platforms, to virtual/real machines, or make them fully executable for Unix systems. - -This section covers some of the more common deployment scenarios. - - -include::deployment/cloud.adoc[] - -include::deployment/installing.adoc[] - -include::deployment/efficient.adoc[] - -include::deployment/whats-next.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/cloud.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/cloud.adoc deleted file mode 100644 index 7d175ddf5427..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/cloud.adoc +++ /dev/null @@ -1,408 +0,0 @@ -[[deployment.cloud]] -== Deploying to the Cloud -Spring Boot's executable jars are ready-made for most popular cloud PaaS (Platform-as-a-Service) providers. -These providers tend to require that you "`bring your own container`". -They manage application processes (not Java applications specifically), so they need an intermediary layer that adapts _your_ application to the _cloud's_ notion of a running process. - -Two popular cloud providers, Heroku and Cloud Foundry, employ a "`buildpack`" approach. -The buildpack wraps your deployed code in whatever is needed to _start_ your application. -It might be a JDK and a call to `java`, an embedded web server, or a full-fledged application server. -A buildpack is pluggable, but ideally you should be able to get by with as few customizations to it as possible. -This reduces the footprint of functionality that is not under your control. -It minimizes divergence between development and production environments. - -Ideally, your application, like a Spring Boot executable jar, has everything that it needs to run packaged within it. - -In this section, we look at what it takes to get the <> in the "`Getting Started`" section up and running in the Cloud. - - - -[[deployment.cloud.cloud-foundry]] -=== Cloud Foundry -Cloud Foundry provides default buildpacks that come into play if no other buildpack is specified. -The Cloud Foundry https://github.com/cloudfoundry/java-buildpack[Java buildpack] has excellent support for Spring applications, including Spring Boot. -You can deploy stand-alone executable jar applications as well as traditional `.war` packaged applications. - -Once you have built your application (by using, for example, `mvn clean package`) and have https://docs.cloudfoundry.org/cf-cli/install-go-cli.html[installed the `cf` command line tool], deploy your application by using the `cf push` command, substituting the path to your compiled `.jar`. -Be sure to have https://docs.cloudfoundry.org/cf-cli/getting-started.html#login[logged in with your `cf` command line client] before pushing an application. -The following line shows using the `cf push` command to deploy an application: - -[source,shell,indent=0,subs="verbatim"] ----- - $ cf push acloudyspringtime -p target/demo-0.0.1-SNAPSHOT.jar ----- - -NOTE: In the preceding example, we substitute `acloudyspringtime` for whatever value you give `cf` as the name of your application. - -See the https://docs.cloudfoundry.org/cf-cli/getting-started.html#push[`cf push` documentation] for more options. -If there is a Cloud Foundry https://docs.cloudfoundry.org/devguide/deploy-apps/manifest.html[`manifest.yml`] file present in the same directory, it is considered. - -At this point, `cf` starts uploading your application, producing output similar to the following example: - -[indent=0,subs="verbatim,quotes"] ----- - Uploading acloudyspringtime... *OK* - Preparing to start acloudyspringtime... *OK* - -----> Downloaded app package (*8.9M*) - -----> Java Buildpack Version: v3.12 (offline) | https://github.com/cloudfoundry/java-buildpack.git#6f25b7e - -----> Downloading Open Jdk JRE - Expanding Open Jdk JRE to .java-buildpack/open_jdk_jre (1.6s) - -----> Downloading Open JDK Like Memory Calculator 2.0.2_RELEASE from https://java-buildpack.cloudfoundry.org/memory-calculator/trusty/x86_64/memory-calculator-2.0.2_RELEASE.tar.gz (found in cache) - Memory Settings: -Xss349K -Xmx681574K -XX:MaxMetaspaceSize=104857K -Xms681574K -XX:MetaspaceSize=104857K - -----> Downloading Container Certificate Trust Store 1.0.0_RELEASE from https://java-buildpack.cloudfoundry.org/container-certificate-trust-store/container-certificate-trust-store-1.0.0_RELEASE.jar (found in cache) - Adding certificates to .java-buildpack/container_certificate_trust_store/truststore.jks (0.6s) - -----> Downloading Spring Auto Reconfiguration 1.10.0_RELEASE from https://java-buildpack.cloudfoundry.org/auto-reconfiguration/auto-reconfiguration-1.10.0_RELEASE.jar (found in cache) - Checking status of app 'acloudyspringtime'... - 0 of 1 instances running (1 starting) - ... - 0 of 1 instances running (1 starting) - ... - 0 of 1 instances running (1 starting) - ... - 1 of 1 instances running (1 running) - - App started ----- - -Congratulations! The application is now live! - -Once your application is live, you can verify the status of the deployed application by using the `cf apps` command, as shown in the following example: - -[source,shell,indent=0,subs="verbatim"] ----- - $ cf apps - Getting applications in ... - OK - - name requested state instances memory disk urls - ... - acloudyspringtime started 1/1 512M 1G acloudyspringtime.cfapps.io - ... ----- - -Once Cloud Foundry acknowledges that your application has been deployed, you should be able to find the application at the URI given. -In the preceding example, you could find it at `\https://acloudyspringtime.cfapps.io/`. - - - -[[deployment.cloud.cloud-foundry.binding-to-services]] -==== Binding to Services -By default, metadata about the running application as well as service connection information is exposed to the application as environment variables (for example: `$VCAP_SERVICES`). -This architecture decision is due to Cloud Foundry's polyglot (any language and platform can be supported as a buildpack) nature. -Process-scoped environment variables are language agnostic. - -Environment variables do not always make for the easiest API, so Spring Boot automatically extracts them and flattens the data into properties that can be accessed through Spring's `Environment` abstraction, as shown in the following example: - -include::code:MyBean[] - -All Cloud Foundry properties are prefixed with `vcap`. -You can use `vcap` properties to access application information (such as the public URL of the application) and service information (such as database credentials). -See the {spring-boot-module-api}/cloud/CloudFoundryVcapEnvironmentPostProcessor.html[`CloudFoundryVcapEnvironmentPostProcessor`] Javadoc for complete details. - -TIP: The https://github.com/pivotal-cf/java-cfenv/[Java CFEnv] project is a better fit for tasks such as configuring a DataSource. - - - -[[deployment.cloud.kubernetes]] -=== Kubernetes -Spring Boot auto-detects Kubernetes deployment environments by checking the environment for `"*_SERVICE_HOST"` and `"*_SERVICE_PORT"` variables. -You can override this detection with the configprop:spring.main.cloud-platform[] configuration property. - -Spring Boot helps you to <> and export it with <>. - - - -[[deployment.cloud.kubernetes.container-lifecycle]] -==== Kubernetes Container Lifecycle -When Kubernetes deletes an application instance, the shutdown process involves several subsystems concurrently: shutdown hooks, unregistering the service, removing the instance from the load-balancer... -Because this shutdown processing happens in parallel (and due to the nature of distributed systems), there is a window during which traffic can be routed to a pod that has also begun its shutdown processing. - -You can configure a sleep execution in a preStop handler to avoid requests being routed to a pod that has already begun shutting down. -This sleep should be long enough for new requests to stop being routed to the pod and its duration will vary from deployment to deployment. -The preStop handler can be configured by using the PodSpec in the pod's configuration file as follows: - -[source,yaml,indent=0,subs="verbatim"] ----- - spec: - containers: - - name: "example-container" - image: "example-image" - lifecycle: - preStop: - exec: - command: ["sh", "-c", "sleep 10"] ----- - -Once the pre-stop hook has completed, SIGTERM will be sent to the container and <> will begin, allowing any remaining in-flight requests to complete. - -NOTE: When Kubernetes sends a SIGTERM signal to the pod, it waits for a specified time called the termination grace period (the default for which is 30 seconds). -If the containers are still running after the grace period, they are sent the SIGKILL signal and forcibly removed. -If the pod takes longer than 30 seconds to shut down, which could be because you have increased configprop:spring.lifecycle.timeout-per-shutdown-phase[], make sure to increase the termination grace period by setting the `terminationGracePeriodSeconds` option in the Pod YAML. - - - -[[deployment.cloud.heroku]] -=== Heroku -Heroku is another popular PaaS platform. -To customize Heroku builds, you provide a `Procfile`, which provides the incantation required to deploy an application. -Heroku assigns a `port` for the Java application to use and then ensures that routing to the external URI works. - -You must configure your application to listen on the correct port. -The following example shows the `Procfile` for our starter REST application: - -[indent=0] ----- - web: java -Dserver.port=$PORT -jar target/demo-0.0.1-SNAPSHOT.jar ----- - -Spring Boot makes `-D` arguments available as properties accessible from a Spring `Environment` instance. -The `server.port` configuration property is fed to the embedded Tomcat, Jetty, or Undertow instance, which then uses the port when it starts up. -The `$PORT` environment variable is assigned to us by the Heroku PaaS. - -This should be everything you need. -The most common deployment workflow for Heroku deployments is to `git push` the code to production, as shown in the following example: - -[source,shell,indent=0,subs="verbatim"] ----- - $ git push heroku main ----- - -Which will result in the following: - -[indent=0,subs="verbatim,quotes"] ----- - Initializing repository, *done*. - Counting objects: 95, *done*. - Delta compression using up to 8 threads. - Compressing objects: 100% (78/78), *done*. - Writing objects: 100% (95/95), 8.66 MiB | 606.00 KiB/s, *done*. - Total 95 (delta 31), reused 0 (delta 0) - - -----> Java app detected - -----> Installing OpenJDK... *done* - -----> Installing Maven... *done* - -----> Installing settings.xml... *done* - -----> Executing: mvn -B -DskipTests=true clean install - - [INFO] Scanning for projects... - Downloading: https://repo.spring.io/... - Downloaded: https://repo.spring.io/... (818 B at 1.8 KB/sec) - .... - Downloaded: https://s3pository.heroku.com/jvm/... (152 KB at 595.3 KB/sec) - [INFO] Installing /tmp/build_0c35a5d2-a067-4abc-a232-14b1fb7a8229/target/... - [INFO] Installing /tmp/build_0c35a5d2-a067-4abc-a232-14b1fb7a8229/pom.xml ... - [INFO] ------------------------------------------------------------------------ - [INFO] *BUILD SUCCESS* - [INFO] ------------------------------------------------------------------------ - [INFO] Total time: 59.358s - [INFO] Finished at: Fri Mar 07 07:28:25 UTC 2014 - [INFO] Final Memory: 20M/493M - [INFO] ------------------------------------------------------------------------ - - -----> Discovering process types - Procfile declares types -> *web* - - -----> Compressing... *done*, 70.4MB - -----> Launching... *done*, v6 - https://agile-sierra-1405.herokuapp.com/ *deployed to Heroku* - - To git@heroku.com:agile-sierra-1405.git - * [new branch] main -> main ----- - -Your application should now be up and running on Heroku. -For more details, see https://devcenter.heroku.com/articles/deploying-spring-boot-apps-to-heroku[Deploying Spring Boot Applications to Heroku]. - - - -[[deployment.cloud.openshift]] -=== OpenShift -https://www.openshift.com/[OpenShift] has many resources describing how to deploy Spring Boot applications, including: - -* https://blog.openshift.com/using-openshift-enterprise-grade-spring-boot-deployments/[Using the S2I builder] -* https://access.redhat.com/documentation/en-us/reference_architectures/2017/html-single/spring_boot_microservices_on_red_hat_openshift_container_platform_3/[Architecture guide] -* https://blog.openshift.com/using-spring-boot-on-openshift/[Running as a traditional web application on Wildfly] -* https://blog.openshift.com/openshift-commons-briefing-96-cloud-native-applications-spring-rhoar/[OpenShift Commons Briefing] - - - -[[deployment.cloud.aws]] -=== Amazon Web Services (AWS) -Amazon Web Services offers multiple ways to install Spring Boot-based applications, either as traditional web applications (war) or as executable jar files with an embedded web server. -The options include: - -* AWS Elastic Beanstalk -* AWS Code Deploy -* AWS OPS Works -* AWS Cloud Formation -* AWS Container Registry - -Each has different features and pricing models. -In this document, we describe to approach using AWS Elastic Beanstalk. - - - -[[deployment.cloud.aws.beanstalk]] -==== AWS Elastic Beanstalk -As described in the official https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_Java.html[Elastic Beanstalk Java guide], there are two main options to deploy a Java application. -You can either use the "`Tomcat Platform`" or the "`Java SE platform`". - - - -[[deployment.cloud.aws.beanstalk.tomcat-platform]] -===== Using the Tomcat Platform -This option applies to Spring Boot projects that produce a war file. -No special configuration is required. -You need only follow the official guide. - - - -[[deployment.cloud.aws.beanstalk.java-se-platform]] -===== Using the Java SE Platform -This option applies to Spring Boot projects that produce a jar file and run an embedded web container. -Elastic Beanstalk environments run an nginx instance on port 80 to proxy the actual application, running on port 5000. -To configure it, add the following line to your `application.properties` file: - -[indent=0] ----- - server.port=5000 ----- - - -[TIP] -.Upload binaries instead of sources -==== -By default, Elastic Beanstalk uploads sources and compiles them in AWS. -However, it is best to upload the binaries instead. -To do so, add lines similar to the following to your `.elasticbeanstalk/config.yml` file: - -[source,xml,indent=0,subs="verbatim"] ----- - deploy: - artifact: target/demo-0.0.1-SNAPSHOT.jar ----- -==== - -[TIP] -.Reduce costs by setting the environment type -==== -By default an Elastic Beanstalk environment is load balanced. -The load balancer has a significant cost. -To avoid that cost, set the environment type to "`Single instance`", as described in https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/environments-create-wizard.html#environments-create-wizard-capacity[the Amazon documentation]. -You can also create single instance environments by using the CLI and the following command: - -[indent=0] ----- - eb create -s ----- -==== - - - -[[deployment.cloud.aws.summary]] -==== Summary -This is one of the easiest ways to get to AWS, but there are more things to cover, such as how to integrate Elastic Beanstalk into any CI / CD tool, use the Elastic Beanstalk Maven plugin instead of the CLI, and others. -There is a https://exampledriven.wordpress.com/2017/01/09/spring-boot-aws-elastic-beanstalk-example/[blog post] covering these topics more in detail. - - - -[[deployment.cloud.boxfuse]] -=== CloudCaptain and Amazon Web Services -https://cloudcaptain.sh/[CloudCaptain] works by turning your Spring Boot executable jar or war into a minimal VM image that can be deployed unchanged either on VirtualBox or on AWS. -CloudCaptain comes with deep integration for Spring Boot and uses the information from your Spring Boot configuration file to automatically configure ports and health check URLs. -CloudCaptain leverages this information both for the images it produces as well as for all the resources it provisions (instances, security groups, elastic load balancers, and so on). - -Once you have created a https://console.cloudcaptain.sh[CloudCaptain account], connected it to your AWS account, installed the latest version of the CloudCaptain Client, and ensured that the application has been built by Maven or Gradle (by using, for example, `mvn clean package`), you can deploy your Spring Boot application to AWS with a command similar to the following: - -[source,shell,indent=0,subs="verbatim"] ----- - $ boxfuse run myapp-1.0.jar -env=prod ----- - -See the https://cloudcaptain.sh/docs/commandline/run.html[`boxfuse run` documentation] for more options. -If there is a https://cloudcaptain.sh/docs/commandline/#configuration[`boxfuse.conf`] file present in the current directory, it is considered. - -TIP: By default, CloudCaptain activates a Spring profile named `boxfuse` on startup. -If your executable jar or war contains an https://cloudcaptain.sh/docs/payloads/springboot.html#configuration[`application-boxfuse.properties`] file, CloudCaptain bases its configuration on the properties it contains. - -At this point, CloudCaptain creates an image for your application, uploads it, and configures and starts the necessary resources on AWS, resulting in output similar to the following example: - -[indent=0,subs="verbatim"] ----- - Fusing Image for myapp-1.0.jar ... - Image fused in 00:06.838s (53937 K) -> axelfontaine/myapp:1.0 - Creating axelfontaine/myapp ... - Pushing axelfontaine/myapp:1.0 ... - Verifying axelfontaine/myapp:1.0 ... - Creating Elastic IP ... - Mapping myapp-axelfontaine.boxfuse.io to 52.28.233.167 ... - Waiting for AWS to create an AMI for axelfontaine/myapp:1.0 in eu-central-1 (this may take up to 50 seconds) ... - AMI created in 00:23.557s -> ami-d23f38cf - Creating security group boxfuse-sg_axelfontaine/myapp:1.0 ... - Launching t2.micro instance of axelfontaine/myapp:1.0 (ami-d23f38cf) in eu-central-1 ... - Instance launched in 00:30.306s -> i-92ef9f53 - Waiting for AWS to boot Instance i-92ef9f53 and Payload to start at https://52.28.235.61/ ... - Payload started in 00:29.266s -> https://52.28.235.61/ - Remapping Elastic IP 52.28.233.167 to i-92ef9f53 ... - Waiting 15s for AWS to complete Elastic IP Zero Downtime transition ... - Deployment completed successfully. axelfontaine/myapp:1.0 is up and running at https://myapp-axelfontaine.boxfuse.io/ ----- - -Your application should now be up and running on AWS. - -See the blog post on https://cloudcaptain.sh/blog/spring-boot-ec2.html[deploying Spring Boot apps on EC2] as well as the https://cloudcaptain.sh/docs/payloads/springboot.html[documentation for the CloudCaptain Spring Boot integration] to get started with a Maven build to run the app. - - - -[[deployment.cloud.azure]] -=== Azure -This https://spring.io/guides/gs/spring-boot-for-azure/[Getting Started guide] walks you through deploying your Spring Boot application to either https://azure.microsoft.com/en-us/services/spring-cloud/[Azure Spring Cloud] or https://docs.microsoft.com/en-us/azure/app-service/overview[Azure App Service]. - - - -[[deployment.cloud.google]] -=== Google Cloud -Google Cloud has several options that can be used to launch Spring Boot applications. -The easiest to get started with is probably App Engine, but you could also find ways to run Spring Boot in a container with Container Engine or on a virtual machine with Compute Engine. - -To deploy your first app to App Engine standard environment, follow https://codelabs.developers.google.com/codelabs/cloud-app-engine-springboot#0[this tutorial]. - -Alternatively, App Engine Flex requires you to create an `app.yaml` file to describe the resources your app requires. -Normally, you put this file in `src/main/appengine`, and it should resemble the following file: - -[source,yaml,indent=0,subs="verbatim"] ----- - service: "default" - - runtime: "java17" - env: "flex" - - handlers: - - url: "/.*" - script: "this field is required, but ignored" - - manual_scaling: - instances: 1 - - health_check: - enable_health_check: false - - env_variables: - ENCRYPT_KEY: "your_encryption_key_here" ----- - -You can deploy the app (for example, with a Maven plugin) by adding the project ID to the build configuration, as shown in the following example: - -[source,xml,indent=0,subs="verbatim"] ----- - - com.google.cloud.tools - appengine-maven-plugin - 2.4.4 - - myproject - - ----- - -Then deploy with `mvn appengine:deploy` (you need to authenticate first, otherwise the build fails). diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/efficient.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/efficient.adoc deleted file mode 100644 index 9064f844c7f8..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/efficient.adoc +++ /dev/null @@ -1,67 +0,0 @@ -[[deployment.efficient]] -== Efficient deployments - - - -[[deployment.efficient.unpacking]] -=== Unpacking the Executable JAR -If you are running your application from a container, you can use an executable jar, but it is also often an advantage to explode it and run it in a different way. -Certain PaaS implementations may also choose to unpack archives before they run. -For example, Cloud Foundry operates this way. -One way to run an unpacked archive is by starting the appropriate launcher, as follows: - -[source,shell,indent=0,subs="verbatim"] ----- - $ jar -xf myapp.jar - $ java org.springframework.boot.loader.JarLauncher ----- - -This is actually slightly faster on startup (depending on the size of the jar) than running from an unexploded archive. -After startup, you should not expect any differences. - -Once you have unpacked the jar file, you can also get an extra boost to startup time by running the app with its "natural" main method instead of the `JarLauncher`. For example: - -[source,shell,indent=0,subs="verbatim"] ----- - $ jar -xf myapp.jar - $ java -cp "BOOT-INF/classes:BOOT-INF/lib/*" com.example.MyApplication ----- - -NOTE: Using the `JarLauncher` over the application's main method has the added benefit of a predictable classpath order. -The jar contains a `classpath.idx` file which is used by the `JarLauncher` when constructing the classpath. - - - -[[deployment.efficient.aot]] -=== Using Ahead-of-time Processing With the JVM - -It's beneficial for the startup time to run your application using the AOT generated initialization code. -First, you need to ensure that the jar you are building includes AOT generated code. - -For Maven, this means that you should build with `-Pnative` to activate the `native` profile: - -[source,shell,indent=0,subs="verbatim"] ----- - $ mvn -Pnative package ----- - -For Gradle, you need to ensure that your build includes the `org.springframework.boot.aot` plugin. - -When the JAR has been built, run it with `spring.aot.enabled` system property set to `true`. For example: - -[source,shell,indent=0,subs="verbatim"] ----- - $ java -Dspring.aot.enabled=true -jar myapplication.jar - - ........ Starting AOT-processed MyApplication ... ----- - -Beware that using the ahead-of-time processing has drawbacks. -It implies the following restrictions: - -* The classpath is fixed and fully defined at build time -* The beans defined in your application cannot change at runtime, meaning: -- The Spring `@Profile` annotation and profile-specific configuration <>. -- Properties that change if a bean is created are not supported (for example, `@ConditionalOnProperty` and `.enable` properties). - -To learn more about ahead-of-time processing, please see the <>. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/installing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/installing.adoc deleted file mode 100644 index e4af64af3f02..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/installing.adoc +++ /dev/null @@ -1,380 +0,0 @@ -[[deployment.installing]] -== Installing Spring Boot Applications -In addition to running Spring Boot applications by using `java -jar` directly, it is also possible to run them as `systemd`, `init.d` or Windows services. - - - -[[deployment.installing.system-d]] -=== Installation as a systemd Service -`systemd` is the successor of the System V init system and is now being used by many modern Linux distributions. -Spring Boot applications can be launched by using `systemd` '`service`' scripts. - -Assuming that you have a Spring Boot application packaged as an uber jar in `/var/myapp`, to install it as a `systemd` service, create a script named `myapp.service` and place it in `/etc/systemd/system` directory. -The following script offers an example: - -[indent=0] ----- - [Unit] - Description=myapp - After=syslog.target network.target - - [Service] - User=myapp - Group=myapp - - Environment="JAVA_HOME=/path/to/java/home" - - ExecStart=${JAVA_HOME}/bin/java -jar /var/myapp/myapp.jar - ExecStop=/bin/kill -15 $MAINPID - SuccessExitStatus=143 - - [Install] - WantedBy=multi-user.target ----- - -IMPORTANT: Remember to change the `Description`, `User`, `Group`, `Environment` and `ExecStart` fields for your application. - -NOTE: The `ExecStart` field does not declare the script action command, which means that the `run` command is used by default. - -The user that runs the application, the PID file, and the console log file are managed by `systemd` itself and therefore must be configured by using appropriate fields in the '`service`' script. -Consult the https://www.freedesktop.org/software/systemd/man/systemd.service.html[service unit configuration man page] for more details. - -To flag the application to start automatically on system boot, use the following command: - -[source,shell,indent=0,subs="verbatim"] ----- - $ systemctl enable myapp.service ----- - -Run `man systemctl` for more details. - - - -[[deployment.installing.init-d]] -=== Installation as an init.d Service (System V) -To use your application as `init.d` service, configure its build to produce a <>. - -CAUTION: Fully executable jars work by embedding an extra script at the front of the file. -Currently, some tools do not accept this format, so you may not always be able to use this technique. -For example, `jar -xf` may silently fail to extract a jar or war that has been made fully executable. -It is recommended that you make your jar or war fully executable only if you intend to execute it directly, rather than running it with `java -jar` or deploying it to a servlet container. - -CAUTION: A zip64-format jar file cannot be made fully executable. -Attempting to do so will result in a jar file that is reported as corrupt when executed directly or with `java -jar`. -A standard-format jar file that contains one or more zip64-format nested jars can be fully executable. - -To create a '`fully executable`' jar with Maven, use the following plugin configuration: - -[source,xml,indent=0,subs="verbatim"] ----- - - org.springframework.boot - spring-boot-maven-plugin - - true - - ----- - -The following example shows the equivalent Gradle configuration: - -[source,gradle,indent=0,subs="verbatim"] ----- - tasks.named('bootJar') { - launchScript() - } ----- - -It can then be symlinked to `init.d` to support the standard `start`, `stop`, `restart`, and `status` commands. - -The default launch script that is added to a fully executable jar supports most Linux distributions and is tested on CentOS and Ubuntu. -Other platforms, such as OS X and FreeBSD, require the use of a custom script. -The default scripts supports the following features: - -* Starts the services as the user that owns the jar file -* Tracks the application's PID by using `/var/run//.pid` -* Writes console logs to `/var/log/.log` - -Assuming that you have a Spring Boot application installed in `/var/myapp`, to install a Spring Boot application as an `init.d` service, create a symlink, as follows: - -[source,shell,indent=0,subs="verbatim"] ----- - $ sudo ln -s /var/myapp/myapp.jar /etc/init.d/myapp ----- - -Once installed, you can start and stop the service in the usual way. -For example, on a Debian-based system, you could start it with the following command: - -[source,shell,indent=0,subs="verbatim"] ----- - $ service myapp start ----- - -TIP: If your application fails to start, check the log file written to `/var/log/.log` for errors. - -You can also flag the application to start automatically by using your standard operating system tools. -For example, on Debian, you could use the following command: - -[source,shell,indent=0,subs="verbatim"] ----- - $ update-rc.d myapp defaults ----- - - - -[[deployment.installing.init-d.securing]] -==== Securing an init.d Service -NOTE: The following is a set of guidelines on how to secure a Spring Boot application that runs as an init.d service. -It is not intended to be an exhaustive list of everything that should be done to harden an application and the environment in which it runs. - -When executed as root, as is the case when root is being used to start an init.d service, the default executable script runs the application as the user specified in the `RUN_AS_USER` environment variable. -When the environment variable is not set, the user who owns the jar file is used instead. -You should never run a Spring Boot application as `root`, so `RUN_AS_USER` should never be root and your application's jar file should never be owned by root. -Instead, create a specific user to run your application and set the `RUN_AS_USER` environment variable or use `chown` to make it the owner of the jar file, as shown in the following example: - -[source,shell,indent=0,subs="verbatim"] ----- - $ chown bootapp:bootapp your-app.jar ----- - -In this case, the default executable script runs the application as the `bootapp` user. - -TIP: To reduce the chances of the application's user account being compromised, you should consider preventing it from using a login shell. -For example, you can set the account's shell to `/usr/sbin/nologin`. - -You should also take steps to prevent the modification of your application's jar file. -Firstly, configure its permissions so that it cannot be written and can only be read or executed by its owner, as shown in the following example: - -[source,shell,indent=0,subs="verbatim"] ----- - $ chmod 500 your-app.jar ----- - -Second, you should also take steps to limit the damage if your application or the account that is running it is compromised. -If an attacker does gain access, they could make the jar file writable and change its contents. -One way to protect against this is to make it immutable by using `chattr`, as shown in the following example: - -[source,shell,indent=0,subs="verbatim"] ----- - $ sudo chattr +i your-app.jar ----- - -This will prevent any user, including root, from modifying the jar. - -If root is used to control the application's service and you <> to customize its startup, the `.conf` file is read and evaluated by the root user. -It should be secured accordingly. -Use `chmod` so that the file can only be read by the owner and use `chown` to make root the owner, as shown in the following example: - -[source,shell,indent=0,subs="verbatim"] ----- - $ chmod 400 your-app.conf - $ sudo chown root:root your-app.conf ----- - - - -[[deployment.installing.init-d.script-customization]] -==== Customizing the Startup Script -The default embedded startup script written by the Maven or Gradle plugin can be customized in a number of ways. -For most people, using the default script along with a few customizations is usually enough. -If you find you cannot customize something that you need to, use the `embeddedLaunchScript` option to write your own file entirely. - - - -[[deployment.installing.init-d.script-customization.when-written]] -===== Customizing the Start Script When It Is Written -It often makes sense to customize elements of the start script as it is written into the jar file. -For example, init.d scripts can provide a "`description`". -Since you know the description up front (and it need not change), you may as well provide it when the jar is generated. - -To customize written elements, use the `embeddedLaunchScriptProperties` option of the Spring Boot Maven plugin or the {spring-boot-gradle-plugin-docs}#packaging-executable-configuring-launch-script[`properties` property of the Spring Boot Gradle plugin's `launchScript`]. - -The following property substitutions are supported with the default script: - -[cols="1,3,3,3"] -|=== -| Name | Description | Gradle default | Maven default - -| `mode` -| The script mode. -| `auto` -| `auto` - -| `initInfoProvides` -| The `Provides` section of "`INIT INFO`" -| `${task.baseName}` -| `${project.artifactId}` - -| `initInfoRequiredStart` -| `Required-Start` section of "`INIT INFO`". -| `$remote_fs $syslog $network` -| `$remote_fs $syslog $network` - -| `initInfoRequiredStop` -| `Required-Stop` section of "`INIT INFO`". -| `$remote_fs $syslog $network` -| `$remote_fs $syslog $network` - -| `initInfoDefaultStart` -| `Default-Start` section of "`INIT INFO`". -| `2 3 4 5` -| `2 3 4 5` - -| `initInfoDefaultStop` -| `Default-Stop` section of "`INIT INFO`". -| `0 1 6` -| `0 1 6` - -| `initInfoShortDescription` -| `Short-Description` section of "`INIT INFO`". -| Single-line version of `${project.description}` (falling back to `${task.baseName}`) -| `${project.name}` - -| `initInfoDescription` -| `Description` section of "`INIT INFO`". -| `${project.description}` (falling back to `${task.baseName}`) -| `${project.description}` (falling back to `${project.name}`) - -| `initInfoChkconfig` -| `chkconfig` section of "`INIT INFO`" -| `2345 99 01` -| `2345 99 01` - -| `confFolder` -| The default value for `CONF_FOLDER` -| Folder containing the jar -| Folder containing the jar - -| `inlinedConfScript` -| Reference to a file script that should be inlined in the default launch script. - This can be used to set environmental variables such as `JAVA_OPTS` before any external config files are loaded -| -| - -| `logFolder` -| Default value for `LOG_FOLDER`. - Only valid for an `init.d` service -| -| - -| `logFilename` -| Default value for `LOG_FILENAME`. - Only valid for an `init.d` service -| -| - -| `pidFolder` -| Default value for `PID_FOLDER`. - Only valid for an `init.d` service -| -| - -| `pidFilename` -| Default value for the name of the PID file in `PID_FOLDER`. - Only valid for an `init.d` service -| -| - -| `useStartStopDaemon` -| Whether the `start-stop-daemon` command, when it is available, should be used to control the process -| `true` -| `true` - -| `stopWaitTime` -| Default value for `STOP_WAIT_TIME` in seconds. - Only valid for an `init.d` service -| 60 -| 60 -|=== - - - -[[deployment.installing.init-d.script-customization.when-running]] -===== Customizing a Script When It Runs -For items of the script that need to be customized _after_ the jar has been written, you can use environment variables or a <>. - -The following environment properties are supported with the default script: - -[cols="1,6"] -|=== -| Variable | Description - -| `MODE` -| The "`mode`" of operation. - The default depends on the way the jar was built but is usually `auto` (meaning it tries to guess if it is an init script by checking if it is a symlink in a directory called `init.d`). - You can explicitly set it to `service` so that the `stop\|start\|status\|restart` commands work or to `run` if you want to run the script in the foreground. - -| `RUN_AS_USER` -| The user that will be used to run the application. - When not set, the user that owns the jar file will be used. - -| `USE_START_STOP_DAEMON` -| Whether the `start-stop-daemon` command, when it is available, should be used to control the process. - Defaults to `true`. - -| `PID_FOLDER` -| The root name of the pid folder (`/var/run` by default). - -| `LOG_FOLDER` -| The name of the folder in which to put log files (`/var/log` by default). - -| `CONF_FOLDER` -| The name of the folder from which to read .conf files (same folder as jar-file by default). - -| `LOG_FILENAME` -| The name of the log file in the `LOG_FOLDER` (`.log` by default). - -| `APP_NAME` -| The name of the app. - If the jar is run from a symlink, the script guesses the app name. - If it is not a symlink or you want to explicitly set the app name, this can be useful. - -| `RUN_ARGS` -| The arguments to pass to the program (the Spring Boot app). - -| `JAVA_HOME` -| The location of the `java` executable is discovered by using the `PATH` by default, but you can set it explicitly if there is an executable file at `$JAVA_HOME/bin/java`. - -| `JAVA_OPTS` -| Options that are passed to the JVM when it is launched. - -| `JARFILE` -| The explicit location of the jar file, in case the script is being used to launch a jar that it is not actually embedded. - -| `DEBUG` -| If not empty, sets the `-x` flag on the shell process, allowing you to see the logic in the script. - -| `STOP_WAIT_TIME` -| The time in seconds to wait when stopping the application before forcing a shutdown (`60` by default). -|=== - -NOTE: The `PID_FOLDER`, `LOG_FOLDER`, and `LOG_FILENAME` variables are only valid for an `init.d` service. -For `systemd`, the equivalent customizations are made by using the '`service`' script. -See the https://www.freedesktop.org/software/systemd/man/systemd.service.html[service unit configuration man page] for more details. - - - -[[deployment.installing.init-d.script-customization.when-running.conf-file]] -====== Using a Conf Gile -With the exception of `JARFILE` and `APP_NAME`, the settings listed in the preceding section can be configured by using a `.conf` file. -The file is expected to be next to the jar file and have the same name but suffixed with `.conf` rather than `.jar`. -For example, a jar named `/var/myapp/myapp.jar` uses the configuration file named `/var/myapp/myapp.conf`, as shown in the following example: - -.myapp.conf -[indent=0,subs="verbatim"] ----- - JAVA_OPTS=-Xmx1024M - LOG_FOLDER=/custom/log/folder ----- - -TIP: If you do not like having the config file next to the jar file, you can set a `CONF_FOLDER` environment variable to customize the location of the config file. - -To learn about securing this file appropriately, see <>. - - - -[[deployment.installing.windows-services]] -=== Microsoft Windows Services -A Spring Boot application can be started as a Windows service by using https://github.com/kohsuke/winsw[`winsw`]. - -A (https://github.com/snicoll/spring-boot-daemon[separately maintained sample]) describes step-by-step how you can create a Windows service for your Spring Boot application. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/whats-next.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/whats-next.adoc deleted file mode 100644 index a7b832772867..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/whats-next.adoc +++ /dev/null @@ -1,7 +0,0 @@ -[[deployment.whats-next]] -== What to Read Next -See the https://www.cloudfoundry.org/[Cloud Foundry], https://www.heroku.com/[Heroku], https://www.openshift.com[OpenShift], and https://boxfuse.com[Boxfuse] web sites for more information about the kinds of features that a PaaS can offer. -These are just four of the most popular Java PaaS providers. -Since Spring Boot is so amenable to cloud-based deployment, you can freely consider other providers as well. - -The next section goes on to cover the _<>_, or you can jump ahead to read about the _<>_ or our _<>_. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation.adoc deleted file mode 100644 index d61170bc489c..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation.adoc +++ /dev/null @@ -1,39 +0,0 @@ -include::attributes.adoc[] - - - -[[documentation]] -= Documentation Overview -include::attributes.adoc[] - - - -This section provides a brief overview of Spring Boot reference documentation. -It serves as a map for the rest of the document. - -The latest copy of this document is available at {spring-boot-current-docs}. - - -include::documentation/first-steps.adoc[] - -include::documentation/upgrading.adoc[] - -include::documentation/using.adoc[] - -include::documentation/features.adoc[] - -include::documentation/web.adoc[] - -include::documentation/data.adoc[] - -include::documentation/messaging.adoc[] - -include::documentation/io.adoc[] - -include::documentation/container-images.adoc[] - -include::documentation/actuator.adoc[] - -include::documentation/native-images.adoc[] - -include::documentation/advanced.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/actuator.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/actuator.adoc deleted file mode 100644 index 32342c74286b..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/actuator.adoc +++ /dev/null @@ -1,7 +0,0 @@ -[[documentation.actuator]] -== Moving to Production -When you are ready to push your Spring Boot application to production, we have <> that you might like: - -* *Management endpoints:* <> -* *Connection options:* <> | <> -* *Monitoring:* <> | <> | <> | <> diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/advanced.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/advanced.adoc deleted file mode 100644 index a24d2582158e..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/advanced.adoc +++ /dev/null @@ -1,7 +0,0 @@ -[[documentation.advanced]] -== Advanced Topics -Finally, we have a few topics for more advanced users: - -* *Spring Boot Applications Deployment:* <> | <> -* *Build tool plugins:* <> | <> -* *Appendix:* <> | <> | <> | <> | <> | <> diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/container-images.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/container-images.adoc deleted file mode 100644 index 62a373430a9c..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/container-images.adoc +++ /dev/null @@ -1,7 +0,0 @@ -[[documentation.container-images]] -== Container Images -Spring Boot provides first-class support for building efficient container images. You can read more about it here: - -* *Efficient Container Images:* <> -* *Dockerfiles:* <> -* *Cloud Native Buildpacks:* <> diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/data.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/data.adoc deleted file mode 100644 index 3a0a7dd82053..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/data.adoc +++ /dev/null @@ -1,6 +0,0 @@ -[[documentation.data]] -== Data -If your application deals with a datastore, you can see how to configure that here: - -* *SQL:* <> -* *NOSQL:* <> diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/features.adoc deleted file mode 100644 index 846e23e67c13..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/features.adoc +++ /dev/null @@ -1,9 +0,0 @@ -[[documentation.features]] -== Learning About Spring Boot Features -Need more details about Spring Boot's core features? -<>: - -* *Spring Application:* <> -* *External Configuration:* <> -* *Profiles:* <> -* *Logging:* <> diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/first-steps.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/first-steps.adoc deleted file mode 100644 index fb396ffd3408..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/first-steps.adoc +++ /dev/null @@ -1,7 +0,0 @@ -[[documentation.first-steps]] -== First Steps -If you are getting started with Spring Boot or 'Spring' in general, start with <>: - -* *From scratch:* <> | <> | <> -* *Tutorial:* <> | <> -* *Running your example:* <> | <> diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/io.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/io.adoc deleted file mode 100644 index 462d2d68d0a6..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/io.adoc +++ /dev/null @@ -1,11 +0,0 @@ -[[documentation.io]] -== IO -If your application needs IO capabilities, see one or more of the following sections: - -* *Caching:* <> -* *Quartz:* <> -* *Mail:* <> -* *Validation:* <> -* *REST Clients:* <> -* *Webservices:* <> -* *JTA:* <> diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/messaging.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/messaging.adoc deleted file mode 100644 index 51412fde0c9b..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/messaging.adoc +++ /dev/null @@ -1,9 +0,0 @@ -[[documentation.messaging]] -== Messaging -If your application uses any messaging protocol, see one or more of the following sections: - -* *JMS:* <> -* *AMQP:* <> -* *Kafka:* <> -* *RSocket:* <> -* *Spring Integration:* <> diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/native-images.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/native-images.adoc deleted file mode 100644 index f19f7437228b..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/native-images.adoc +++ /dev/null @@ -1,9 +0,0 @@ -[[documentation.native-images]] -== GraalVM Native Images -Spring Boot applications can be converted into native executables using GraalVM. -You can read more about our native image support here: - -* *GraalVM Native Images:* <> | <> | <> -* *Getting Started:* <> | <> -* *Testing:* <> | <> -* *Advanced Topics:* <> | <> | <> diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/upgrading.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/upgrading.adoc deleted file mode 100644 index cd31169cf304..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/upgrading.adoc +++ /dev/null @@ -1,11 +0,0 @@ -[[documentation.upgrading]] -== Upgrading From an Earlier Version - -You should always ensure that you are running a {github-wiki}/Supported-Versions[supported version] of Spring Boot. - -Depending on the version that you are upgrading to, you can find some additional tips here: - -* *From 1.x:* <> -* *To a new feature release:* <> -* *Spring Boot CLI:* <> - diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/using.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/using.adoc deleted file mode 100644 index c5a30914f843..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/using.adoc +++ /dev/null @@ -1,9 +0,0 @@ -[[documentation.using]] -== Developing With Spring Boot -Ready to actually start using Spring Boot? <>: - -* *Build systems:* <> | <> | <> | <> -* *Best practices:* <> | <> | <> | <> -* *Running your code:* <> | <> | <> | <> -* *Packaging your app:* <> -* *Spring Boot CLI:* <> diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/web.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/web.adoc deleted file mode 100644 index a2705125fa1d..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/web.adoc +++ /dev/null @@ -1,10 +0,0 @@ -[[documentation.web]] -== Web -If you develop Spring Boot web applications, take a look at the following content: - -* *Servlet Web Applications:* <> -* *Reactive Web Applications:* <> -* *Graceful Shutdown:* <> -* *Spring Security:* <> -* *Spring Session:* <> -* *Spring HATEOAS:* <> diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar.adoc deleted file mode 100644 index 49bb31a01af8..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar.adoc +++ /dev/null @@ -1,25 +0,0 @@ -[appendix] -[[appendix.executable-jar]] -= The Executable Jar Format -include::attributes.adoc[] - - - -The `spring-boot-loader` modules lets Spring Boot support executable jar and war files. -If you use the Maven plugin or the Gradle plugin, executable jars are automatically generated, and you generally do not need to know the details of how they work. - -If you need to create executable jars from a different build system or if you are just curious about the underlying technology, this appendix provides some background. - - - -include::executable-jar/nested-jars.adoc[] - -include::executable-jar/jarfile-class.adoc[] - -include::executable-jar/launching.adoc[] - -include::executable-jar/property-launcher.adoc[] - -include::executable-jar/restrictions.adoc[] - -include::executable-jar/alternatives.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/jarfile-class.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/jarfile-class.adoc deleted file mode 100644 index da7c616fb314..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/jarfile-class.adoc +++ /dev/null @@ -1,32 +0,0 @@ -[[appendix.executable-jar.jarfile-class]] -== Spring Boot's "`JarFile`" Class -The core class used to support loading nested jars is `org.springframework.boot.loader.jar.JarFile`. -It lets you load jar content from a standard jar file or from nested child jar data. -When first loaded, the location of each `JarEntry` is mapped to a physical file offset of the outer jar, as shown in the following example: - -[indent=0] ----- - myapp.jar - +-------------------+-------------------------+ - | /BOOT-INF/classes | /BOOT-INF/lib/mylib.jar | - |+-----------------+||+-----------+----------+| - || A.class ||| B.class | C.class || - |+-----------------+||+-----------+----------+| - +-------------------+-------------------------+ - ^ ^ ^ - 0063 3452 3980 ----- - -The preceding example shows how `A.class` can be found in `/BOOT-INF/classes` in `myapp.jar` at position `0063`. -`B.class` from the nested jar can actually be found in `myapp.jar` at position `3452`, and `C.class` is at position `3980`. - -Armed with this information, we can load specific nested entries by seeking to the appropriate part of the outer jar. -We do not need to unpack the archive, and we do not need to read all entry data into memory. - - - -[[appendix.executable-jar.jarfile-class.compatibility]] -=== Compatibility With the Standard Java "`JarFile`" -Spring Boot Loader strives to remain compatible with existing code and libraries. -`org.springframework.boot.loader.jar.JarFile` extends from `java.util.jar.JarFile` and should work as a drop-in replacement. -The `getURL()` method returns a `URL` that opens a connection compatible with `java.net.JarURLConnection` and can be used with Java's `URLClassLoader`. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/launching.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/launching.adoc deleted file mode 100644 index a672c6963495..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/launching.adoc +++ /dev/null @@ -1,38 +0,0 @@ -[[appendix.executable-jar.launching]] -== Launching Executable Jars -The `org.springframework.boot.loader.Launcher` class is a special bootstrap class that is used as an executable jar's main entry point. -It is the actual `Main-Class` in your jar file, and it is used to setup an appropriate `URLClassLoader` and ultimately call your `main()` method. - -There are three launcher subclasses (`JarLauncher`, `WarLauncher`, and `PropertiesLauncher`). -Their purpose is to load resources (`.class` files and so on) from nested jar files or war files in directories (as opposed to those explicitly on the classpath). -In the case of `JarLauncher` and `WarLauncher`, the nested paths are fixed. -`JarLauncher` looks in `BOOT-INF/lib/`, and `WarLauncher` looks in `WEB-INF/lib/` and `WEB-INF/lib-provided/`. -You can add extra jars in those locations if you want more. -The `PropertiesLauncher` looks in `BOOT-INF/lib/` in your application archive by default. -You can add additional locations by setting an environment variable called `LOADER_PATH` or `loader.path` in `loader.properties` (which is a comma-separated list of directories, archives, or directories within archives). - - - -[[appendix.executable-jar.launching.manifest]] -=== Launcher Manifest -You need to specify an appropriate `Launcher` as the `Main-Class` attribute of `META-INF/MANIFEST.MF`. -The actual class that you want to launch (that is, the class that contains a `main` method) should be specified in the `Start-Class` attribute. - -The following example shows a typical `MANIFEST.MF` for an executable jar file: - -[indent=0] ----- - Main-Class: org.springframework.boot.loader.JarLauncher - Start-Class: com.mycompany.project.MyApplication ----- - -For a war file, it would be as follows: - -[indent=0] ----- - Main-Class: org.springframework.boot.loader.WarLauncher - Start-Class: com.mycompany.project.MyApplication ----- - -NOTE: You need not specify `Class-Path` entries in your manifest file. -The classpath is deduced from the nested jars. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/nested-jars.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/nested-jars.adoc deleted file mode 100644 index 2c580bb4d965..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/nested-jars.adoc +++ /dev/null @@ -1,143 +0,0 @@ -[[appendix.executable-jar.nested-jars]] -== Nested JARs -Java does not provide any standard way to load nested jar files (that is, jar files that are themselves contained within a jar). -This can be problematic if you need to distribute a self-contained application that can be run from the command line without unpacking. - -To solve this problem, many developers use "`shaded`" jars. -A shaded jar packages all classes, from all jars, into a single "`uber jar`". -The problem with shaded jars is that it becomes hard to see which libraries are actually in your application. -It can also be problematic if the same filename is used (but with different content) in multiple jars. -Spring Boot takes a different approach and lets you actually nest jars directly. - - - -[[appendix.executable-jar.nested-jars.jar-structure]] -=== The Executable Jar File Structure -Spring Boot Loader-compatible jar files should be structured in the following way: - -[indent=0] ----- - example.jar - | - +-META-INF - | +-MANIFEST.MF - +-org - | +-springframework - | +-boot - | +-loader - | +- - +-BOOT-INF - +-classes - | +-mycompany - | +-project - | +-YourClasses.class - +-lib - +-dependency1.jar - +-dependency2.jar ----- - -Application classes should be placed in a nested `BOOT-INF/classes` directory. -Dependencies should be placed in a nested `BOOT-INF/lib` directory. - - - -[[appendix.executable-jar.nested-jars.war-structure]] -=== The Executable War File Structure -Spring Boot Loader-compatible war files should be structured in the following way: - -[indent=0] ----- - example.war - | - +-META-INF - | +-MANIFEST.MF - +-org - | +-springframework - | +-boot - | +-loader - | +- - +-WEB-INF - +-classes - | +-com - | +-mycompany - | +-project - | +-YourClasses.class - +-lib - | +-dependency1.jar - | +-dependency2.jar - +-lib-provided - +-servlet-api.jar - +-dependency3.jar ----- - -Dependencies should be placed in a nested `WEB-INF/lib` directory. -Any dependencies that are required when running embedded but are not required when deploying to a traditional web container should be placed in `WEB-INF/lib-provided`. - - - -[[appendix.executable-jar.nested-jars.index-files]] -=== Index Files -Spring Boot Loader-compatible jar and war archives can include additional index files under the `BOOT-INF/` directory. -A `classpath.idx` file can be provided for both jars and wars, and it provides the ordering that jars should be added to the classpath. -The `layers.idx` file can be used only for jars, and it allows a jar to be split into logical layers for Docker/OCI image creation. - -Index files follow a YAML compatible syntax so that they can be easily parsed by third-party tools. -These files, however, are _not_ parsed internally as YAML and they must be written in exactly the formats described below in order to be used. - - - -[[appendix.executable-jar.nested-jars.classpath-index]] -=== Classpath Index -The classpath index file can be provided in `BOOT-INF/classpath.idx`. -Typically, it is generated automatically by Spring Boot's Maven and Gradle build plugins. -It provides a list of jar names (including the directory) in the order that they should be added to the classpath. -When generated by the build plugins, this classpath ordering matches that used by the build system for running and testing the application. -Each line must start with dash space (`"-·"`) and names must be in double quotes. - -For example, given the following jar: - -[indent=0] ----- - example.jar - | - +-META-INF - | +-... - +-BOOT-INF - +-classes - | +... - +-lib - +-dependency1.jar - +-dependency2.jar ----- - -The index file would look like this: - -[indent=0] ----- - - "BOOT-INF/lib/dependency2.jar" - - "BOOT-INF/lib/dependency1.jar" ----- - - - -[[appendix.executable-jar.nested-jars.layer-index]] -=== Layer Index -The layers index file can be provided in `BOOT-INF/layers.idx`. -It provides a list of layers and the parts of the jar that should be contained within them. -Layers are written in the order that they should be added to the Docker/OCI image. -Layers names are written as quoted strings prefixed with dash space (`"-·"`) and with a colon (`":"`) suffix. -Layer content is either a file or directory name written as a quoted string prefixed by space space dash space (`"··-·"`). -A directory name ends with `/`, a file name does not. -When a directory name is used it means that all files inside that directory are in the same layer. - -A typical example of a layers index would be: - -[indent=0] ----- - - "dependencies": - - "BOOT-INF/lib/dependency1.jar" - - "BOOT-INF/lib/dependency2.jar" - - "application": - - "BOOT-INF/classes/" - - "META-INF/" ----- diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features.adoc deleted file mode 100644 index 55d96c714ec8..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features.adoc +++ /dev/null @@ -1,41 +0,0 @@ -[[features]] -= Core Features -include::attributes.adoc[] - - - -This section dives into the details of Spring Boot. -Here you can learn about the key features that you may want to use and customize. -If you have not already done so, you might want to read the "<>" and "<>" sections, so that you have a good grounding of the basics. - - - -include::features/spring-application.adoc[] - -include::features/external-config.adoc[] - -include::features/profiles.adoc[] - -include::features/logging.adoc[] - -include::features/internationalization.adoc[] - -include::features/aop.adoc[] - -include::features/json.adoc[] - -include::features/task-execution-and-scheduling.adoc[] - -include::features/testing.adoc[] - -include::features/docker-compose.adoc[] - -include::features/testcontainers.adoc[] - -include::features/developing-auto-configuration.adoc[] - -include::features/kotlin.adoc[] - -include::features/ssl.adoc[] - -include::features/whats-next.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/aop.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/aop.adoc deleted file mode 100644 index ad7dcc6c4f7b..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/aop.adoc +++ /dev/null @@ -1,9 +0,0 @@ -[[features.aop]] -== Aspect-Oriented Programming -Spring Boot provides auto-configuration for aspect-oriented programming (AOP). -You can learn more about AOP with Spring in the {spring-framework-docs}/core/aop-api.html[Spring Framework reference documentation]. - -By default, Spring Boot's auto-configuration configures Spring AOP to use CGLib proxies. -To use JDK proxies instead, set `configprop:spring.aop.proxy-target-class` to `false`. - -If AspectJ is on the classpath, Spring Boot's auto-configuration will automatically enable AspectJ auto proxy such that `@EnableAspectJAutoProxy` is not required. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/docker-compose.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/docker-compose.adoc deleted file mode 100644 index 35920a67aae9..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/docker-compose.adoc +++ /dev/null @@ -1,248 +0,0 @@ -[[features.docker-compose]] -== Docker Compose Support -Docker Compose is a popular technology that can be used to define and manage multiple containers for services that your application needs. -A `compose.yml` file is typically created next to your application which defines and configures service containers. - -A typical workflow with Docker Compose is to run `docker compose up`, work on your application with it connecting to started services, then run `docker compose down` when you are finished. - -The `spring-boot-docker-compose` module can be included in a project to provide support for working with containers using Docker Compose. -Add the module dependency to your build, as shown in the following listings for Maven and Gradle: - -.Maven -[source,xml,indent=0,subs="verbatim"] ----- - - - org.springframework.boot - spring-boot-docker-compose - true - - ----- - -.Gradle -[source,gradle,indent=0,subs="verbatim"] ----- - dependencies { - developmentOnly("org.springframework.boot:spring-boot-docker-compose") - } ----- - -When this module is included as a dependency Spring Boot will do the following: - -* Search for a `compose.yml` and other common compose filenames in your working directory -* Call `docker compose up` with the discovered `compose.yml` -* Create service connection beans for each supported container -* Call `docker compose stop` when the application is shutdown - -If the Docker Compose services are already running when starting the application, Spring Boot will only create the service connection beans for each supported container. -It will not call `docker compose up` again and it will not call `docker compose stop` when the application is shutdown. - -NOTE: By default, Spring Boot's Docker Compose support is disabled when running tests. -To enable it, set configprop:spring.docker.compose.skip.in-tests[] to `false`. - -TIP: Repackaged archives do not contain Spring Boot's Docker Compose by default. -If you want to use this support, you need to include it. -When using the Maven plugin, set the `excludeDockerCompose` property to `false`. -When using the Gradle plugin, {spring-boot-gradle-plugin-docs}#packaging-executable-configuring-including-development-only-dependencies[configure the task's classpath to include the `developmentOnly` configuration]. - - - -[[features.docker-compose.prerequisites]] -=== Prerequisites -You need to have the `docker` and `docker compose` (or `docker-compose`) CLI applications on your path. -The minimum supported Docker Compose version is 2.2.0. - - - -[[features.docker-compose.service-connections]] -=== Service Connections -A service connection is a connection to any remote service. -Spring Boot’s auto-configuration can consume the details of a service connection and use them to establish a connection to a remote service. -When doing so, the connection details take precedence over any connection-related configuration properties. - -When using Spring Boot’s Docker Compose support, service connections are established to the port mapped by the container. - -NOTE: Docker compose is usually used in such a way that the ports inside the container are mapped to ephemeral ports on your computer. -For example, a Postgres server may run inside the container using port 5432 but be mapped to a totally different port locally. -The service connection will always discover and use the locally mapped port. - -Service connections are established by using the image name of the container. -The following service connections are currently supported: - - -|=== -| Connection Details | Matched on - -| `CassandraConnectionDetails` -| Containers named "cassandra" - -| `ElasticsearchConnectionDetails` -| Containers named "elasticsearch" - -| `JdbcConnectionDetails` -| Containers named "gvenzl/oracle-xe", "mariadb", "mssql/server", "mysql", or "postgres" - -| `MongoConnectionDetails` -| Containers named "mongo" - -| `R2dbcConnectionDetails` -| Containers named "gvenzl/oracle-xe", "mariadb", "mssql/server", "mysql", or "postgres" - -| `RabbitConnectionDetails` -| Containers named "rabbitmq" - -| `RedisConnectionDetails` -| Containers named "redis" - -| `ZipkinConnectionDetails` -| Containers named "openzipkin/zipkin". -|=== - - - -[[features.docker-compose.custom-images]] -=== Custom Images -Sometimes you may need to use your own version of an image to provide a service. -You can use any custom image as long as it behaves in the same way as the standard image. -Specifically, any environment variables that the standard image supports must also be used in your custom image. - -If your image uses a different name, you can use a label in your `compose.yml` file so that Spring Boot can provide a service connection. -Use a label named `org.springframework.boot.service-connection` to provide the service name. - -For example: - -[source,yaml,indent=0] ----- - services: - redis: - image: 'mycompany/mycustomredis:7.0' - ports: - - '6379' - labels: - org.springframework.boot.service-connection: redis ----- - - - -[[features.docker-compose.skipping]] -=== Skipping Specific Containers -If you have a container image defined in your `compose.yml` that you don’t want connected to your application you can use a label to ignore it. -Any container with labeled with `org.springframework.boot.ignore` will be ignored by Spring Boot. - -For example: - -[source,yaml,indent=0] ----- - services: - redis: - image: 'redis:7.0' - ports: - - '6379' - labels: - org.springframework.boot.ignore: true ----- - - - -[[features.docker-compose.specific-file]] -=== Using a Specific Compose File -If your compose file is not in the same directory as your application, or if it’s named differently, you can use configprop:spring.docker.compose.file[] in your `application.properties` or `application.yaml` to point to a different file. -Properties can be defined as an exact path or a path that’s relative to your application. - -For example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - docker: - compose: - file: "../my-compose.yml" ----- - - - -[[features.docker-compose.readiness]] -=== Waiting for Container Readiness -Containers started by Docker Compose may take some time to become fully ready. -The recommended way of checking for readiness is to add a `healthcheck` section under the service definition in your `compose.yml` file. - -Since it's not uncommon for `healthcheck` configuration to be omitted from `compose.yml` files, Spring Boot also checks directly for service readiness. -By default, a container is considered ready when a TCP/IP connection can be established to its mapped port. - -You can disable this on a per-container basis by adding a `org.springframework.boot.readiness-check.tcp.disable` label in your `compose.yml` file. - -For example: - -[source,yaml,indent=0] ----- - services: - redis: - image: 'redis:7.0' - ports: - - '6379' - labels: - org.springframework.boot.readiness-check.tcp.disable: true ----- - -You can also change timeout values in your `application.properties` or `application.yaml` file: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - docker: - compose: - readiness: - tcp: - connect-timeout: 10s - read-timeout: 5s ----- - -The overall timeout can be configured using configprop:spring.docker.compose.readiness.timeout[]. - - - -[[features.docker-compose.lifecycle]] -=== Controlling the Docker Compose Lifecycle -By default Spring Boot calls `docker compose up` when your application starts and `docker compose stop` when it's shut down. -If you prefer to have different lifecycle management you can use the configprop:spring.docker.compose.lifecycle-management[] property. - -The following values are supported: - -* `none` - Do not start or stop Docker Compose -* `start-only` - Start Docker Compose when the application starts and leave it running -* `start-and-stop` - Start Docker Compose when the application starts and stop it when the JVM exits - -In addition you can use the configprop:spring.docker.compose.start.command[] property to change whether `docker compose up` or `docker compose start` is used. -The configprop:spring.docker.compose.stop.command[] allows you to configure if `docker compose down` or `docker compose stop` is used. - -The following example shows how lifecycle management can be configured: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - docker: - compose: - lifecycle-management: start-and-stop - start: - command: start - stop: - command: down - timeout: 1m ----- - - - -[[features.docker-compose.profiles]] -=== Activating Docker Compose Profiles -Docker Compose profiles are similar to Spring profiles in that they let you adjust your Docker Compose configuration for specific environments. -If you want to activate a specific Docker Compose profile you can use the configprop:spring.docker.compose.profiles.active[] property in your `application.properties` or `application.yaml` file: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - docker: - compose: - profiles: - active: "myprofile" ----- diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/internationalization.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/internationalization.adoc deleted file mode 100644 index bc0b7c43a48d..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/internationalization.adoc +++ /dev/null @@ -1,22 +0,0 @@ -[[features.internationalization]] -== Internationalization -Spring Boot supports localized messages so that your application can cater to users of different language preferences. -By default, Spring Boot looks for the presence of a `messages` resource bundle at the root of the classpath. - -NOTE: The auto-configuration applies when the default properties file for the configured resource bundle is available (`messages.properties` by default). -If your resource bundle contains only language-specific properties files, you are required to add the default. -If no properties file is found that matches any of the configured base names, there will be no auto-configured `MessageSource`. - -The basename of the resource bundle as well as several other attributes can be configured using the `spring.messages` namespace, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - messages: - basename: "messages,config.i18n.messages" - fallback-to-system-locale: false ----- - -TIP: `spring.messages.basename` supports comma-separated list of locations, either a package qualifier or a resource resolved from the classpath root. - -See {spring-boot-autoconfigure-module-code}/context/MessageSourceProperties.java[`MessageSourceProperties`] for more supported options. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/json.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/json.adoc deleted file mode 100644 index 0170a669d97a..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/json.adoc +++ /dev/null @@ -1,64 +0,0 @@ -[[features.json]] -== JSON -Spring Boot provides integration with three JSON mapping libraries: - -- Gson -- Jackson -- JSON-B - -Jackson is the preferred and default library. - - - -[[features.json.jackson]] -=== Jackson -Auto-configuration for Jackson is provided and Jackson is part of `spring-boot-starter-json`. -When Jackson is on the classpath an `ObjectMapper` bean is automatically configured. -Several configuration properties are provided for <>. - - - -[[features.json.jackson.custom-serializers-and-deserializers]] -==== Custom Serializers and Deserializers -If you use Jackson to serialize and deserialize JSON data, you might want to write your own `JsonSerializer` and `JsonDeserializer` classes. -Custom serializers are usually https://github.com/FasterXML/jackson-docs/wiki/JacksonHowToCustomSerializers[registered with Jackson through a module], but Spring Boot provides an alternative `@JsonComponent` annotation that makes it easier to directly register Spring Beans. - -You can use the `@JsonComponent` annotation directly on `JsonSerializer`, `JsonDeserializer` or `KeyDeserializer` implementations. -You can also use it on classes that contain serializers/deserializers as inner classes, as shown in the following example: - -include::code:MyJsonComponent[] - -All `@JsonComponent` beans in the `ApplicationContext` are automatically registered with Jackson. -Because `@JsonComponent` is meta-annotated with `@Component`, the usual component-scanning rules apply. - -Spring Boot also provides {spring-boot-module-code}/jackson/JsonObjectSerializer.java[`JsonObjectSerializer`] and {spring-boot-module-code}/jackson/JsonObjectDeserializer.java[`JsonObjectDeserializer`] base classes that provide useful alternatives to the standard Jackson versions when serializing objects. -See {spring-boot-module-api}/jackson/JsonObjectSerializer.html[`JsonObjectSerializer`] and {spring-boot-module-api}/jackson/JsonObjectDeserializer.html[`JsonObjectDeserializer`] in the Javadoc for details. - -The example above can be rewritten to use `JsonObjectSerializer`/`JsonObjectDeserializer` as follows: - -include::code:object/MyJsonComponent[] - - - -[[features.json.jackson.mixins]] -==== Mixins -Jackson has support for mixins that can be used to mix additional annotations into those already declared on a target class. -Spring Boot's Jackson auto-configuration will scan your application's packages for classes annotated with `@JsonMixin` and register them with the auto-configured `ObjectMapper`. -The registration is performed by Spring Boot's `JsonMixinModule`. - - - -[[features.json.gson]] -=== Gson -Auto-configuration for Gson is provided. -When Gson is on the classpath a `Gson` bean is automatically configured. -Several `+spring.gson.*+` configuration properties are provided for customizing the configuration. -To take more control, one or more `GsonBuilderCustomizer` beans can be used. - - - -[[features.json.json-b]] -=== JSON-B -Auto-configuration for JSON-B is provided. -When the JSON-B API and an implementation are on the classpath a `Jsonb` bean will be automatically configured. -The preferred JSON-B implementation is Eclipse Yasson for which dependency management is provided. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/logging.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/logging.adoc deleted file mode 100644 index 20572e63276e..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/logging.adoc +++ /dev/null @@ -1,560 +0,0 @@ -[[features.logging]] -== Logging -Spring Boot uses https://commons.apache.org/logging[Commons Logging] for all internal logging but leaves the underlying log implementation open. -Default configurations are provided for {java-api}/java.logging/java/util/logging/package-summary.html[Java Util Logging], https://logging.apache.org/log4j/2.x/[Log4j2], and https://logback.qos.ch/[Logback]. -In each case, loggers are pre-configured to use console output with optional file output also available. - -By default, if you use the "`Starters`", Logback is used for logging. -Appropriate Logback routing is also included to ensure that dependent libraries that use Java Util Logging, Commons Logging, Log4J, or SLF4J all work correctly. - -TIP: There are a lot of logging frameworks available for Java. -Do not worry if the above list seems confusing. -Generally, you do not need to change your logging dependencies and the Spring Boot defaults work just fine. - -TIP: When you deploy your application to a servlet container or application server, logging performed with the Java Util Logging API is not routed into your application's logs. -This prevents logging performed by the container or other applications that have been deployed to it from appearing in your application's logs. - - - -[[features.logging.log-format]] -=== Log Format -The default log output from Spring Boot resembles the following example: - -[indent=0] ----- -include::{logging-format-output}[] ----- - -The following items are output: - -* Date and Time: Millisecond precision and easily sortable. -* Log Level: `ERROR`, `WARN`, `INFO`, `DEBUG`, or `TRACE`. -* Process ID. -* A `---` separator to distinguish the start of actual log messages. -* Thread name: Enclosed in square brackets (may be truncated for console output). -* Logger name: This is usually the source class name (often abbreviated). -* The log message. - -NOTE: Logback does not have a `FATAL` level. -It is mapped to `ERROR`. - - - -[[features.logging.console-output]] -=== Console Output -The default log configuration echoes messages to the console as they are written. -By default, `ERROR`-level, `WARN`-level, and `INFO`-level messages are logged. -You can also enable a "`debug`" mode by starting your application with a `--debug` flag. - -[source,shell,indent=0,subs="verbatim"] ----- - $ java -jar myapp.jar --debug ----- - -NOTE: You can also specify `debug=true` in your `application.properties`. - -When the debug mode is enabled, a selection of core loggers (embedded container, Hibernate, and Spring Boot) are configured to output more information. -Enabling the debug mode does _not_ configure your application to log all messages with `DEBUG` level. - -Alternatively, you can enable a "`trace`" mode by starting your application with a `--trace` flag (or `trace=true` in your `application.properties`). -Doing so enables trace logging for a selection of core loggers (embedded container, Hibernate schema generation, and the whole Spring portfolio). - - - -[[features.logging.console-output.color-coded]] -==== Color-coded Output -If your terminal supports ANSI, color output is used to aid readability. -You can set `spring.output.ansi.enabled` to a {spring-boot-module-api}/ansi/AnsiOutput.Enabled.html[supported value] to override the auto-detection. - -Color coding is configured by using the `%clr` conversion word. -In its simplest form, the converter colors the output according to the log level, as shown in the following example: - -[source,indent=0,subs="verbatim"] ----- -%clr(%5p) ----- - -The following table describes the mapping of log levels to colors: - -|=== -| Level | Color - -| `FATAL` -| Red - -| `ERROR` -| Red - -| `WARN` -| Yellow - -| `INFO` -| Green - -| `DEBUG` -| Green - -| `TRACE` -| Green -|=== - -Alternatively, you can specify the color or style that should be used by providing it as an option to the conversion. -For example, to make the text yellow, use the following setting: - -[source,indent=0,subs="verbatim"] ----- - %clr(%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}){yellow} ----- - -The following colors and styles are supported: - -* `blue` -* `cyan` -* `faint` -* `green` -* `magenta` -* `red` -* `yellow` - - - -[[features.logging.file-output]] -=== File Output -By default, Spring Boot logs only to the console and does not write log files. -If you want to write log files in addition to the console output, you need to set a configprop:logging.file.name[] or configprop:logging.file.path[] property (for example, in your `application.properties`). - -The following table shows how the `logging.*` properties can be used together: - -.Logging properties -[cols="1,1,1,4"] -|=== -| configprop:logging.file.name[] | configprop:logging.file.path[] | Example | Description - -| _(none)_ -| _(none)_ -| -| Console only logging. - -| Specific file -| _(none)_ -| `my.log` -| Writes to the specified log file. - Names can be an exact location or relative to the current directory. - -| _(none)_ -| Specific directory -| `/var/log` -| Writes `spring.log` to the specified directory. - Names can be an exact location or relative to the current directory. -|=== - -Log files rotate when they reach 10 MB and, as with console output, `ERROR`-level, `WARN`-level, and `INFO`-level messages are logged by default. - -TIP: Logging properties are independent of the actual logging infrastructure. -As a result, specific configuration keys (such as `logback.configurationFile` for Logback) are not managed by spring Boot. - - - -[[features.logging.file-rotation]] -=== File Rotation -If you are using the Logback, it is possible to fine-tune log rotation settings using your `application.properties` or `application.yaml` file. -For all other logging system, you will need to configure rotation settings directly yourself (for example, if you use Log4j2 then you could add a `log4j2.xml` or `log4j2-spring.xml` file). - -The following rotation policy properties are supported: - -|=== -| Name | Description - -| configprop:logging.logback.rollingpolicy.file-name-pattern[] -| The filename pattern used to create log archives. - -| configprop:logging.logback.rollingpolicy.clean-history-on-start[] -| If log archive cleanup should occur when the application starts. - -| configprop:logging.logback.rollingpolicy.max-file-size[] -| The maximum size of log file before it is archived. - -| configprop:logging.logback.rollingpolicy.total-size-cap[] -| The maximum amount of size log archives can take before being deleted. - -| configprop:logging.logback.rollingpolicy.max-history[] -| The maximum number of archive log files to keep (defaults to 7). -|=== - - - -[[features.logging.log-levels]] -=== Log Levels -All the supported logging systems can have the logger levels set in the Spring `Environment` (for example, in `application.properties`) by using `+logging.level.=+` where `level` is one of TRACE, DEBUG, INFO, WARN, ERROR, FATAL, or OFF. -The `root` logger can be configured by using `logging.level.root`. - -The following example shows potential logging settings in `application.properties`: - -[source,properties,indent=0,subs="verbatim",configprops,role="primary"] -.Properties ----- - logging.level.root=warn - logging.level.org.springframework.web=debug - logging.level.org.hibernate=error ----- - -[source,properties,indent=0,subs="verbatim",role="secondary"] -.Yaml ----- - logging: - level: - root: "warn" - org.springframework.web: "debug" - org.hibernate: "error" ----- - -It is also possible to set logging levels using environment variables. -For example, `LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_WEB=DEBUG` will set `org.springframework.web` to `DEBUG`. - -NOTE: The above approach will only work for package level logging. -Since relaxed binding always converts environment variables to lowercase, it is not possible to configure logging for an individual class in this way. -If you need to configure logging for a class, you can use <> variable. - - - -[[features.logging.log-groups]] -=== Log Groups -It is often useful to be able to group related loggers together so that they can all be configured at the same time. -For example, you might commonly change the logging levels for _all_ Tomcat related loggers, but you can not easily remember top level packages. - -To help with this, Spring Boot allows you to define logging groups in your Spring `Environment`. -For example, here is how you could define a "`tomcat`" group by adding it to your `application.properties`: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - logging: - group: - tomcat: "org.apache.catalina,org.apache.coyote,org.apache.tomcat" ----- - -Once defined, you can change the level for all the loggers in the group with a single line: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - logging: - level: - tomcat: "trace" ----- - -Spring Boot includes the following pre-defined logging groups that can be used out-of-the-box: - -[cols="1,4"] -|=== -| Name | Loggers - -| web -| `org.springframework.core.codec`, `org.springframework.http`, `org.springframework.web`, `org.springframework.boot.actuate.endpoint.web`, `org.springframework.boot.web.servlet.ServletContextInitializerBeans` - -| sql -| `org.springframework.jdbc.core`, `org.hibernate.SQL`, `org.jooq.tools.LoggerListener` -|=== - - - -[[features.logging.shutdown-hook]] -=== Using a Log Shutdown Hook -In order to release logging resources when your application terminates, a shutdown hook that will trigger log system cleanup when the JVM exits is provided. -This shutdown hook is registered automatically unless your application is deployed as a war file. -If your application has complex context hierarchies the shutdown hook may not meet your needs. -If it does not, disable the shutdown hook and investigate the options provided directly by the underlying logging system. -For example, Logback offers https://logback.qos.ch/manual/loggingSeparation.html[context selectors] which allow each Logger to be created in its own context. -You can use the configprop:logging.register-shutdown-hook[] property to disable the shutdown hook. -Setting it to `false` will disable the registration. -You can set the property in your `application.properties` or `application.yaml` file: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - logging: - register-shutdown-hook: false ----- - - - -[[features.logging.custom-log-configuration]] -=== Custom Log Configuration -The various logging systems can be activated by including the appropriate libraries on the classpath and can be further customized by providing a suitable configuration file in the root of the classpath or in a location specified by the following Spring `Environment` property: configprop:logging.config[]. - -You can force Spring Boot to use a particular logging system by using the `org.springframework.boot.logging.LoggingSystem` system property. -The value should be the fully qualified class name of a `LoggingSystem` implementation. -You can also disable Spring Boot's logging configuration entirely by using a value of `none`. - -NOTE: Since logging is initialized *before* the `ApplicationContext` is created, it is not possible to control logging from `@PropertySources` in Spring `@Configuration` files. -The only way to change the logging system or disable it entirely is through System properties. - -Depending on your logging system, the following files are loaded: - -|=== -| Logging System | Customization - -| Logback -| `logback-spring.xml`, `logback-spring.groovy`, `logback.xml`, or `logback.groovy` - -| Log4j2 -| `log4j2-spring.xml` or `log4j2.xml` - -| JDK (Java Util Logging) -| `logging.properties` -|=== - -NOTE: When possible, we recommend that you use the `-spring` variants for your logging configuration (for example, `logback-spring.xml` rather than `logback.xml`). -If you use standard configuration locations, Spring cannot completely control log initialization. - -WARNING: There are known classloading issues with Java Util Logging that cause problems when running from an 'executable jar'. -We recommend that you avoid it when running from an 'executable jar' if at all possible. - -To help with the customization, some other properties are transferred from the Spring `Environment` to System properties. -This allows the properties to be consumed by logging system configuration. For example, setting `logging.file.name` in `application.properties` or `LOGGING_FILE_NAME` as an environment variable will result in the `LOG_FILE` System property being set. -The properties that are transferred are described in the following table: - -|=== -| Spring Environment | System Property | Comments - -| configprop:logging.exception-conversion-word[] -| `LOG_EXCEPTION_CONVERSION_WORD` -| The conversion word used when logging exceptions. - -| configprop:logging.file.name[] -| `LOG_FILE` -| If defined, it is used in the default log configuration. - -| configprop:logging.file.path[] -| `LOG_PATH` -| If defined, it is used in the default log configuration. - -| configprop:logging.pattern.console[] -| `CONSOLE_LOG_PATTERN` -| The log pattern to use on the console (stdout). - -| configprop:logging.pattern.dateformat[] -| `LOG_DATEFORMAT_PATTERN` -| Appender pattern for log date format. - -| configprop:logging.charset.console[] -| `CONSOLE_LOG_CHARSET` -| The charset to use for console logging. - -| configprop:logging.threshold.console[] -| `CONSOLE_LOG_THRESHOLD` -| The log level threshold to use for console logging. - -| configprop:logging.pattern.file[] -| `FILE_LOG_PATTERN` -| The log pattern to use in a file (if `LOG_FILE` is enabled). - -| configprop:logging.charset.file[] -| `FILE_LOG_CHARSET` -| The charset to use for file logging (if `LOG_FILE` is enabled). - -| configprop:logging.threshold.file[] -| `FILE_LOG_THRESHOLD` -| The log level threshold to use for file logging. - -| configprop:logging.pattern.level[] -| `LOG_LEVEL_PATTERN` -| The format to use when rendering the log level (default `%5p`). - -| `PID` -| `PID` -| The current process ID (discovered if possible and when not already defined as an OS environment variable). -|=== - -If you use Logback, the following properties are also transferred: - -|=== -| Spring Environment | System Property | Comments - -| configprop:logging.logback.rollingpolicy.file-name-pattern[] -| `LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN` -| Pattern for rolled-over log file names (default `$\{LOG_FILE}.%d\{yyyy-MM-dd}.%i.gz`). - -| configprop:logging.logback.rollingpolicy.clean-history-on-start[] -| `LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START` -| Whether to clean the archive log files on startup. - -| configprop:logging.logback.rollingpolicy.max-file-size[] -| `LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE` -| Maximum log file size. - -| configprop:logging.logback.rollingpolicy.total-size-cap[] -| `LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP` -| Total size of log backups to be kept. - -| configprop:logging.logback.rollingpolicy.max-history[] -| `LOGBACK_ROLLINGPOLICY_MAX_HISTORY` -| Maximum number of archive log files to keep. -|=== - - -All the supported logging systems can consult System properties when parsing their configuration files. -See the default configurations in `spring-boot.jar` for examples: - -* {spring-boot-code}/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml[Logback] -* {spring-boot-code}/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml[Log4j 2] -* {spring-boot-code}/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/java/logging-file.properties[Java Util logging] - -[TIP] -==== -If you want to use a placeholder in a logging property, you should use <> and not the syntax of the underlying framework. -Notably, if you use Logback, you should use `:` as the delimiter between a property name and its default value and not use `:-`. -==== - -[TIP] -==== -You can add MDC and other ad-hoc content to log lines by overriding only the `LOG_LEVEL_PATTERN` (or `logging.pattern.level` with Logback). -For example, if you use `logging.pattern.level=user:%X\{user} %5p`, then the default log format contains an MDC entry for "user", if it exists, as shown in the following example. - -[indent=0] ----- - 2019-08-30 12:30:04.031 user:someone INFO 22174 --- [ nio-8080-exec-0] demo.Controller - Handling authenticated request ----- -==== - - - -[[features.logging.logback-extensions]] -=== Logback Extensions -Spring Boot includes a number of extensions to Logback that can help with advanced configuration. -You can use these extensions in your `logback-spring.xml` configuration file. - -NOTE: Because the standard `logback.xml` configuration file is loaded too early, you cannot use extensions in it. -You need to either use `logback-spring.xml` or define a configprop:logging.config[] property. - -WARNING: The extensions cannot be used with Logback's https://logback.qos.ch/manual/configuration.html#autoScan[configuration scanning]. -If you attempt to do so, making changes to the configuration file results in an error similar to one of the following being logged: - -[indent=0] ----- - ERROR in ch.qos.logback.core.joran.spi.Interpreter@4:71 - no applicable action for [springProperty], current ElementPath is [[configuration][springProperty]] - ERROR in ch.qos.logback.core.joran.spi.Interpreter@4:71 - no applicable action for [springProfile], current ElementPath is [[configuration][springProfile]] ----- - - - -[[features.logging.logback-extensions.profile-specific]] -==== Profile-specific Configuration -The `` tag lets you optionally include or exclude sections of configuration based on the active Spring profiles. -Profile sections are supported anywhere within the `` element. -Use the `name` attribute to specify which profile accepts the configuration. -The `` tag can contain a profile name (for example `staging`) or a profile expression. -A profile expression allows for more complicated profile logic to be expressed, for example `production & (eu-central | eu-west)`. -Check the {spring-framework-docs}/core/beans/environment.html#beans-definition-profiles-java[Spring Framework reference guide] for more details. -The following listing shows three sample profiles: - -[source,xml,subs="verbatim",indent=0] ----- - - - - - - - - - - - ----- - - - -[[features.logging.logback-extensions.environment-properties]] -==== Environment Properties -The `` tag lets you expose properties from the Spring `Environment` for use within Logback. -Doing so can be useful if you want to access values from your `application.properties` file in your Logback configuration. -The tag works in a similar way to Logback's standard `` tag. -However, rather than specifying a direct `value`, you specify the `source` of the property (from the `Environment`). -If you need to store the property somewhere other than in `local` scope, you can use the `scope` attribute. -If you need a fallback value (in case the property is not set in the `Environment`), you can use the `defaultValue` attribute. -The following example shows how to expose properties for use within Logback: - -[source,xml,subs="verbatim",indent=0] ----- - - - ${fluentHost} - ... - ----- - -NOTE: The `source` must be specified in kebab case (such as `my.property-name`). -However, properties can be added to the `Environment` by using the relaxed rules. - - - -[[features.logging.log4j2-extensions]] -=== Log4j2 Extensions -Spring Boot includes a number of extensions to Log4j2 that can help with advanced configuration. -You can use these extensions in any `log4j2-spring.xml` configuration file. - -NOTE: Because the standard `log4j2.xml` configuration file is loaded too early, you cannot use extensions in it. -You need to either use `log4j2-spring.xml` or define a configprop:logging.config[] property. - -NOTE: The extensions supersede the https://logging.apache.org/log4j/2.x/log4j-spring-boot/index.html[Spring Boot support] provided by Log4J. -You should make sure not to include the `org.apache.logging.log4j:log4j-spring-boot` module in your build. - - - -[[features.logging.log4j2-extensions.profile-specific]] -==== Profile-specific Configuration -The `` tag lets you optionally include or exclude sections of configuration based on the active Spring profiles. -Profile sections are supported anywhere within the `` element. -Use the `name` attribute to specify which profile accepts the configuration. -The `` tag can contain a profile name (for example `staging`) or a profile expression. -A profile expression allows for more complicated profile logic to be expressed, for example `production & (eu-central | eu-west)`. -Check the {spring-framework-docs}/core/beans/environment.html#beans-definition-profiles-java[Spring Framework reference guide] for more details. -The following listing shows three sample profiles: - -[source,xml,subs="verbatim",indent=0] ----- - - - - - - - - - - - ----- - - - -[[features.logging.log4j2-extensions.environment-properties-lookup]] -==== Environment Properties Lookup -If you want to refer to properties from your Spring `Environment` within your Log4j2 configuration you can use `spring:` prefixed https://logging.apache.org/log4j/2.x/manual/lookups.html[lookups]. -Doing so can be useful if you want to access values from your `application.properties` file in your Log4j2 configuration. - -The following example shows how to set a Log4j2 property named `applicationName` that reads `spring.application.name` from the Spring `Environment`: - -[source,xml,subs="verbatim",indent=0] ----- - - ${spring:spring.application.name} - ----- - -NOTE: The lookup key should be specified in kebab case (such as `my.property-name`). - - - -[[features.logging.log4j2-extensions.environment-property-source]] -==== Log4j2 System Properties -Log4j2 supports a number of https://logging.apache.org/log4j/2.x/manual/configuration.html#SystemProperties[System Properties] that can be used to configure various items. -For example, the `log4j2.skipJansi` system property can be used to configure if the `ConsoleAppender` will try to use a https://github.com/fusesource/jansi[Jansi] output stream on Windows. - -All system properties that are loaded after the Log4j2 initialization can be obtained from the Spring `Environment`. -For example, you could add `log4j2.skipJansi=false` to your `application.properties` file to have the `ConsoleAppender` use Jansi on Windows. - -NOTE: The Spring `Environment` is only considered when system properties and OS environment variables do not contain the value being loaded. - -WARNING: System properties that are loaded during early Log4j2 initialization cannot reference the Spring `Environment`. -For example, the property Log4j2 uses to allow the default Log4j2 implementation to be chosen is used before the Spring Environment is available. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/profiles.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/profiles.adoc deleted file mode 100644 index b76554a076d9..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/profiles.adoc +++ /dev/null @@ -1,119 +0,0 @@ -[[features.profiles]] -== Profiles -Spring Profiles provide a way to segregate parts of your application configuration and make it be available only in certain environments. -Any `@Component`, `@Configuration` or `@ConfigurationProperties` can be marked with `@Profile` to limit when it is loaded, as shown in the following example: - -include::code:ProductionConfiguration[] - -NOTE: If `@ConfigurationProperties` beans are registered through `@EnableConfigurationProperties` instead of automatic scanning, the `@Profile` annotation needs to be specified on the `@Configuration` class that has the `@EnableConfigurationProperties` annotation. -In the case where `@ConfigurationProperties` are scanned, `@Profile` can be specified on the `@ConfigurationProperties` class itself. - -You can use a configprop:spring.profiles.active[] `Environment` property to specify which profiles are active. -You can specify the property in any of the ways described earlier in this chapter. -For example, you could include it in your `application.properties`, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - profiles: - active: "dev,hsqldb" ----- - -You could also specify it on the command line by using the following switch: `--spring.profiles.active=dev,hsqldb`. - -If no profile is active, a default profile is enabled. -The name of the default profile is `default` and it can be tuned using the configprop:spring.profiles.default[] `Environment` property, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - profiles: - default: "none" ----- - -`spring.profiles.active` and `spring.profiles.default` can only be used in non-profile specific documents. -This means they cannot be included in <> or <> by `spring.config.activate.on-profile`. - -For example, the second document configuration is invalid: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - # this document is valid - spring: - profiles: - active: "prod" - --- - # this document is invalid - spring: - config: - activate: - on-profile: "prod" - profiles: - active: "metrics" ----- - - - -[[features.profiles.adding-active-profiles]] -=== Adding Active Profiles -The configprop:spring.profiles.active[] property follows the same ordering rules as other properties: The highest `PropertySource` wins. -This means that you can specify active profiles in `application.properties` and then *replace* them by using the command line switch. - -Sometimes, it is useful to have properties that *add* to the active profiles rather than replace them. -The `spring.profiles.include` property can be used to add active profiles on top of those activated by the configprop:spring.profiles.active[] property. -The `SpringApplication` entry point also has a Java API for setting additional profiles. -See the `setAdditionalProfiles()` method in {spring-boot-module-api}/SpringApplication.html[SpringApplication]. - -For example, when an application with the following properties is run, the common and local profiles will be activated even when it runs using the `--spring.profiles.active` switch: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - profiles: - include: - - "common" - - "local" ----- - -WARNING: Similar to `spring.profiles.active`, `spring.profiles.include` can only be used in non-profile specific documents. -This means it cannot be included in <> or <> by `spring.config.activate.on-profile`. - -Profile groups, which are described in the <> can also be used to add active profiles if a given profile is active. - - - -[[features.profiles.groups]] -=== Profile Groups -Occasionally the profiles that you define and use in your application are too fine-grained and become cumbersome to use. -For example, you might have `proddb` and `prodmq` profiles that you use to enable database and messaging features independently. - -To help with this, Spring Boot lets you define profile groups. -A profile group allows you to define a logical name for a related group of profiles. - -For example, we can create a `production` group that consists of our `proddb` and `prodmq` profiles. - -[source,yaml,indent=0,subs="verbatim",configblocks] ----- - spring: - profiles: - group: - production: - - "proddb" - - "prodmq" ----- - -Our application can now be started using `--spring.profiles.active=production` to activate the `production`, `proddb` and `prodmq` profiles in one hit. - - - -[[features.profiles.programmatically-setting-profiles]] -=== Programmatically Setting Profiles -You can programmatically set active profiles by calling `SpringApplication.setAdditionalProfiles(...)` before your application runs. -It is also possible to activate profiles by using Spring's `ConfigurableEnvironment` interface. - - - -[[features.profiles.profile-specific-configuration-files]] -=== Profile-specific Configuration Files -Profile-specific variants of both `application.properties` (or `application.yaml`) and files referenced through `@ConfigurationProperties` are considered as files and loaded. -See "<>" for details. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/ssl.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/ssl.adoc deleted file mode 100644 index b5ffd5de590a..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/ssl.adoc +++ /dev/null @@ -1,134 +0,0 @@ -[[features.ssl]] -== SSL -Spring Boot provides the ability to configure SSL trust material that can be applied to several types of connections in order to support secure communications. -Configuration properties with the prefix `spring.ssl.bundle` can be used to specify named sets of trust material and associated information. - - - -[[features.ssl.jks]] -=== Configuring SSL With Java KeyStore Files -Configuration properties with the prefix `spring.ssl.bundle.jks` can be used to configure bundles of trust material created with the Java `keytool` utility and stored in Java KeyStore files in the JKS or PKCS12 format. -Each bundle has a user-provided name that can be used to reference the bundle. - -When used to secure an embedded web server, a `keystore` is typically configured with a Java KeyStore containing a certificate and private key as shown in this example: - -[source,yaml,indent=0,subs="verbatim",configblocks] ----- - spring: - ssl: - bundle: - jks: - mybundle: - key: - alias: "application" - keystore: - location: "classpath:application.p12" - password: "secret" - type: "PKCS12" ----- - -When used to secure a client-side connection, a `truststore` is typically configured with a Java KeyStore containing the server certificate as shown in this example: - -[source,yaml,indent=0,subs="verbatim",configblocks] ----- - spring: - ssl: - bundle: - jks: - mybundle: - truststore: - location: "classpath:server.p12" - password: "secret" ----- - -See {spring-boot-autoconfigure-module-code}/ssl/JksSslBundleProperties.java[JksSslBundleProperties] for the full set of supported properties. - - - -[[features.ssl.pem]] -=== Configuring SSL With PEM-encoded Certificates -Configuration properties with the prefix `spring.ssl.bundle.pem` can be used to configure bundles of trust material in the form of PEM-encoded text. -Each bundle has a user-provided name that can be used to reference the bundle. - -When used to secure an embedded web server, a `keystore` is typically configured with a certificate and private key as shown in this example: - -[source,yaml,indent=0,subs="verbatim",configblocks] ----- - spring: - ssl: - bundle: - pem: - mybundle: - keystore: - certificate: "classpath:application.crt" - private-key: "classpath:application.key" ----- - -When used to secure a client-side connection, a `truststore` is typically configured with the server certificate as shown in this example: - -[source,yaml,indent=0,subs="verbatim",configblocks] ----- - spring: - ssl: - bundle: - pem: - mybundle: - truststore: - certificate: "classpath:server.crt" ----- - -[TIP] -==== -PEM content can be used directly for both the `certificate` and `private-key` properties. -If the property values contains `BEGIN` and `END` markers then they will be treated as PEM content rather than a resource location. - -The following example shows how a truststore certificate can be defined: - -[source,yaml,indent=0,subs="verbatim",configblocks] ----- - spring: - ssl: - bundle: - pem: - mybundle: - truststore: - certificate: | - -----BEGIN CERTIFICATE----- - MIID1zCCAr+gAwIBAgIUNM5QQv8IzVQsgSmmdPQNaqyzWs4wDQYJKoZIhvcNAQEL - BQAwezELMAkGA1UEBhMCWFgxEjAQBgNVBAgMCVN0YXRlTmFtZTERMA8GA1UEBwwI - ... - V0IJjcmYjEZbTvpjFKznvaFiOUv+8L7jHQ1/Yf+9c3C8gSjdUfv88m17pqYXd+Ds - HEmfmNNjht130UyjNCITmLVXyy5p35vWmdf95U3uEbJSnNVtXH8qRmN9oK9mUpDb - ngX6JBJI7fw7tXoqWSLHNiBODM88fUlQSho8 - -----END CERTIFICATE----- ----- -==== - -See {spring-boot-autoconfigure-module-code}/ssl/PemSslBundleProperties.java[PemSslBundleProperties] for the full set of supported properties. - - - -[[features.ssl.applying]] -=== Applying SSL Bundles -Once configured using properties, SSL bundles can be referred to by name in configuration properties for various types of connections that are auto-configured by Spring Boot. -See the sections on <>, <>, and <> for further information. - - - -[[features.ssl.bundles]] -=== Using SSL Bundles -Spring Boot auto-configures a bean of type `SslBundles` that provides access to each of the named bundles configured using the `spring.ssl.bundle` properties. - -An `SslBundle` can be retrieved from the auto-configured `SslBundles` bean and used to create objects that are used to configure SSL connectivity in client libraries. -The `SslBundle` provides a layered approach of obtaining these SSL objects: - -- `getStores()` provides access to the key store and trust store `java.security.KeyStore` instances as well as any required key store password. -- `getManagers()` provides access to the `java.net.ssl.KeyManagerFactory` and `java.net.ssl.TrustManagerFactory` instances as well as the `java.net.ssl.KeyManager` and `java.net.ssl.TrustManager` arrays that they create. -- `createSslContext()` provides a convenient way to obtain a new `java.net.ssl.SSLContext` instance. - -In addition, the `SslBundle` provides details about the key being used, the protocol to use and any option that should be applied to the SSL engine. - -The following example shows retrieving an `SslBundle` and using it to create an `SSLContext`: - -include::code:MyComponent[] - diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc deleted file mode 100644 index 71c66a419add..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc +++ /dev/null @@ -1,43 +0,0 @@ -[[features.task-execution-and-scheduling]] -== Task Execution and Scheduling -In the absence of an `Executor` bean in the context, Spring Boot auto-configures a `ThreadPoolTaskExecutor` with sensible defaults that can be automatically associated to asynchronous task execution (`@EnableAsync`) and Spring MVC asynchronous request processing. - -[TIP] -==== -If you have defined a custom `Executor` in the context, regular task execution (that is `@EnableAsync`) will use it transparently but the Spring MVC support will not be configured as it requires an `AsyncTaskExecutor` implementation (named `applicationTaskExecutor`). -Depending on your target arrangement, you could change your `Executor` into a `ThreadPoolTaskExecutor` or define both a `ThreadPoolTaskExecutor` and an `AsyncConfigurer` wrapping your custom `Executor`. - -The auto-configured `TaskExecutorBuilder` allows you to easily create instances that reproduce what the auto-configuration does by default. -==== - -The thread pool uses 8 core threads that can grow and shrink according to the load. -Those default settings can be fine-tuned using the `spring.task.execution` namespace, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - task: - execution: - pool: - max-size: 16 - queue-capacity: 100 - keep-alive: "10s" ----- - -This changes the thread pool to use a bounded queue so that when the queue is full (100 tasks), the thread pool increases to maximum 16 threads. -Shrinking of the pool is more aggressive as threads are reclaimed when they are idle for 10 seconds (rather than 60 seconds by default). - -A `ThreadPoolTaskScheduler` can also be auto-configured if need to be associated to scheduled task execution (using `@EnableScheduling` for instance). -The thread pool uses one thread by default and its settings can be fine-tuned using the `spring.task.scheduling` namespace, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - task: - scheduling: - thread-name-prefix: "scheduling-" - pool: - size: 2 ----- - -Both a `TaskExecutorBuilder` bean and a `TaskSchedulerBuilder` bean are made available in the context if a custom executor or scheduler needs to be created. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testcontainers.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testcontainers.adoc deleted file mode 100644 index f7d806bc2702..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testcontainers.adoc +++ /dev/null @@ -1,90 +0,0 @@ -[[features.testcontainers]] -== Testcontainers Support -As well as <>, it's also possible to use them at development time. -The next sections will provide more details about that. - - - -[[features.testcontainers.at-development-time]] -=== Using Testcontainers at Development Time -This approach allows developers to quickly start containers for the services that the application depends on, removing the need to manually provision things like database servers. -Using Testcontainers in this way provides functionality similar to Docker Compose, except that your container configuration is in Java rather than YAML. - -To use Testcontainers at development time you need to launch your application using your "`test`" classpath rather than "`main`". -This will allow you to access all declared test dependencies and give you a natural place to write your test configuration. - -To create a test launchable version of your application you should create an "`Application`" class in the `src/test` directory. -For example, if your main application is in `src/main/java/com/example/MyApplication.java`, you should create `src/test/java/com/example/TestMyApplication.java` - -The `TestMyApplication` class can use the `SpringApplication.from(...)` method to launch the real application: - -include::code:launch/TestMyApplication[] - -You'll also need to define the `Container` instances that you want to start along with your application. -To do this, you need to make sure that the `spring-boot-testcontainers` module has been added as a `test` dependency. -Once that has been done, you can create a `@TestConfiguration` class that declares `@Bean` methods for the containers you want to start. - -You can also annotate your `@Bean` methods with `@ServiceConnection` in order to create `ConnectionDetails` beans. -See <> section for details of the supported technologies. - -A typical Testcontainers configuration would look like this: - -include::code:test/MyContainersConfiguration[] - -NOTE: The lifecycle of `Container` beans is automatically managed by Spring Boot. -Containers will be started and stopped automatically. - -Once you have defined your test configuration, you can use the `with(...)` method to attach it to your test launcher: - -include::code:test/TestMyApplication[] - -You can now launch `TestMyApplication` as you would any regular Java `main` method application to start your application and the containers that it needs to run. - -TIP: You can use the Maven goal `spring-boot:test-run` or the Gradle task `bootTestRun` to do this from the command line. - - - -[[features.testcontainers.at-development-time.dynamic-properties]] -==== Contributing Dynamic Properties at Development Time -If you want to contribute dynamic properties at development time from your `Container` `@Bean` methods, you can do so by injecting a `DynamicPropertyRegistry`. -This works in a similar way to the <> that you can use in your tests. -It allows you to add properties that will become available once your container has started. - -A typical configuration would look like this: - -include::code:MyContainersConfiguration[] - -NOTE: Using a `@ServiceConnection` is recommended whenever possible, however, dynamic properties can be a useful fallback for technologies that don't yet have `@ServiceConnection` support. - - - -[[features.testcontainers.at-development-time.importing-container-declarations]] -==== Importing Testcontainer Declaration Classes -A common pattern when using Testcontainers is to declare `Container` instances as static fields. -Often these fields are defined directly on the test class. -They can also be declared on a parent class or on an interface that the test implements. - -For example, the following `MyContainers` interface declares `mongo` and `neo4j` containers: - -include::code:MyContainers[] - -If you already have containers defined in this way, or you just prefer this style, you can import these declaration classes rather than defining you containers as `@Bean` methods. -To do so, add the `@ImportTestcontainers` annotation to your test configuration class: - -include::code:MyContainersConfiguration[] - -TIP: If you don't intend to use the <> but want to use <> instead, remove the `@ServiceConnection` annotation from the `Container` fields. -You can also add `@DynamicPropertySource` annotated methods to your declaration class. - - - -[[features.testcontainers.at-development-time.devtools]] -==== Using DevTools with Testcontainers at Development Time -When using devtools, you can annotate beans and bean methods with `@RestartScope`. -Such beans won't be recreated when the devtools restart the application. -This is especially useful for Testcontainer `Container` beans, as they keep their state despite the application restart. - -include::code:MyContainersConfiguration[] - -WARNING: If you're using Gradle and want to use this feature, you need to change the configuration of the `spring-boot-devtools` dependency from `developmentOnly` to `testImplementation`. -With the default scope of `developmentOnly`, the `bootTestRun` task will not pick up changes in your code, as the devtools are not active. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc deleted file mode 100644 index e96aef405cd3..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc +++ /dev/null @@ -1,1102 +0,0 @@ -[[features.testing]] -== Testing -Spring Boot provides a number of utilities and annotations to help when testing your application. -Test support is provided by two modules: `spring-boot-test` contains core items, and `spring-boot-test-autoconfigure` supports auto-configuration for tests. - -Most developers use the `spring-boot-starter-test` "`Starter`", which imports both Spring Boot test modules as well as JUnit Jupiter, AssertJ, Hamcrest, and a number of other useful libraries. - -[TIP] -==== -If you have tests that use JUnit 4, JUnit 5's vintage engine can be used to run them. -To use the vintage engine, add a dependency on `junit-vintage-engine`, as shown in the following example: - -[source,xml,indent=0,subs="verbatim"] ----- - - org.junit.vintage - junit-vintage-engine - test - - - org.hamcrest - hamcrest-core - - - ----- -==== - -`hamcrest-core` is excluded in favor of `org.hamcrest:hamcrest` that is part of `spring-boot-starter-test`. - - - -[[features.testing.test-scope-dependencies]] -=== Test Scope Dependencies -The `spring-boot-starter-test` "`Starter`" (in the `test` `scope`) contains the following provided libraries: - -* https://junit.org/junit5/[JUnit 5]: The de-facto standard for unit testing Java applications. -* {spring-framework-docs}/testing/integration.html[Spring Test] & Spring Boot Test: Utilities and integration test support for Spring Boot applications. -* https://assertj.github.io/doc/[AssertJ]: A fluent assertion library. -* https://github.com/hamcrest/JavaHamcrest[Hamcrest]: A library of matcher objects (also known as constraints or predicates). -* https://site.mockito.org/[Mockito]: A Java mocking framework. -* https://github.com/skyscreamer/JSONassert[JSONassert]: An assertion library for JSON. -* https://github.com/jayway/JsonPath[JsonPath]: XPath for JSON. - -We generally find these common libraries to be useful when writing tests. -If these libraries do not suit your needs, you can add additional test dependencies of your own. - - - -[[features.testing.spring-applications]] -=== Testing Spring Applications -One of the major advantages of dependency injection is that it should make your code easier to unit test. -You can instantiate objects by using the `new` operator without even involving Spring. -You can also use _mock objects_ instead of real dependencies. - -Often, you need to move beyond unit testing and start integration testing (with a Spring `ApplicationContext`). -It is useful to be able to perform integration testing without requiring deployment of your application or needing to connect to other infrastructure. - -The Spring Framework includes a dedicated test module for such integration testing. -You can declare a dependency directly to `org.springframework:spring-test` or use the `spring-boot-starter-test` "`Starter`" to pull it in transitively. - -If you have not used the `spring-test` module before, you should start by reading the {spring-framework-docs}/testing.html[relevant section] of the Spring Framework reference documentation. - - - -[[features.testing.spring-boot-applications]] -=== Testing Spring Boot Applications -A Spring Boot application is a Spring `ApplicationContext`, so nothing very special has to be done to test it beyond what you would normally do with a vanilla Spring context. - -NOTE: External properties, logging, and other features of Spring Boot are installed in the context by default only if you use `SpringApplication` to create it. - -Spring Boot provides a `@SpringBootTest` annotation, which can be used as an alternative to the standard `spring-test` `@ContextConfiguration` annotation when you need Spring Boot features. -The annotation works by <>. -In addition to `@SpringBootTest` a number of other annotations are also provided for <> of an application. - -TIP: If you are using JUnit 4, do not forget to also add `@RunWith(SpringRunner.class)` to your test, otherwise the annotations will be ignored. -If you are using JUnit 5, there is no need to add the equivalent `@ExtendWith(SpringExtension.class)` as `@SpringBootTest` and the other `@...Test` annotations are already annotated with it. - -By default, `@SpringBootTest` will not start a server. -You can use the `webEnvironment` attribute of `@SpringBootTest` to further refine how your tests run: - -* `MOCK`(Default) : Loads a web `ApplicationContext` and provides a mock web environment. - Embedded servers are not started when using this annotation. - If a web environment is not available on your classpath, this mode transparently falls back to creating a regular non-web `ApplicationContext`. - It can be used in conjunction with <> for mock-based testing of your web application. -* `RANDOM_PORT`: Loads a `WebServerApplicationContext` and provides a real web environment. - Embedded servers are started and listen on a random port. -* `DEFINED_PORT`: Loads a `WebServerApplicationContext` and provides a real web environment. - Embedded servers are started and listen on a defined port (from your `application.properties`) or on the default port of `8080`. -* `NONE`: Loads an `ApplicationContext` by using `SpringApplication` but does not provide _any_ web environment (mock or otherwise). - -NOTE: If your test is `@Transactional`, it rolls back the transaction at the end of each test method by default. -However, as using this arrangement with either `RANDOM_PORT` or `DEFINED_PORT` implicitly provides a real servlet environment, the HTTP client and server run in separate threads and, thus, in separate transactions. -Any transaction initiated on the server does not roll back in this case. - -NOTE: `@SpringBootTest` with `webEnvironment = WebEnvironment.RANDOM_PORT` will also start the management server on a separate random port if your application uses a different port for the management server. - - - -[[features.testing.spring-boot-applications.detecting-web-app-type]] -==== Detecting Web Application Type -If Spring MVC is available, a regular MVC-based application context is configured. -If you have only Spring WebFlux, we will detect that and configure a WebFlux-based application context instead. - -If both are present, Spring MVC takes precedence. -If you want to test a reactive web application in this scenario, you must set the configprop:spring.main.web-application-type[] property: - -include::code:MyWebFluxTests[] - - - -[[features.testing.spring-boot-applications.detecting-configuration]] -==== Detecting Test Configuration -If you are familiar with the Spring Test Framework, you may be used to using `@ContextConfiguration(classes=...)` in order to specify which Spring `@Configuration` to load. -Alternatively, you might have often used nested `@Configuration` classes within your test. - -When testing Spring Boot applications, this is often not required. -Spring Boot's `@*Test` annotations search for your primary configuration automatically whenever you do not explicitly define one. - -The search algorithm works up from the package that contains the test until it finds a class annotated with `@SpringBootApplication` or `@SpringBootConfiguration`. -As long as you <> in a sensible way, your main configuration is usually found. - -[NOTE] -==== -If you use a <>, you should avoid adding configuration settings that are specific to a particular area on the <>. - -The underlying component scan configuration of `@SpringBootApplication` defines exclude filters that are used to make sure slicing works as expected. -If you are using an explicit `@ComponentScan` directive on your `@SpringBootApplication`-annotated class, be aware that those filters will be disabled. -If you are using slicing, you should define them again. -==== - -If you want to customize the primary configuration, you can use a nested `@TestConfiguration` class. -Unlike a nested `@Configuration` class, which would be used instead of your application's primary configuration, a nested `@TestConfiguration` class is used in addition to your application's primary configuration. - -NOTE: Spring's test framework caches application contexts between tests. -Therefore, as long as your tests share the same configuration (no matter how it is discovered), the potentially time-consuming process of loading the context happens only once. - - - -[[features.testing.spring-boot-applications.using-main]] -==== Using the Test Configuration Main Method -Typically the test configuration discovered by `@SpringBootTest` will be your main `@SpringBootApplication`. -In most well structured applications, this configuration class will also include the `main` method used to launch the application. - -For example, the following is a very common code pattern for a typical Spring Boot application: - -include::code:typical/MyApplication[] - -In the example above, the `main` method doesn't do anything other than delegate to `SpringApplication.run`. -It is, however, possible to have a more complex `main` method that applies customizations before calling `SpringApplication.run`. - -For example, here is an application that changes the banner mode and sets additional profiles: - -include::code:custom/MyApplication[] - -Since customizations in the `main` method can affect the resulting `ApplicationContext`, it's possible that you might also want to use the `main` method to create the `ApplicationContext` used in your tests. -By default, `@SpringBootTest` will not call your `main` method, and instead the class itself is used directly to create the `ApplicationContext` - -If you want to change this behavior, you can change the `useMainMethod` attribute of `@SpringBootTest` to `UseMainMethod.ALWAYS` or `UseMainMethod.WHEN_AVAILABLE`. -When set to `ALWAYS`, the test will fail if no `main` method can be found. -When set to `WHEN_AVAILABLE` the `main` method will be used if it is available, otherwise the standard loading mechanism will be used. - -For example, the following test will invoke the `main` method of `MyApplication` in order to create the `ApplicationContext`. -If the main method sets additional profiles then those will be active when the `ApplicationContext` starts. - -include::code:always/MyApplicationTests[] - - - -[[features.testing.spring-boot-applications.excluding-configuration]] -==== Excluding Test Configuration -If your application uses component scanning (for example, if you use `@SpringBootApplication` or `@ComponentScan`), you may find top-level configuration classes that you created only for specific tests accidentally get picked up everywhere. - -As we <>, `@TestConfiguration` can be used on an inner class of a test to customize the primary configuration. -`@TestConfiguration` can also be used on a top-level class. Doing so indicates that the class should not be picked up by scanning. -You can then import the class explicitly where it is required, as shown in the following example: - -include::code:MyTests[] - -NOTE: If you directly use `@ComponentScan` (that is, not through `@SpringBootApplication`) you need to register the `TypeExcludeFilter` with it. -See {spring-boot-module-api}/context/TypeExcludeFilter.html[the Javadoc] for details. - -NOTE: An imported `@TestConfiguration` is processed earlier than an inner-class `@TestConfiguration` and an imported `@TestConfiguration` will be processed before any configuration found through component scanning. -Generally speaking, this difference in ordering has no noticeable effect but it is something to be aware of if you're relying on bean overriding. - - - -[[features.testing.spring-boot-applications.using-application-arguments]] -==== Using Application Arguments -If your application expects <>, you can -have `@SpringBootTest` inject them using the `args` attribute. - -include::code:MyApplicationArgumentTests[] - - - -[[features.testing.spring-boot-applications.with-mock-environment]] -==== Testing With a Mock Environment -By default, `@SpringBootTest` does not start the server but instead sets up a mock environment for testing web endpoints. - -With Spring MVC, we can query our web endpoints using {spring-framework-docs}/testing/spring-mvc-test-framework.html[`MockMvc`] or `WebTestClient`, as shown in the following example: - -include::code:MyMockMvcTests[] - -TIP: If you want to focus only on the web layer and not start a complete `ApplicationContext`, consider <>. - -With Spring WebFlux endpoints, you can use {spring-framework-docs}/testing/webtestclient.html[`WebTestClient`] as shown in the following example: - -include::code:MyMockWebTestClientTests[] - -[TIP] -==== -Testing within a mocked environment is usually faster than running with a full servlet container. -However, since mocking occurs at the Spring MVC layer, code that relies on lower-level servlet container behavior cannot be directly tested with MockMvc. - -For example, Spring Boot's error handling is based on the "`error page`" support provided by the servlet container. -This means that, whilst you can test your MVC layer throws and handles exceptions as expected, you cannot directly test that a specific <> is rendered. -If you need to test these lower-level concerns, you can start a fully running server as described in the next section. -==== - - - -[[features.testing.spring-boot-applications.with-running-server]] -==== Testing With a Running Server -If you need to start a full running server, we recommend that you use random ports. -If you use `@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)`, an available port is picked at random each time your test runs. - -The `@LocalServerPort` annotation can be used to <> into your test. -For convenience, tests that need to make REST calls to the started server can additionally `@Autowire` a {spring-framework-docs}/testing/webtestclient.html[`WebTestClient`], which resolves relative links to the running server and comes with a dedicated API for verifying responses, as shown in the following example: - -include::code:MyRandomPortWebTestClientTests[] - -TIP: `WebTestClient` can also used with a <>, removing the need for a running server, by annotating your test class with `@AutoConfigureWebTestClient`. - -This setup requires `spring-webflux` on the classpath. -If you can not or will not add webflux, Spring Boot also provides a `TestRestTemplate` facility: - -include::code:MyRandomPortTestRestTemplateTests[] - - - -[[features.testing.spring-boot-applications.customizing-web-test-client]] -==== Customizing WebTestClient -To customize the `WebTestClient` bean, configure a `WebTestClientBuilderCustomizer` bean. -Any such beans are called with the `WebTestClient.Builder` that is used to create the `WebTestClient`. - - - -[[features.testing.spring-boot-applications.jmx]] -==== Using JMX -As the test context framework caches context, JMX is disabled by default to prevent identical components to register on the same domain. -If such test needs access to an `MBeanServer`, consider marking it dirty as well: - -include::code:MyJmxTests[] - - - -[[features.testing.spring-boot-applications.metrics]] -==== Using Metrics -Regardless of your classpath, meter registries, except the in-memory backed, are not auto-configured when using `@SpringBootTest`. - -If you need to export metrics to a different backend as part of an integration test, annotate it with `@AutoConfigureObservability`. - - - -[[features.testing.spring-boot-applications.tracing]] -==== Using Tracing -Regardless of your classpath, tracing is not auto-configured when using `@SpringBootTest`. - -If you need tracing as part of an integration test, annotate it with `@AutoConfigureObservability`. - - - -[[features.testing.spring-boot-applications.mocking-beans]] -==== Mocking and Spying Beans -When running tests, it is sometimes necessary to mock certain components within your application context. -For example, you may have a facade over some remote service that is unavailable during development. -Mocking can also be useful when you want to simulate failures that might be hard to trigger in a real environment. - -Spring Boot includes a `@MockBean` annotation that can be used to define a Mockito mock for a bean inside your `ApplicationContext`. -You can use the annotation to add new beans or replace a single existing bean definition. -The annotation can be used directly on test classes, on fields within your test, or on `@Configuration` classes and fields. -When used on a field, the instance of the created mock is also injected. -Mock beans are automatically reset after each test method. - -[NOTE] -==== -If your test uses one of Spring Boot's test annotations (such as `@SpringBootTest`), this feature is automatically enabled. -To use this feature with a different arrangement, listeners must be explicitly added, as shown in the following example: - -include::code:listener/MyTests[] -==== - -The following example replaces an existing `RemoteService` bean with a mock implementation: - -include::code:bean/MyTests[] - -NOTE: `@MockBean` cannot be used to mock the behavior of a bean that is exercised during application context refresh. -By the time the test is executed, the application context refresh has completed and it is too late to configure the mocked behavior. -We recommend using a `@Bean` method to create and configure the mock in this situation. - -Additionally, you can use `@SpyBean` to wrap any existing bean with a Mockito `spy`. -See the {spring-boot-test-module-api}/mock/mockito/SpyBean.html[Javadoc] for full details. - -NOTE: While Spring's test framework caches application contexts between tests and reuses a context for tests sharing the same configuration, the use of `@MockBean` or `@SpyBean` influences the cache key, which will most likely increase the number of contexts. - -TIP: If you are using `@SpyBean` to spy on a bean with `@Cacheable` methods that refer to parameters by name, your application must be compiled with `-parameters`. -This ensures that the parameter names are available to the caching infrastructure once the bean has been spied upon. - -TIP: When you are using `@SpyBean` to spy on a bean that is proxied by Spring, you may need to remove Spring's proxy in some situations, for example when setting expectations using `given` or `when`. -Use `AopTestUtils.getTargetObject(yourProxiedSpy)` to do so. - - - -[[features.testing.spring-boot-applications.autoconfigured-tests]] -==== Auto-configured Tests -Spring Boot's auto-configuration system works well for applications but can sometimes be a little too much for tests. -It often helps to load only the parts of the configuration that are required to test a "`slice`" of your application. -For example, you might want to test that Spring MVC controllers are mapping URLs correctly, and you do not want to involve database calls in those tests, or you might want to test JPA entities, and you are not interested in the web layer when those tests run. - -The `spring-boot-test-autoconfigure` module includes a number of annotations that can be used to automatically configure such "`slices`". -Each of them works in a similar way, providing a `@...Test` annotation that loads the `ApplicationContext` and one or more `@AutoConfigure...` annotations that can be used to customize auto-configuration settings. - -NOTE: Each slice restricts component scan to appropriate components and loads a very restricted set of auto-configuration classes. -If you need to exclude one of them, most `@...Test` annotations provide an `excludeAutoConfiguration` attribute. -Alternatively, you can use `@ImportAutoConfiguration#exclude`. - -NOTE: Including multiple "`slices`" by using several `@...Test` annotations in one test is not supported. -If you need multiple "`slices`", pick one of the `@...Test` annotations and include the `@AutoConfigure...` annotations of the other "`slices`" by hand. - -TIP: It is also possible to use the `@AutoConfigure...` annotations with the standard `@SpringBootTest` annotation. -You can use this combination if you are not interested in "`slicing`" your application but you want some of the auto-configured test beans. - - - -[[features.testing.spring-boot-applications.json-tests]] -==== Auto-configured JSON Tests -To test that object JSON serialization and deserialization is working as expected, you can use the `@JsonTest` annotation. -`@JsonTest` auto-configures the available supported JSON mapper, which can be one of the following libraries: - -* Jackson `ObjectMapper`, any `@JsonComponent` beans and any Jackson ``Module``s -* `Gson` -* `Jsonb` - -TIP: A list of the auto-configurations that are enabled by `@JsonTest` can be <>. - -If you need to configure elements of the auto-configuration, you can use the `@AutoConfigureJsonTesters` annotation. - -Spring Boot includes AssertJ-based helpers that work with the JSONAssert and JsonPath libraries to check that JSON appears as expected. -The `JacksonTester`, `GsonTester`, `JsonbTester`, and `BasicJsonTester` classes can be used for Jackson, Gson, Jsonb, and Strings respectively. -Any helper fields on the test class can be `@Autowired` when using `@JsonTest`. -The following example shows a test class for Jackson: - -include::code:MyJsonTests[] - -NOTE: JSON helper classes can also be used directly in standard unit tests. -To do so, call the `initFields` method of the helper in your `@Before` method if you do not use `@JsonTest`. - -If you use Spring Boot's AssertJ-based helpers to assert on a number value at a given JSON path, you might not be able to use `isEqualTo` depending on the type. -Instead, you can use AssertJ's `satisfies` to assert that the value matches the given condition. -For instance, the following example asserts that the actual number is a float value close to `0.15` within an offset of `0.01`. - -include::code:MyJsonAssertJTests[tag=*] - - - -[[features.testing.spring-boot-applications.spring-mvc-tests]] -==== Auto-configured Spring MVC Tests -To test whether Spring MVC controllers are working as expected, use the `@WebMvcTest` annotation. -`@WebMvcTest` auto-configures the Spring MVC infrastructure and limits scanned beans to `@Controller`, `@ControllerAdvice`, `@JsonComponent`, `Converter`, `GenericConverter`, `Filter`, `HandlerInterceptor`, `WebMvcConfigurer`, `WebMvcRegistrations`, and `HandlerMethodArgumentResolver`. -Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@WebMvcTest` annotation is used. -`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. - -TIP: A list of the auto-configuration settings that are enabled by `@WebMvcTest` can be <>. - -TIP: If you need to register extra components, such as the Jackson `Module`, you can import additional configuration classes by using `@Import` on your test. - -Often, `@WebMvcTest` is limited to a single controller and is used in combination with `@MockBean` to provide mock implementations for required collaborators. - -`@WebMvcTest` also auto-configures `MockMvc`. -Mock MVC offers a powerful way to quickly test MVC controllers without needing to start a full HTTP server. - -TIP: You can also auto-configure `MockMvc` in a non-`@WebMvcTest` (such as `@SpringBootTest`) by annotating it with `@AutoConfigureMockMvc`. -The following example uses `MockMvc`: - -include::code:MyControllerTests[] - -TIP: If you need to configure elements of the auto-configuration (for example, when servlet filters should be applied) you can use attributes in the `@AutoConfigureMockMvc` annotation. - -If you use HtmlUnit and Selenium, auto-configuration also provides an HtmlUnit `WebClient` bean and/or a Selenium `WebDriver` bean. -The following example uses HtmlUnit: - -include::code:MyHtmlUnitTests[] - -NOTE: By default, Spring Boot puts `WebDriver` beans in a special "`scope`" to ensure that the driver exits after each test and that a new instance is injected. -If you do not want this behavior, you can add `@Scope("singleton")` to your `WebDriver` `@Bean` definition. - -WARNING: The `webDriver` scope created by Spring Boot will replace any user defined scope of the same name. -If you define your own `webDriver` scope you may find it stops working when you use `@WebMvcTest`. - -If you have Spring Security on the classpath, `@WebMvcTest` will also scan `WebSecurityConfigurer` beans. -Instead of disabling security completely for such tests, you can use Spring Security's test support. -More details on how to use Spring Security's `MockMvc` support can be found in this _<>_ how-to section. - -TIP: Sometimes writing Spring MVC tests is not enough; Spring Boot can help you run <>. - - - -[[features.testing.spring-boot-applications.spring-webflux-tests]] -==== Auto-configured Spring WebFlux Tests -To test that {spring-framework-docs}/web-reactive.html[Spring WebFlux] controllers are working as expected, you can use the `@WebFluxTest` annotation. -`@WebFluxTest` auto-configures the Spring WebFlux infrastructure and limits scanned beans to `@Controller`, `@ControllerAdvice`, `@JsonComponent`, `Converter`, `GenericConverter`, `WebFilter`, and `WebFluxConfigurer`. -Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@WebFluxTest` annotation is used. -`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. - -TIP: A list of the auto-configurations that are enabled by `@WebFluxTest` can be <>. - -TIP: If you need to register extra components, such as Jackson `Module`, you can import additional configuration classes using `@Import` on your test. - -Often, `@WebFluxTest` is limited to a single controller and used in combination with the `@MockBean` annotation to provide mock implementations for required collaborators. - -`@WebFluxTest` also auto-configures {spring-framework-docs}/testing/webtestclient.html[`WebTestClient`], which offers a powerful way to quickly test WebFlux controllers without needing to start a full HTTP server. - -TIP: You can also auto-configure `WebTestClient` in a non-`@WebFluxTest` (such as `@SpringBootTest`) by annotating it with `@AutoConfigureWebTestClient`. -The following example shows a class that uses both `@WebFluxTest` and a `WebTestClient`: - -include::code:MyControllerTests[] - -TIP: This setup is only supported by WebFlux applications as using `WebTestClient` in a mocked web application only works with WebFlux at the moment. - -NOTE: `@WebFluxTest` cannot detect routes registered through the functional web framework. -For testing `RouterFunction` beans in the context, consider importing your `RouterFunction` yourself by using `@Import` or by using `@SpringBootTest`. - -NOTE: `@WebFluxTest` cannot detect custom security configuration registered as a `@Bean` of type `SecurityWebFilterChain`. -To include that in your test, you will need to import the configuration that registers the bean by using `@Import` or by using `@SpringBootTest`. - -TIP: Sometimes writing Spring WebFlux tests is not enough; Spring Boot can help you run <>. - - - -[[features.testing.spring-boot-applications.spring-graphql-tests]] -==== Auto-configured Spring GraphQL Tests -Spring GraphQL offers a dedicated testing support module; you'll need to add it to your project: - -.Maven -[source,xml,indent=0,subs="verbatim"] ----- - - - org.springframework.graphql - spring-graphql-test - test - - - - org.springframework.boot - spring-boot-starter-webflux - test - - ----- - -.Gradle -[source,gradle,indent=0,subs="verbatim"] ----- - dependencies { - testImplementation("org.springframework.graphql:spring-graphql-test") - // Unless already present in the implementation configuration - testImplementation("org.springframework.boot:spring-boot-starter-webflux") - } ----- - -This testing module ships the {spring-graphql-docs}/#testing-graphqltester[GraphQlTester]. -The tester is heavily used in test, so be sure to become familiar with using it. -There are `GraphQlTester` variants and Spring Boot will auto-configure them depending on the type of tests: - -* the `ExecutionGraphQlServiceTester` performs tests on the server side, without a client nor a transport -* the `HttpGraphQlTester` performs tests with a client that connects to a server, with or without a live server - -Spring Boot helps you to test your {spring-graphql-docs}#controllers[Spring GraphQL Controllers] with the `@GraphQlTest` annotation. -`@GraphQlTest` auto-configures the Spring GraphQL infrastructure, without any transport nor server being involved. -This limits scanned beans to `@Controller`, `RuntimeWiringConfigurer`, `JsonComponent`, `Converter`, `GenericConverter`, `DataFetcherExceptionResolver`, `Instrumentation` and `GraphQlSourceBuilderCustomizer`. -Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@GraphQlTest` annotation is used. -`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. - -TIP: A list of the auto-configurations that are enabled by `@GraphQlTest` can be <>. - -Often, `@GraphQlTest` is limited to a set of controllers and used in combination with the `@MockBean` annotation to provide mock implementations for required collaborators. - -include::code:GreetingControllerTests[] - -`@SpringBootTest` tests are full integration tests and involve the entire application. -When using a random or defined port, a live server is configured and an `HttpGraphQlTester` bean is contributed automatically so you can use it to test your server. -When a MOCK environment is configured, you can also request an `HttpGraphQlTester` bean by annotating your test class with `@AutoConfigureHttpGraphQlTester`: - -include::code:GraphQlIntegrationTests[] - - - -[[features.testing.spring-boot-applications.autoconfigured-spring-data-cassandra]] -==== Auto-configured Data Cassandra Tests -You can use `@DataCassandraTest` to test Cassandra applications. -By default, it configures a `CassandraTemplate`, scans for `@Table` classes, and configures Spring Data Cassandra repositories. -Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@DataCassandraTest` annotation is used. -`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. -(For more about using Cassandra with Spring Boot, see "<>".) - -TIP: A list of the auto-configuration settings that are enabled by `@DataCassandraTest` can be <>. - -The following example shows a typical setup for using Cassandra tests in Spring Boot: - -include::code:MyDataCassandraTests[] - - - -[[features.testing.spring-boot-applications.autoconfigured-spring-data-couchbase]] -==== Auto-configured Data Couchbase Tests -You can use `@DataCouchbaseTest` to test Couchbase applications. -By default, it configures a `CouchbaseTemplate` or `ReactiveCouchbaseTemplate`, scans for `@Document` classes, and configures Spring Data Couchbase repositories. -Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@DataCouchbaseTest` annotation is used. -`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. -(For more about using Couchbase with Spring Boot, see "<>", earlier in this chapter.) - -TIP: A list of the auto-configuration settings that are enabled by `@DataCouchbaseTest` can be <>. - -The following example shows a typical setup for using Couchbase tests in Spring Boot: - -include::code:MyDataCouchbaseTests[] - - - -[[features.testing.spring-boot-applications.autoconfigured-spring-data-elasticsearch]] -==== Auto-configured Data Elasticsearch Tests -You can use `@DataElasticsearchTest` to test Elasticsearch applications. -By default, it configures an `ElasticsearchRestTemplate`, scans for `@Document` classes, and configures Spring Data Elasticsearch repositories. -Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@DataElasticsearchTest` annotation is used. -`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. -(For more about using Elasticsearch with Spring Boot, see "<>", earlier in this chapter.) - -TIP: A list of the auto-configuration settings that are enabled by `@DataElasticsearchTest` can be <>. - -The following example shows a typical setup for using Elasticsearch tests in Spring Boot: - -include::code:MyDataElasticsearchTests[] - - - -[[features.testing.spring-boot-applications.autoconfigured-spring-data-jpa]] -==== Auto-configured Data JPA Tests -You can use the `@DataJpaTest` annotation to test JPA applications. -By default, it scans for `@Entity` classes and configures Spring Data JPA repositories. -If an embedded database is available on the classpath, it configures one as well. -SQL queries are logged by default by setting the `spring.jpa.show-sql` property to `true`. -This can be disabled using the `showSql` attribute of the annotation. - -Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@DataJpaTest` annotation is used. -`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. - -TIP: A list of the auto-configuration settings that are enabled by `@DataJpaTest` can be <>. - -By default, data JPA tests are transactional and roll back at the end of each test. -See the {spring-framework-docs}/testing/testcontext-framework/tx.html#testcontext-tx-enabling-transactions[relevant section] in the Spring Framework Reference Documentation for more details. -If that is not what you want, you can disable transaction management for a test or for the whole class as follows: - -include::code:MyNonTransactionalTests[] - -Data JPA tests may also inject a {spring-boot-test-autoconfigure-module-code}/orm/jpa/TestEntityManager.java[`TestEntityManager`] bean, which provides an alternative to the standard JPA `EntityManager` that is specifically designed for tests. - -TIP: `TestEntityManager` can also be auto-configured to any of your Spring-based test class by adding `@AutoConfigureTestEntityManager`. -When doing so, make sure that your test is running in a transaction, for instance by adding `@Transactional` on your test class or method. - -A `JdbcTemplate` is also available if you need that. -The following example shows the `@DataJpaTest` annotation in use: - -include::code:withoutdb/MyRepositoryTests[] - -In-memory embedded databases generally work well for tests, since they are fast and do not require any installation. -If, however, you prefer to run tests against a real database you can use the `@AutoConfigureTestDatabase` annotation, as shown in the following example: - -include::code:withdb/MyRepositoryTests[] - - - -[[features.testing.spring-boot-applications.autoconfigured-jdbc]] -==== Auto-configured JDBC Tests -`@JdbcTest` is similar to `@DataJpaTest` but is for tests that only require a `DataSource` and do not use Spring Data JDBC. -By default, it configures an in-memory embedded database and a `JdbcTemplate`. -Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@JdbcTest` annotation is used. -`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. - -TIP: A list of the auto-configurations that are enabled by `@JdbcTest` can be <>. - -By default, JDBC tests are transactional and roll back at the end of each test. -See the {spring-framework-docs}/testing/testcontext-framework/tx.html#testcontext-tx-enabling-transactions[relevant section] in the Spring Framework Reference Documentation for more details. -If that is not what you want, you can disable transaction management for a test or for the whole class, as follows: - -include::code:MyTransactionalTests[] - -If you prefer your test to run against a real database, you can use the `@AutoConfigureTestDatabase` annotation in the same way as for `@DataJpaTest`. -(See "<>".) - - - -[[features.testing.spring-boot-applications.autoconfigured-spring-data-jdbc]] -==== Auto-configured Data JDBC Tests -`@DataJdbcTest` is similar to `@JdbcTest` but is for tests that use Spring Data JDBC repositories. -By default, it configures an in-memory embedded database, a `JdbcTemplate`, and Spring Data JDBC repositories. -Only `AbstractJdbcConfiguration` subclasses are scanned when the `@DataJdbcTest` annotation is used, regular `@Component` and `@ConfigurationProperties` beans are not scanned. -`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. - -TIP: A list of the auto-configurations that are enabled by `@DataJdbcTest` can be <>. - -By default, Data JDBC tests are transactional and roll back at the end of each test. -See the {spring-framework-docs}/testing/testcontext-framework/tx.html#testcontext-tx-enabling-transactions[relevant section] in the Spring Framework Reference Documentation for more details. -If that is not what you want, you can disable transaction management for a test or for the whole test class as <>. - -If you prefer your test to run against a real database, you can use the `@AutoConfigureTestDatabase` annotation in the same way as for `@DataJpaTest`. -(See "<>".) - - - -[[features.testing.spring-boot-applications.autoconfigured-spring-data-r2dbc]] -==== Auto-configured Data R2DBC Tests -`@DataR2dbcTest` is similar to `@DataJdbcTest` but is for tests that use Spring Data R2DBC repositories. -By default, it configures an in-memory embedded database, an `R2dbcEntityTemplate`, and Spring Data R2DBC repositories. -Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@DataR2dbcTest` annotation is used. -`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. - -TIP: A list of the auto-configurations that are enabled by `@DataR2dbcTest` can be <>. - -By default, Data R2DBC tests are not transactional. - -If you prefer your test to run against a real database, you can use the `@AutoConfigureTestDatabase` annotation in the same way as for `@DataJpaTest`. -(See "<>".) - - - -[[features.testing.spring-boot-applications.autoconfigured-jooq]] -==== Auto-configured jOOQ Tests -You can use `@JooqTest` in a similar fashion as `@JdbcTest` but for jOOQ-related tests. -As jOOQ relies heavily on a Java-based schema that corresponds with the database schema, the existing `DataSource` is used. -If you want to replace it with an in-memory database, you can use `@AutoConfigureTestDatabase` to override those settings. -(For more about using jOOQ with Spring Boot, see "<>".) -Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@JooqTest` annotation is used. -`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. - -TIP: A list of the auto-configurations that are enabled by `@JooqTest` can be <>. - -`@JooqTest` configures a `DSLContext`. -The following example shows the `@JooqTest` annotation in use: - -include::code:MyJooqTests[] - -JOOQ tests are transactional and roll back at the end of each test by default. -If that is not what you want, you can disable transaction management for a test or for the whole test class as <>. - - - -[[features.testing.spring-boot-applications.autoconfigured-spring-data-mongodb]] -==== Auto-configured Data MongoDB Tests -You can use `@DataMongoTest` to test MongoDB applications. -By default, it configures a `MongoTemplate`, scans for `@Document` classes, and configures Spring Data MongoDB repositories. -Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@DataMongoTest` annotation is used. -`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. -(For more about using MongoDB with Spring Boot, see "<>".) - -TIP: A list of the auto-configuration settings that are enabled by `@DataMongoTest` can be <>. - -The following class shows the `@DataMongoTest` annotation in use: - -include::code:MyDataMongoDbTests[] - - - -[[features.testing.spring-boot-applications.autoconfigured-spring-data-neo4j]] -==== Auto-configured Data Neo4j Tests -You can use `@DataNeo4jTest` to test Neo4j applications. -By default, it scans for `@Node` classes, and configures Spring Data Neo4j repositories. -Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@DataNeo4jTest` annotation is used. -`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. -(For more about using Neo4J with Spring Boot, see "<>".) - -TIP: A list of the auto-configuration settings that are enabled by `@DataNeo4jTest` can be <>. - -The following example shows a typical setup for using Neo4J tests in Spring Boot: - -include::code:propagation/MyDataNeo4jTests[] - -By default, Data Neo4j tests are transactional and roll back at the end of each test. -See the {spring-framework-docs}/testing/testcontext-framework/tx.html#testcontext-tx-enabling-transactions[relevant section] in the Spring Framework Reference Documentation for more details. -If that is not what you want, you can disable transaction management for a test or for the whole class, as follows: - -include::code:nopropagation/MyDataNeo4jTests[] - -NOTE: Transactional tests are not supported with reactive access. -If you are using this style, you must configure `@DataNeo4jTest` tests as described above. - - - -[[features.testing.spring-boot-applications.autoconfigured-spring-data-redis]] -==== Auto-configured Data Redis Tests -You can use `@DataRedisTest` to test Redis applications. -By default, it scans for `@RedisHash` classes and configures Spring Data Redis repositories. -Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@DataRedisTest` annotation is used. -`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. -(For more about using Redis with Spring Boot, see "<>".) - -TIP: A list of the auto-configuration settings that are enabled by `@DataRedisTest` can be <>. - -The following example shows the `@DataRedisTest` annotation in use: - -include::code:MyDataRedisTests[] - - - -[[features.testing.spring-boot-applications.autoconfigured-spring-data-ldap]] -==== Auto-configured Data LDAP Tests -You can use `@DataLdapTest` to test LDAP applications. -By default, it configures an in-memory embedded LDAP (if available), configures an `LdapTemplate`, scans for `@Entry` classes, and configures Spring Data LDAP repositories. -Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@DataLdapTest` annotation is used. -`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. -(For more about using LDAP with Spring Boot, see "<>".) - -TIP: A list of the auto-configuration settings that are enabled by `@DataLdapTest` can be <>. - -The following example shows the `@DataLdapTest` annotation in use: - -include::code:inmemory/MyDataLdapTests[] - -In-memory embedded LDAP generally works well for tests, since it is fast and does not require any developer installation. -If, however, you prefer to run tests against a real LDAP server, you should exclude the embedded LDAP auto-configuration, as shown in the following example: - -include::code:server/MyDataLdapTests[] - - - -[[features.testing.spring-boot-applications.autoconfigured-rest-client]] -==== Auto-configured REST Clients -You can use the `@RestClientTest` annotation to test REST clients. -By default, it auto-configures Jackson, GSON, and Jsonb support, configures a `RestTemplateBuilder`, and adds support for `MockRestServiceServer`. -Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@RestClientTest` annotation is used. -`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. - -TIP: A list of the auto-configuration settings that are enabled by `@RestClientTest` can be <>. - -The specific beans that you want to test should be specified by using the `value` or `components` attribute of `@RestClientTest`, as shown in the following example: - -include::code:MyRestClientTests[] - - - -[[features.testing.spring-boot-applications.autoconfigured-spring-restdocs]] -==== Auto-configured Spring REST Docs Tests -You can use the `@AutoConfigureRestDocs` annotation to use {spring-restdocs}[Spring REST Docs] in your tests with Mock MVC, REST Assured, or WebTestClient. -It removes the need for the JUnit extension in Spring REST Docs. - -`@AutoConfigureRestDocs` can be used to override the default output directory (`target/generated-snippets` if you are using Maven or `build/generated-snippets` if you are using Gradle). -It can also be used to configure the host, scheme, and port that appears in any documented URIs. - - - -[[features.testing.spring-boot-applications.autoconfigured-spring-restdocs.with-mock-mvc]] -===== Auto-configured Spring REST Docs Tests With Mock MVC -`@AutoConfigureRestDocs` customizes the `MockMvc` bean to use Spring REST Docs when testing servlet-based web applications. -You can inject it by using `@Autowired` and use it in your tests as you normally would when using Mock MVC and Spring REST Docs, as shown in the following example: - -include::code:MyUserDocumentationTests[] - -If you require more control over Spring REST Docs configuration than offered by the attributes of `@AutoConfigureRestDocs`, you can use a `RestDocsMockMvcConfigurationCustomizer` bean, as shown in the following example: - -include::code:MyRestDocsConfiguration[] - -If you want to make use of Spring REST Docs support for a parameterized output directory, you can create a `RestDocumentationResultHandler` bean. -The auto-configuration calls `alwaysDo` with this result handler, thereby causing each `MockMvc` call to automatically generate the default snippets. -The following example shows a `RestDocumentationResultHandler` being defined: - -include::code:MyResultHandlerConfiguration[] - - - -[[features.testing.spring-boot-applications.autoconfigured-spring-restdocs.with-web-test-client]] -===== Auto-configured Spring REST Docs Tests With WebTestClient -`@AutoConfigureRestDocs` can also be used with `WebTestClient` when testing reactive web applications. -You can inject it by using `@Autowired` and use it in your tests as you normally would when using `@WebFluxTest` and Spring REST Docs, as shown in the following example: - -include::code:MyUsersDocumentationTests[] - -If you require more control over Spring REST Docs configuration than offered by the attributes of `@AutoConfigureRestDocs`, you can use a `RestDocsWebTestClientConfigurationCustomizer` bean, as shown in the following example: - -include::code:MyRestDocsConfiguration[] - -If you want to make use of Spring REST Docs support for a parameterized output directory, you can use a `WebTestClientBuilderCustomizer` to configure a consumer for every entity exchange result. -The following example shows such a `WebTestClientBuilderCustomizer` being defined: - -include::code:MyWebTestClientBuilderCustomizerConfiguration[] - - - -[[features.testing.spring-boot-applications.autoconfigured-spring-restdocs.with-rest-assured]] -===== Auto-configured Spring REST Docs Tests With REST Assured -`@AutoConfigureRestDocs` makes a `RequestSpecification` bean, preconfigured to use Spring REST Docs, available to your tests. -You can inject it by using `@Autowired` and use it in your tests as you normally would when using REST Assured and Spring REST Docs, as shown in the following example: - -include::code:MyUserDocumentationTests[] - -If you require more control over Spring REST Docs configuration than offered by the attributes of `@AutoConfigureRestDocs`, a `RestDocsRestAssuredConfigurationCustomizer` bean can be used, as shown in the following example: - -include::code:MyRestDocsConfiguration[] - - - -[[features.testing.spring-boot-applications.autoconfigured-webservices]] -==== Auto-configured Spring Web Services Tests - - - -[[features.testing.spring-boot-applications.autoconfigured-webservices.client]] -===== Auto-configured Spring Web Services Client Tests -You can use `@WebServiceClientTest` to test applications that call web services using the Spring Web Services project. -By default, it configures a mock `WebServiceServer` bean and automatically customizes your `WebServiceTemplateBuilder`. -(For more about using Web Services with Spring Boot, see "<>".) - - -TIP: A list of the auto-configuration settings that are enabled by `@WebServiceClientTest` can be <>. - -The following example shows the `@WebServiceClientTest` annotation in use: - -include::code:MyWebServiceClientTests[] - - - -[[features.testing.spring-boot-applications.autoconfigured-webservices.server]] -===== Auto-configured Spring Web Services Server Tests -You can use `@WebServiceServerTest` to test applications that implement web services using the Spring Web Services project. -By default, it configures a `MockWebServiceClient` bean that can be used to call your web service endpoints. -(For more about using Web Services with Spring Boot, see "<>".) - - -TIP: A list of the auto-configuration settings that are enabled by `@WebServiceServerTest` can be <>. - -The following example shows the `@WebServiceServerTest` annotation in use: - -include::code:MyWebServiceServerTests[] - - - -[[features.testing.spring-boot-applications.additional-autoconfiguration-and-slicing]] -==== Additional Auto-configuration and Slicing -Each slice provides one or more `@AutoConfigure...` annotations that namely defines the auto-configurations that should be included as part of a slice. -Additional auto-configurations can be added on a test-by-test basis by creating a custom `@AutoConfigure...` annotation or by adding `@ImportAutoConfiguration` to the test as shown in the following example: - -include::code:MyJdbcTests[] - -NOTE: Make sure to not use the regular `@Import` annotation to import auto-configurations as they are handled in a specific way by Spring Boot. - -Alternatively, additional auto-configurations can be added for any use of a slice annotation by registering them in a file stored in `META-INF/spring` as shown in the following example: - -.META-INF/spring/org.springframework.boot.test.autoconfigure.jdbc.JdbcTest.imports -[indent=0] ----- - com.example.IntegrationAutoConfiguration ----- - -In this example, the `com.example.IntegrationAutoConfiguration` is enabled on every test annotated with `@JdbcTest`. - -TIP: You can use comments with `#` in this file. - -TIP: A slice or `@AutoConfigure...` annotation can be customized this way as long as it is meta-annotated with `@ImportAutoConfiguration`. - - - -[[features.testing.spring-boot-applications.user-configuration-and-slicing]] -==== User Configuration and Slicing -If you <> in a sensible way, your `@SpringBootApplication` class is <> as the configuration of your tests. - -It then becomes important not to litter the application's main class with configuration settings that are specific to a particular area of its functionality. - -Assume that you are using Spring Data MongoDB, you rely on the auto-configuration for it, and you have enabled auditing. -You could define your `@SpringBootApplication` as follows: - -include::code:MyApplication[] - -Because this class is the source configuration for the test, any slice test actually tries to enable Mongo auditing, which is definitely not what you want to do. -A recommended approach is to move that area-specific configuration to a separate `@Configuration` class at the same level as your application, as shown in the following example: - -include::code:MyMongoConfiguration[] - -NOTE: Depending on the complexity of your application, you may either have a single `@Configuration` class for your customizations or one class per domain area. -The latter approach lets you enable it in one of your tests, if necessary, with the `@Import` annotation. -See <> for more details on when you might want to enable specific `@Configuration` classes for slice tests. - -Test slices exclude `@Configuration` classes from scanning. -For example, for a `@WebMvcTest`, the following configuration will not include the given `WebMvcConfigurer` bean in the application context loaded by the test slice: - -include::code:MyWebConfiguration[] - -The configuration below will, however, cause the custom `WebMvcConfigurer` to be loaded by the test slice. - -include::code:MyWebMvcConfigurer[] - -Another source of confusion is classpath scanning. -Assume that, while you structured your code in a sensible way, you need to scan an additional package. -Your application may resemble the following code: - -include::code:scan/MyApplication[] - -Doing so effectively overrides the default component scan directive with the side effect of scanning those two packages regardless of the slice that you chose. -For instance, a `@DataJpaTest` seems to suddenly scan components and user configurations of your application. -Again, moving the custom directive to a separate class is a good way to fix this issue. - -TIP: If this is not an option for you, you can create a `@SpringBootConfiguration` somewhere in the hierarchy of your test so that it is used instead. -Alternatively, you can specify a source for your test, which disables the behavior of finding a default one. - - - -[[features.testing.spring-boot-applications.spock]] -==== Using Spock to Test Spring Boot Applications -Spock 2.2 or later can be used to test a Spring Boot application. -To do so, add a dependency on a `-groovy-4.0` version of Spock's `spock-spring` module to your application's build. -`spock-spring` integrates Spring's test framework into Spock. -See https://spockframework.org/spock/docs/2.2-M1/modules.html#_spring_module[the documentation for Spock's Spring module] for further details. - - - -[[features.testing.testcontainers]] -=== Testcontainers -The https://www.testcontainers.org/[Testcontainers] library provides a way to manage services running inside Docker containers. -It integrates with JUnit, allowing you to write a test class that can start up a container before any of the tests run. -Testcontainers is especially useful for writing integration tests that talk to a real backend service such as MySQL, MongoDB, Cassandra and others. - -Testcontainers can be used in a Spring Boot test as follows: - -include::code:vanilla/MyIntegrationTests[] - -This will start up a docker container running Neo4j (if Docker is running locally) before any of the tests are run. -In most cases, you will need to configure the application to connect to the service running in the container. - - - -[[features.testing.testcontainers.service-connections]] -==== Service Connections -A service connection is a connection to any remote service. -Spring Boot's auto-configuration can consume the details of a service connection and use them to establish a connection to a remote service. -When doing so, the connection details take precedence over any connection-related configuration properties. - -When using Testcontainers, connection details can be automatically created for a service running in a container by annotating the container field in the test class. - -include::code:MyIntegrationTests[] - -Thanks to `@ServiceConnection`, the above configuration allows Neo4j-related beans in the application to communicate with Neo4j running inside the Testcontainers-managed Docker container. -This is done by automatically defining a `Neo4jConnectionDetails` bean which is then used by the Neo4j auto-configuration, overriding any connection-related configuration properties. - -NOTE: You'll need to add the `spring-boot-testcontainers` module as a test dependency in order to use service connections with Testcontainers. - -Service connection annotations are processed by `ContainerConnectionDetailsFactory` classes registered with `spring.factories`. -A `ContainerConnectionDetailsFactory` can create a `ConnectionDetails` bean based on a specific `Container` subclass, or the Docker image name. - -The following service connection factories are provided in the `spring-boot-testcontainers` jar: - -|=== -| Connection Details | Matched on - -| `CassandraConnectionDetails` -| Containers of type `CassandraContainer` - -| `CouchbaseConnectionDetails` -| Containers of type `CouchbaseContainer` - -| `ElasticsearchConnectionDetails` -| Containers of type `ElasticsearchContainer` - -| `FlywayConnectionDetails` -| Containers of type `JdbcDatabaseContainer` - -| `JdbcConnectionDetails` -| Containers of type `JdbcDatabaseContainer` - -| `KafkaConnectionDetails` -| Containers of type `KafkaContainer` or `RedpandaContainer` - -| `LiquibaseConnectionDetails` -| Containers of type `JdbcDatabaseContainer` - -| `MongoConnectionDetails` -| Containers of type `MongoDBContainer` - -| `Neo4jConnectionDetails` -| Containers of type `Neo4jContainer` - -| `R2dbcConnectionDetails` -| Containers of type `MariaDBContainer`, `MSSQLServerContainer`, `MySQLContainer`, `OracleContainer`, or `PostgreSQLContainer` - -| `RabbitConnectionDetails` -| Containers of type `RabbitMQContainer` - -| `RedisConnectionDetails` -| Containers named "redis" - -| `ZipkinConnectionDetails` -| Containers named "openzipkin/zipkin" -|=== - -[TIP] -==== -By default all applicable connection details beans will be created for a given `Container`. -For example, a `PostgreSQLContainer` will create both `JdbcConnectionDetails` and `R2dbcConnectionDetails`. - -If you want to create only a subset of the applicable types, you can use the `type` attribute of `@ServiceConnection`. -==== - -By default `Container.getDockerImageName()` is used to obtain the name used to find connection details. -This works as long as Spring Boot is able to get the instance of the `Container`, which is the case when using a `static` field like in the example above. - -If you're using a `@Bean` method, Spring Boot won't call the bean method to get the Docker image name, because this would cause eager initialization issues. -Instead, the return type of the bean method is used to find out which connection detail should be used. -This works as long as you're using typed containers, e.g. `Neo4jContainer` or `RabbitMQContainer`. -This stops working if you're using `GenericContainer`, e.g. with Redis, as shown in the following example: - -include::code:MyRedisConfiguration[] - -Spring Boot can't tell from `GenericContainer` which container image is used, so the `name` attribute from `@ServiceConnection` must be used to provide that hint. - -You can also can use the `name` attribute of `@ServiceConnection` to override which connection detail will be used, for example when using custom images. -If you are using the Docker image `registry.mycompany.com/mirror/myredis`, you'd use `@ServiceConnection(name="redis")` to ensure `RedisConnectionDetails` are created. - - - -[[features.testing.testcontainers.dynamic-properties]] -==== Dynamic Properties -A slightly more verbose but also more flexible alternative to service connections is `@DynamicPropertySource`. -A static `@DynamicPropertySource` method allows adding dynamic property values to the Spring Environment. - -include::code:MyIntegrationTests[] - -The above configuration allows Neo4j-related beans in the application to communicate with Neo4j running inside the Testcontainers-managed Docker container. - - - -[[features.testing.utilities]] -=== Test Utilities -A few test utility classes that are generally useful when testing your application are packaged as part of `spring-boot`. - - - -[[features.testing.utilities.config-data-application-context-initializer]] -==== ConfigDataApplicationContextInitializer -`ConfigDataApplicationContextInitializer` is an `ApplicationContextInitializer` that you can apply to your tests to load Spring Boot `application.properties` files. -You can use it when you do not need the full set of features provided by `@SpringBootTest`, as shown in the following example: - -include::code:MyConfigFileTests[] - -NOTE: Using `ConfigDataApplicationContextInitializer` alone does not provide support for `@Value("${...}")` injection. -Its only job is to ensure that `application.properties` files are loaded into Spring's `Environment`. -For `@Value` support, you need to either additionally configure a `PropertySourcesPlaceholderConfigurer` or use `@SpringBootTest`, which auto-configures one for you. - - - -[[features.testing.utilities.test-property-values]] -==== TestPropertyValues -`TestPropertyValues` lets you quickly add properties to a `ConfigurableEnvironment` or `ConfigurableApplicationContext`. -You can call it with `key=value` strings, as follows: - -include::code:MyEnvironmentTests[] - - - -[[features.testing.utilities.output-capture]] -==== OutputCapture -`OutputCapture` is a JUnit `Extension` that you can use to capture `System.out` and `System.err` output. -To use it, add `@ExtendWith(OutputCaptureExtension.class)` and inject `CapturedOutput` as an argument to your test class constructor or test method as follows: - -include::code:MyOutputCaptureTests[] - - - -[[features.testing.utilities.test-rest-template]] -==== TestRestTemplate -`TestRestTemplate` is a convenience alternative to Spring's `RestTemplate` that is useful in integration tests. -You can get a vanilla template or one that sends Basic HTTP authentication (with a username and password). -In either case, the template is fault tolerant. -This means that it behaves in a test-friendly way by not throwing exceptions on 4xx and 5xx errors. -Instead, such errors can be detected through the returned `ResponseEntity` and its status code. - -TIP: Spring Framework 5.0 provides a new `WebTestClient` that works for <> and both <>. -It provides a fluent API for assertions, unlike `TestRestTemplate`. - -It is recommended, but not mandatory, to use the Apache HTTP Client (version 5.1 or better). -If you have that on your classpath, the `TestRestTemplate` responds by configuring the client appropriately. -If you do use Apache's HTTP client, some additional test-friendly features are enabled: - -* Redirects are not followed (so you can assert the response location). -* Cookies are ignored (so the template is stateless). - -`TestRestTemplate` can be instantiated directly in your integration tests, as shown in the following example: - -include::code:MyTests[] - -Alternatively, if you use the `@SpringBootTest` annotation with `WebEnvironment.RANDOM_PORT` or `WebEnvironment.DEFINED_PORT`, you can inject a fully configured `TestRestTemplate` and start using it. -If necessary, additional customizations can be applied through the `RestTemplateBuilder` bean. -Any URLs that do not specify a host and port automatically connect to the embedded server, as shown in the following example: - -include::code:MySpringBootTests[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/whats-next.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/whats-next.adoc deleted file mode 100644 index 8affb1c76a24..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/whats-next.adoc +++ /dev/null @@ -1,6 +0,0 @@ -[[features.whats-next]] -== What to Read Next -If you want to learn more about any of the classes discussed in this section, see the {spring-boot-api}/[Spring Boot API documentation] or you can browse the {spring-boot-code}[source code directly]. -If you have specific questions, see the <> section. - -If you are comfortable with Spring Boot's core features, you can continue on and read about <>. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-help.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-help.adoc deleted file mode 100644 index 93c8cbdc82f6..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-help.adoc +++ /dev/null @@ -1,18 +0,0 @@ -[[getting-help]] -= Getting Help -include::attributes.adoc[] - -If you have trouble with Spring Boot, we would like to help. - -* Try the <>. -They provide solutions to the most common questions. -* Learn the Spring basics. -Spring Boot builds on many other Spring projects. -Check the https://spring.io[spring.io] web-site for a wealth of reference documentation. -If you are starting out with Spring, try one of the https://spring.io/guides[guides]. -* Ask a question. -We monitor https://stackoverflow.com[stackoverflow.com] for questions tagged with https://stackoverflow.com/tags/spring-boot[`spring-boot`]. -* Report bugs with Spring Boot at https://github.com/spring-projects/spring-boot/issues. - -NOTE: All of Spring Boot is open source, including the documentation. -If you find problems with the docs or if you want to improve them, please {spring-boot-code}[get involved]. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started.adoc deleted file mode 100644 index 57ea7dbd2d9c..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started.adoc +++ /dev/null @@ -1,23 +0,0 @@ -[[getting-started]] -= Getting Started -include::attributes.adoc[] - - - -If you are getting started with Spring Boot, or "`Spring`" in general, start by reading this section. -It answers the basic "`what?`", "`how?`" and "`why?`" questions. -It includes an introduction to Spring Boot, along with installation instructions. -We then walk you through building your first Spring Boot application, discussing some core principles as we go. - - - -include::getting-started/introducing-spring-boot.adoc[] - -include::getting-started/system-requirements.adoc[] - -include::getting-started/installing.adoc[] - -include::getting-started/first-application.adoc[] - -include::getting-started/whats-next.adoc[] - diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/first-application.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/first-application.adoc deleted file mode 100644 index c49470f65cd4..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/first-application.adoc +++ /dev/null @@ -1,518 +0,0 @@ -[[getting-started.first-application]] -== Developing Your First Spring Boot Application -This section describes how to develop a small "`Hello World!`" web application that highlights some of Spring Boot's key features. -You can choose between Maven or Gradle as the build system. - -[TIP] -==== -The https://spring.io[spring.io] website contains many "`Getting Started`" https://spring.io/guides[guides] that use Spring Boot. -If you need to solve a specific problem, check there first. - -You can shortcut the steps below by going to https://start.spring.io and choosing the "Web" starter from the dependencies searcher. -Doing so generates a new project structure so that you can <>. -Check the https://github.com/spring-io/start.spring.io/blob/main/USING.adoc[start.spring.io user guide] for more details. -==== - - - -[[getting-started.first-application.prerequisites]] -=== Prerequisites -Before we begin, open a terminal and run the following commands to ensure that you have a valid version of Java installed: - -[source,shell,indent=0,subs="verbatim"] ----- - $ java -version - openjdk version "17.0.4.1" 2022-08-12 LTS - OpenJDK Runtime Environment (build 17.0.4.1+1-LTS) - OpenJDK 64-Bit Server VM (build 17.0.4.1+1-LTS, mixed mode, sharing) ----- - -NOTE: This sample needs to be created in its own directory. -Subsequent instructions assume that you have created a suitable directory and that it is your current directory. - - - -[[getting-started.first-application.prerequisites.maven]] -==== Maven -If you want to use Maven, ensure that you have Maven installed: - -[source,shell,indent=0,subs="verbatim"] ----- - $ mvn -v - Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0) - Maven home: usr/Users/developer/tools/maven/3.8.5 - Java version: 17.0.4.1, vendor: BellSoft, runtime: /Users/developer/sdkman/candidates/java/17.0.4.1-librca ----- - - - -[[getting-started.first-application.prerequisites.gradle]] -==== Gradle -If you want to use Gradle, ensure that you have Gradle installed: - -[source,shell,indent=0,subs="verbatim"] ----- - $ gradle --version - - ------------------------------------------------------------ - Gradle 8.1.1 - ------------------------------------------------------------ - - Build time: 2023-04-21 12:31:26 UTC - Revision: 1cf537a851c635c364a4214885f8b9798051175b - - Kotlin: 1.8.10 - Groovy: 3.0.15 - Ant: Apache Ant(TM) version 1.10.11 compiled on July 10 2021 - JVM: 17.0.7 (BellSoft 17.0.7+7-LTS) - OS: Linux 6.2.12-200.fc37.aarch64 aarch64 ----- - - - -[[getting-started.first-application.pom]] -=== Setting up the project with Maven -We need to start by creating a Maven `pom.xml` file. -The `pom.xml` is the recipe that is used to build your project. -Open your favorite text editor and add the following: - -[source,xml,indent=0,subs="verbatim,attributes"] ----- - - - 4.0.0 - - com.example - myproject - 0.0.1-SNAPSHOT - - - org.springframework.boot - spring-boot-starter-parent - {spring-boot-version} - - - - -ifeval::["{artifact-release-type}" != "release"] - - - - spring-snapshots - https://repo.spring.io/snapshot - true - - - spring-milestones - https://repo.spring.io/milestone - - - - - spring-snapshots - https://repo.spring.io/snapshot - - - spring-milestones - https://repo.spring.io/milestone - - -endif::[] - ----- - -The preceding listing should give you a working build. -You can test it by running `mvn package` (for now, you can ignore the "`jar will be empty - no content was marked for inclusion!`" warning). - -NOTE: At this point, you could import the project into an IDE (most modern Java IDEs include built-in support for Maven). -For simplicity, we continue to use a plain text editor for this example. - - - -[[getting-started.first-application.gradle]] -=== Setting up the project with Gradle -We need to start by creating a Gradle `build.gradle` file. -The `build.gradle` is the build script that is used to build your project. -Open your favorite text editor and add the following: - -[source,gradle,indent=0,subs="verbatim,attributes"] ----- - plugins { - id 'java' - id 'org.springframework.boot' version '{spring-boot-version}' - } - - apply plugin: 'io.spring.dependency-management' - - group = 'com.example' - version = '0.0.1-SNAPSHOT' - sourceCompatibility = '17' - - repositories { - mavenCentral() -ifeval::["{artifact-release-type}" != "release"] - maven { url 'https://repo.spring.io/milestone' } - maven { url 'https://repo.spring.io/snapshot' } -endif::[] - } - - dependencies { - } ----- - -The preceding listing should give you a working build. -You can test it by running `gradle classes`. - -NOTE: At this point, you could import the project into an IDE (most modern Java IDEs include built-in support for Gradle). -For simplicity, we continue to use a plain text editor for this example. - - - -[[getting-started.first-application.dependencies]] -=== Adding Classpath Dependencies -Spring Boot provides a number of "`Starters`" that let you add jars to your classpath. -"`Starters`" provide dependencies that you are likely to need when developing a specific type of application. - - - -[[getting-started.first-application.dependencies.maven]] -==== Maven -Most Spring Boot applications use the `spring-boot-starter-parent` in the `parent` section of the POM. -The `spring-boot-starter-parent` is a special starter that provides useful Maven defaults. -It also provides a <> section so that you can omit `version` tags for "`blessed`" dependencies. - -Since we are developing a web application, we add a `spring-boot-starter-web` dependency. -Before that, we can look at what we currently have by running the following command: - -[source,shell,indent=0,subs="verbatim"] ----- - $ mvn dependency:tree - - [INFO] com.example:myproject:jar:0.0.1-SNAPSHOT ----- - -The `mvn dependency:tree` command prints a tree representation of your project dependencies. -You can see that `spring-boot-starter-parent` provides no dependencies by itself. -To add the necessary dependencies, edit your `pom.xml` and add the `spring-boot-starter-web` dependency immediately below the `parent` section: - -[source,xml,indent=0,subs="verbatim"] ----- - - - org.springframework.boot - spring-boot-starter-web - - ----- - -If you run `mvn dependency:tree` again, you see that there are now a number of additional dependencies, including the Tomcat web server and Spring Boot itself. - - - -[[getting-started.first-application.dependencies.gradle]] -==== Gradle -Most Spring Boot applications use the `org.springframework.boot` Gradle plugin. -This plugin provides useful defaults and Gradle tasks. -The `io.spring.dependency-management` Gradle plugin provides <> so that you can omit `version` tags for "`blessed`" dependencies. - -Since we are developing a web application, we add a `spring-boot-starter-web` dependency. -Before that, we can look at what we currently have by running the following command: - -[source,shell,indent=0,subs="verbatim"] ----- - $ gradle dependencies - - > Task :dependencies - - ------------------------------------------------------------ - Root project 'myproject' - ------------------------------------------------------------ ----- - -The `gradle dependencies` command prints a tree representation of your project dependencies. -Right now, the project has no dependencies. -To add the necessary dependencies, edit your `build.gradle` and add the `spring-boot-starter-web` dependency in the `dependencies` section: - -[source,gradle,indent=0,subs="verbatim"] ----- - dependencies { - implementation 'org.springframework.boot:spring-boot-starter-web' - } ----- - -If you run `gradle dependencies` again, you see that there are now a number of additional dependencies, including the Tomcat web server and Spring Boot itself. - - - -[[getting-started.first-application.code]] -=== Writing the Code -To finish our application, we need to create a single Java file. -By default, Maven and Gradle compile sources from `src/main/java`, so you need to create that directory structure and then add a file named `src/main/java/MyApplication.java` to contain the following code: - -[chomp_package_replacement=com.example] -include::code:MyApplication[] - -Although there is not much code here, quite a lot is going on. -We step through the important parts in the next few sections. - - - -[[getting-started.first-application.code.mvc-annotations]] -==== The @RestController and @RequestMapping Annotations -The first annotation on our `MyApplication` class is `@RestController`. -This is known as a _stereotype_ annotation. -It provides hints for people reading the code and for Spring that the class plays a specific role. -In this case, our class is a web `@Controller`, so Spring considers it when handling incoming web requests. - -The `@RequestMapping` annotation provides "`routing`" information. -It tells Spring that any HTTP request with the `/` path should be mapped to the `home` method. -The `@RestController` annotation tells Spring to render the resulting string directly back to the caller. - -TIP: The `@RestController` and `@RequestMapping` annotations are Spring MVC annotations (they are not specific to Spring Boot). -See the {spring-framework-docs}/web/webmvc.html[MVC section] in the Spring Reference Documentation for more details. - - - -[[getting-started.first-application.code.spring-boot-application]] -==== The @SpringBootApplication Annotation -The second class-level annotation is `@SpringBootApplication`. -This annotation is known as a _meta-annotation_, it combines `@SpringBootConfiguration`, `@EnableAutoConfiguration` and `@ComponentScan`. - -Of those, the annotation we're most interested in here is `@EnableAutoConfiguration`. -`@EnableAutoConfiguration` tells Spring Boot to "`guess`" how you want to configure Spring, based on the jar dependencies that you have added. -Since `spring-boot-starter-web` added Tomcat and Spring MVC, the auto-configuration assumes that you are developing a web application and sets up Spring accordingly. - -.Starters and Auto-configuration -**** -Auto-configuration is designed to work well with "`Starters`", but the two concepts are not directly tied. -You are free to pick and choose jar dependencies outside of the starters. -Spring Boot still does its best to auto-configure your application. -**** - - - -[[getting-started.first-application.code.main-method]] -==== The "`main`" Method -The final part of our application is the `main` method. -This is a standard method that follows the Java convention for an application entry point. -Our main method delegates to Spring Boot's `SpringApplication` class by calling `run`. -`SpringApplication` bootstraps our application, starting Spring, which, in turn, starts the auto-configured Tomcat web server. -We need to pass `MyApplication.class` as an argument to the `run` method to tell `SpringApplication` which is the primary Spring component. -The `args` array is also passed through to expose any command-line arguments. - - - -[[getting-started.first-application.run]] -=== Running the Example - - - -[[getting-started.first-application.run.maven]] -==== Maven -At this point, your application should work. -Since you used the `spring-boot-starter-parent` POM, you have a useful `run` goal that you can use to start the application. -Type `mvn spring-boot:run` from the root project directory to start the application. -You should see output similar to the following: - -[source,shell,indent=0,subs="verbatim,attributes"] ----- - $ mvn spring-boot:run - - . ____ _ __ _ _ - /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ - ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ - \\/ ___)| |_)| | | | | || (_| | ) ) ) ) - ' |____| .__|_| |_|_| |_\__, | / / / / - =========|_|==============|___/=/_/_/_/ - :: Spring Boot :: (v{spring-boot-version}) - ....... . . . - ....... . . . (log output here) - ....... . . . - ........ Started MyApplication in 0.906 seconds (process running for 6.514) ----- - -If you open a web browser to `http://localhost:8080`, you should see the following output: - -[indent=0] ----- - Hello World! ----- - -To gracefully exit the application, press `ctrl-c`. - - - -[[getting-started.first-application.run.gradle]] -==== Gradle -At this point, your application should work. -Since you used the `org.springframework.boot` Gradle plugin, you have a useful `bootRun` goal that you can use to start the application. -Type `gradle bootRun` from the root project directory to start the application. -You should see output similar to the following: - -[source,shell,indent=0,subs="verbatim,attributes"] ----- - $ gradle bootRun - - . ____ _ __ _ _ - /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ - ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ - \\/ ___)| |_)| | | | | || (_| | ) ) ) ) - ' |____| .__|_| |_|_| |_\__, | / / / / - =========|_|==============|___/=/_/_/_/ - :: Spring Boot :: (v{spring-boot-version}) - ....... . . . - ....... . . . (log output here) - ....... . . . - ........ Started MyApplication in 0.906 seconds (process running for 6.514) ----- - -If you open a web browser to `http://localhost:8080`, you should see the following output: - -[indent=0] ----- - Hello World! ----- - -To gracefully exit the application, press `ctrl-c`. - - - -[[getting-started.first-application.executable-jar]] -=== Creating an Executable Jar -We finish our example by creating a completely self-contained executable jar file that we could run in production. -Executable jars (sometimes called "`fat jars`") are archives containing your compiled classes along with all of the jar dependencies that your code needs to run. - -.Executable jars and Java -**** -Java does not provide a standard way to load nested jar files (jar files that are themselves contained within a jar). -This can be problematic if you are looking to distribute a self-contained application. - -To solve this problem, many developers use "`uber`" jars. -An uber jar packages all the classes from all the application's dependencies into a single archive. -The problem with this approach is that it becomes hard to see which libraries are in your application. -It can also be problematic if the same filename is used (but with different content) in multiple jars. - -Spring Boot takes a <> and lets you actually nest jars directly. -**** - - - -[[getting-started.first-application.executable-jar.maven]] -==== Maven -To create an executable jar, we need to add the `spring-boot-maven-plugin` to our `pom.xml`. -To do so, insert the following lines just below the `dependencies` section: - -[source,xml,indent=0,subs="verbatim"] ----- - - - - org.springframework.boot - spring-boot-maven-plugin - - - ----- - -NOTE: The `spring-boot-starter-parent` POM includes `` configuration to bind the `repackage` goal. -If you do not use the parent POM, you need to declare this configuration yourself. -See the {spring-boot-maven-plugin-docs}#getting-started[plugin documentation] for details. - -Save your `pom.xml` and run `mvn package` from the command line, as follows: - -[source,shell,indent=0,subs="verbatim,attributes"] ----- - $ mvn package - - [INFO] Scanning for projects... - [INFO] - [INFO] ------------------------------------------------------------------------ - [INFO] Building myproject 0.0.1-SNAPSHOT - [INFO] ------------------------------------------------------------------------ - [INFO] .... .. - [INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ myproject --- - [INFO] Building jar: /Users/developer/example/spring-boot-example/target/myproject-0.0.1-SNAPSHOT.jar - [INFO] - [INFO] --- spring-boot-maven-plugin:{spring-boot-version}:repackage (default) @ myproject --- - [INFO] ------------------------------------------------------------------------ - [INFO] BUILD SUCCESS - [INFO] ------------------------------------------------------------------------ ----- - -If you look in the `target` directory, you should see `myproject-0.0.1-SNAPSHOT.jar`. -The file should be around 18 MB in size. -If you want to peek inside, you can use `jar tvf`, as follows: - -[source,shell,indent=0,subs="verbatim"] ----- - $ jar tvf target/myproject-0.0.1-SNAPSHOT.jar ----- - -You should also see a much smaller file named `myproject-0.0.1-SNAPSHOT.jar.original` in the `target` directory. -This is the original jar file that Maven created before it was repackaged by Spring Boot. - -To run that application, use the `java -jar` command, as follows: - -[source,shell,indent=0,subs="verbatim,attributes"] ----- - $ java -jar target/myproject-0.0.1-SNAPSHOT.jar - - . ____ _ __ _ _ - /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ - ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ - \\/ ___)| |_)| | | | | || (_| | ) ) ) ) - ' |____| .__|_| |_|_| |_\__, | / / / / - =========|_|==============|___/=/_/_/_/ - :: Spring Boot :: (v{spring-boot-version}) - ....... . . . - ....... . . . (log output here) - ....... . . . - ........ Started MyApplication in 0.999 seconds (process running for 1.253) ----- - -As before, to exit the application, press `ctrl-c`. - - - -[[getting-started.first-application.executable-jar.gradle]] -==== Gradle -To create an executable jar, we need to run `gradle bootJar` from the command line, as follows: - -[source,shell,indent=0,subs="verbatim,attributes"] ----- - $ gradle bootJar - - BUILD SUCCESSFUL in 639ms - 3 actionable tasks: 3 executed ----- - -If you look in the `build/libs` directory, you should see `myproject-0.0.1-SNAPSHOT.jar`. -The file should be around 18 MB in size. -If you want to peek inside, you can use `jar tvf`, as follows: - -[source,shell,indent=0,subs="verbatim"] ----- - $ jar tvf build/libs/myproject-0.0.1-SNAPSHOT.jar ----- - -To run that application, use the `java -jar` command, as follows: - -[source,shell,indent=0,subs="verbatim,attributes"] ----- - $ java -jar build/libs/myproject-0.0.1-SNAPSHOT.jar - - . ____ _ __ _ _ - /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ - ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ - \\/ ___)| |_)| | | | | || (_| | ) ) ) ) - ' |____| .__|_| |_|_| |_\__, | / / / / - =========|_|==============|___/=/_/_/_/ - :: Spring Boot :: (v{spring-boot-version}) - ....... . . . - ....... . . . (log output here) - ....... . . . - ........ Started MyApplication in 0.999 seconds (process running for 1.253) ----- - -As before, to exit the application, press `ctrl-c`. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/installing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/installing.adoc deleted file mode 100644 index 4c5d3cac36e6..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/installing.adoc +++ /dev/null @@ -1,198 +0,0 @@ -[[getting-started.installing]] -== Installing Spring Boot -Spring Boot can be used with "`classic`" Java development tools or installed as a command line tool. -Either way, you need https://www.java.com[Java SDK v17] or higher. -Before you begin, you should check your current Java installation by using the following command: - -[source,shell,indent=0,subs="verbatim"] ----- - $ java -version ----- - -If you are new to Java development or if you want to experiment with Spring Boot, you might want to try the <> (Command Line Interface) first. -Otherwise, read on for "`classic`" installation instructions. - - - -[[getting-started.installing.java]] -=== Installation Instructions for the Java Developer -You can use Spring Boot in the same way as any standard Java library. -To do so, include the appropriate `+spring-boot-*.jar+` files on your classpath. -Spring Boot does not require any special tools integration, so you can use any IDE or text editor. -Also, there is nothing special about a Spring Boot application, so you can run and debug a Spring Boot application as you would any other Java program. - -Although you _could_ copy Spring Boot jars, we generally recommend that you use a build tool that supports dependency management (such as Maven or Gradle). - - - -[[getting-started.installing.java.maven]] -==== Maven Installation -Spring Boot is compatible with Apache Maven 3.6.3 or later. -If you do not already have Maven installed, you can follow the instructions at https://maven.apache.org. - -TIP: On many operating systems, Maven can be installed with a package manager. -If you use OSX Homebrew, try `brew install maven`. -Ubuntu users can run `sudo apt-get install maven`. -Windows users with https://chocolatey.org/[Chocolatey] can run `choco install maven` from an elevated (administrator) prompt. - -Spring Boot dependencies use the `org.springframework.boot` group id. -Typically, your Maven POM file inherits from the `spring-boot-starter-parent` project and declares dependencies to one or more <>. -Spring Boot also provides an optional <> to create executable jars. - -More details on getting started with Spring Boot and Maven can be found in the {spring-boot-maven-plugin-docs}#getting-started[Getting Started section] of the Maven plugin's reference guide. - - - -[[getting-started.installing.java.gradle]] -==== Gradle Installation -Spring Boot is compatible with Gradle 7.x (7.5 or later) and 8.x. -If you do not already have Gradle installed, you can follow the instructions at https://gradle.org. - -Spring Boot dependencies can be declared by using the `org.springframework.boot` `group`. -Typically, your project declares dependencies to one or more <>. -Spring Boot provides a useful <> that can be used to simplify dependency declarations and to create executable jars. - -.Gradle Wrapper -**** -The Gradle Wrapper provides a nice way of "`obtaining`" Gradle when you need to build a project. -It is a small script and library that you commit alongside your code to bootstrap the build process. -See {gradle-docs}/gradle_wrapper.html for details. -**** - -More details on getting started with Spring Boot and Gradle can be found in the {spring-boot-gradle-plugin-docs}#getting-started[Getting Started section] of the Gradle plugin's reference guide. - - - -[[getting-started.installing.cli]] -=== Installing the Spring Boot CLI -The Spring Boot CLI (Command Line Interface) is a command line tool that you can use to quickly prototype with Spring. - -You do not need to use the CLI to work with Spring Boot, but it is a quick way to get a Spring application off the ground without an IDE. - - - -[[getting-started.installing.cli.manual-installation]] -==== Manual Installation -ifeval::["{artifact-release-type}" == "snapshot"] -You can download one of the `spring-boot-cli-\*-bin.zip` or `spring-boot-cli-*-bin.tar.gz` files from the {artifact-download-repo}/org/springframework/boot/spring-boot-cli/{spring-boot-version}/[Spring software repository]. -endif::[] -ifeval::["{artifact-release-type}" != "snapshot"] -You can download the Spring CLI distribution from one of the following locations: - -* {artifact-download-repo}/org/springframework/boot/spring-boot-cli/{spring-boot-version}/spring-boot-cli-{spring-boot-version}-bin.zip[spring-boot-cli-{spring-boot-version}-bin.zip] -* {artifact-download-repo}/org/springframework/boot/spring-boot-cli/{spring-boot-version}/spring-boot-cli-{spring-boot-version}-bin.tar.gz[spring-boot-cli-{spring-boot-version}-bin.tar.gz] -endif::[] - - -Once downloaded, follow the {github-raw}/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/content/INSTALL.txt[INSTALL.txt] instructions from the unpacked archive. -In summary, there is a `spring` script (`spring.bat` for Windows) in a `bin/` directory in the `.zip` file. -Alternatively, you can use `java -jar` with the `.jar` file (the script helps you to be sure that the classpath is set correctly). - - - -[[getting-started.installing.cli.sdkman]] -==== Installation with SDKMAN! -SDKMAN! (The Software Development Kit Manager) can be used for managing multiple versions of various binary SDKs, including Groovy and the Spring Boot CLI. -Get SDKMAN! from https://sdkman.io and install Spring Boot by using the following commands: - -[source,shell,indent=0,subs="verbatim,attributes"] ----- - $ sdk install springboot - $ spring --version - Spring CLI v{spring-boot-version} ----- - -If you develop features for the CLI and want access to the version you built, use the following commands: - -[source,shell,indent=0,subs="verbatim,attributes"] ----- - $ sdk install springboot dev /path/to/spring-boot/spring-boot-cli/target/spring-boot-cli-{spring-boot-version}-bin/spring-{spring-boot-version}/ - $ sdk default springboot dev - $ spring --version - Spring CLI v{spring-boot-version} ----- - -The preceding instructions install a local instance of `spring` called the `dev` instance. -It points at your target build location, so every time you rebuild Spring Boot, `spring` is up-to-date. - -You can see it by running the following command: - -[source,shell,indent=0,subs="verbatim,attributes"] ----- - $ sdk ls springboot - - ================================================================================ - Available Springboot Versions - ================================================================================ - > + dev - * {spring-boot-version} - - ================================================================================ - + - local version - * - installed - > - currently in use - ================================================================================ ----- - - - -[[getting-started.installing.cli.homebrew]] -==== OSX Homebrew Installation -If you are on a Mac and use https://brew.sh/[Homebrew], you can install the Spring Boot CLI by using the following commands: - -[source,shell,indent=0,subs="verbatim"] ----- - $ brew tap spring-io/tap - $ brew install spring-boot ----- - -Homebrew installs `spring` to `/usr/local/bin`. - -NOTE: If you do not see the formula, your installation of brew might be out-of-date. -In that case, run `brew update` and try again. - - - -[[getting-started.installing.cli.macports]] -==== MacPorts Installation -If you are on a Mac and use https://www.macports.org/[MacPorts], you can install the Spring Boot CLI by using the following command: - -[source,shell,indent=0,subs="verbatim"] ----- - $ sudo port install spring-boot-cli ----- - - - -[[getting-started.installing.cli.completion]] -==== Command-line Completion -The Spring Boot CLI includes scripts that provide command completion for the https://en.wikipedia.org/wiki/Bash_%28Unix_shell%29[BASH] and https://en.wikipedia.org/wiki/Z_shell[zsh] shells. -You can `source` the script (also named `spring`) in any shell or put it in your personal or system-wide bash completion initialization. -On a Debian system, the system-wide scripts are in `/shell-completion/bash` and all scripts in that directory are executed when a new shell starts. -For example, to run the script manually if you have installed by using SDKMAN!, use the following commands: - -[source,shell,indent=0,subs="verbatim"] ----- - $ . ~/.sdkman/candidates/springboot/current/shell-completion/bash/spring - $ spring - grab help jar run test version ----- - -NOTE: If you install the Spring Boot CLI by using Homebrew or MacPorts, the command-line completion scripts are automatically registered with your shell. - - - -[[getting-started.installing.cli.scoop]] -==== Windows Scoop Installation -If you are on a Windows and use https://scoop.sh/[Scoop], you can install the Spring Boot CLI by using the following commands: - -[indent=0] ----- - > scoop bucket add extras - > scoop install springboot ----- - -Scoop installs `spring` to `~/scoop/apps/springboot/current/bin`. - -NOTE: If you do not see the app manifest, your installation of scoop might be out-of-date. -In that case, run `scoop update` and try again. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/introducing-spring-boot.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/introducing-spring-boot.adoc deleted file mode 100644 index 06602519faf6..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/introducing-spring-boot.adoc +++ /dev/null @@ -1,14 +0,0 @@ -[[getting-started.introducing-spring-boot]] -== Introducing Spring Boot -Spring Boot helps you to create stand-alone, production-grade Spring-based applications that you can run. -We take an opinionated view of the Spring platform and third-party libraries, so that you can get started with minimum fuss. -Most Spring Boot applications need very little Spring configuration. - -You can use Spring Boot to create Java applications that can be started by using `java -jar` or more traditional war deployments. - -Our primary goals are: - -* Provide a radically faster and widely accessible getting-started experience for all Spring development. -* Be opinionated out of the box but get out of the way quickly as requirements start to diverge from the defaults. -* Provide a range of non-functional features that are common to large classes of projects (such as embedded servers, security, metrics, health checks, and externalized configuration). -* Absolutely no code generation (when not targeting native image) and no requirement for XML configuration. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/system-requirements.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/system-requirements.adoc deleted file mode 100644 index 9029d7a99626..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/system-requirements.adoc +++ /dev/null @@ -1,58 +0,0 @@ -[[getting-started.system-requirements]] -== System Requirements -Spring Boot {spring-boot-version} requires https://www.java.com[Java 17] and is compatible up to and including Java 21. -{spring-framework-docs}/[Spring Framework {spring-framework-version}] or above is also required. - -Explicit build support is provided for the following build tools: - -|=== -| Build Tool | Version - -| Maven -| 3.6.3 or later - -| Gradle -| 7.x (7.5 or later) and 8.x -|=== - - - -[[getting-started.system-requirements.servlet-containers]] -=== Servlet Containers -Spring Boot supports the following embedded servlet containers: - -|=== -| Name | Servlet Version - -| Tomcat 10.1 -| 6.0 - -| Jetty 11.0 -| 5.0 - -| Undertow 2.3 -| 6.0 -|=== - -You can also deploy Spring Boot applications to any servlet 5.0+ compatible container. - - - -[[getting-started.system-requirements.graal]] -=== GraalVM Native Images -Spring Boot applications can be <> using GraalVM {graal-version} or above. - -Images can be created using the https://github.com/graalvm/native-build-tools[native build tools] Gradle/Maven plugins or `native-image` tool provided by GraalVM. -You can also create native images using the https://github.com/paketo-buildpacks/native-image[native-image Paketo buildpack]. - -The following versions are supported: - -|=== -| Name | Version - -| GraalVM Community -| {graal-version} - -| Native Build Tools -| {native-build-tools-version} -|=== diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/whats-next.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/whats-next.adoc deleted file mode 100644 index 63dae39f0c67..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/whats-next.adoc +++ /dev/null @@ -1,8 +0,0 @@ -[[getting-started.whats-next]] -== What to Read Next -Hopefully, this section provided some of the Spring Boot basics and got you on your way to writing your own applications. -If you are a task-oriented type of developer, you might want to jump over to https://spring.io and follow some of the https://spring.io/guides/[getting started] guides that solve specific "`How do I do that with Spring?`" problems. -We also have Spring Boot-specific "`<>`" reference documentation. - -Otherwise, the next logical step is to read _<>_. -If you are really impatient, you could also jump ahead and read about _<>_. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc deleted file mode 100644 index 3a94bf4a8480..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc +++ /dev/null @@ -1,56 +0,0 @@ -[[howto]] -= "`How-to`" Guides -include::attributes.adoc[] - - - -This section provides answers to some common '`how do I do that...`' questions that often arise when using Spring Boot. -Its coverage is not exhaustive, but it does cover quite a lot. - -If you have a specific problem that we do not cover here, you might want to check https://stackoverflow.com/tags/spring-boot[stackoverflow.com] to see if someone has already provided an answer. -This is also a great place to ask new questions (please use the `spring-boot` tag). - -We are also more than happy to extend this section. -If you want to add a '`how-to`', send us a {spring-boot-code}[pull request]. - - - -include::howto/application.adoc[] - -include::howto/properties-and-configuration.adoc[] - -include::howto/webserver.adoc[] - -include::howto/spring-mvc.adoc[] - -include::howto/jersey.adoc[] - -include::howto/http-clients.adoc[] - -include::howto/logging.adoc[] - -include::howto/data-access.adoc[] - -include::howto/data-initialization.adoc[] - -include::howto/nosql.adoc[] - -include::howto/messaging.adoc[] - -include::howto/batch.adoc[] - -include::howto/actuator.adoc[] - -include::howto/security.adoc[] - -include::howto/hotswapping.adoc[] - -include::howto/testing.adoc[] - -include::howto/build.adoc[] - -include::howto/aot.adoc[] - -include::howto/traditional-deployment.adoc[] - -include::howto/docker-compose.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/actuator.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/actuator.adoc deleted file mode 100644 index 7c0c993eb793..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/actuator.adoc +++ /dev/null @@ -1,55 +0,0 @@ -[[howto.actuator]] -== Actuator -Spring Boot includes the Spring Boot Actuator. -This section answers questions that often arise from its use. - - - -[[howto.actuator.change-http-port-or-address]] -=== Change the HTTP Port or Address of the Actuator Endpoints -In a standalone application, the Actuator HTTP port defaults to the same as the main HTTP port. -To make the application listen on a different port, set the external property: configprop:management.server.port[]. -To listen on a completely different network address (such as when you have an internal network for management and an external one for user applications), you can also set `management.server.address` to a valid IP address to which the server is able to bind. - -For more detail, see the {spring-boot-actuator-autoconfigure-module-code}/web/server/ManagementServerProperties.java[`ManagementServerProperties`] source code and "`<>`" in the "`Production-ready features`" section. - - - -[[howto.actuator.customize-whitelabel-error-page]] -=== Customize the '`whitelabel`' Error Page -Spring Boot installs a '`whitelabel`' error page that you see in a browser client if you encounter a server error (machine clients consuming JSON and other media types should see a sensible response with the right error code). - -NOTE: Set `server.error.whitelabel.enabled=false` to switch the default error page off. -Doing so restores the default of the servlet container that you are using. -Note that Spring Boot still tries to resolve the error view, so you should probably add your own error page rather than disabling it completely. - -Overriding the error page with your own depends on the templating technology that you use. -For example, if you use Thymeleaf, you can add an `error.html` template. -If you use FreeMarker, you can add an `error.ftlh` template. -In general, you need a `View` that resolves with a name of `error` or a `@Controller` that handles the `/error` path. -Unless you replaced some of the default configuration, you should find a `BeanNameViewResolver` in your `ApplicationContext`, so a `@Bean` named `error` would be one way of doing that. -See {spring-boot-autoconfigure-module-code}/web/servlet/error/ErrorMvcAutoConfiguration.java[`ErrorMvcAutoConfiguration`] for more options. - -See also the section on "`<>`" for details of how to register handlers in the servlet container. - - - -[[howto.actuator.customizing-sanitization]] -=== Customizing Sanitization -To take control over the sanitization, define a `SanitizingFunction` bean. -The `SanitizableData` with which the function is called provides access to the key and value as well as the `PropertySource` from which they came. -This allows you to, for example, sanitize every value that comes from a particular property source. -Each `SanitizingFunction` is called in order until a function changes the value of the sanitizable data. - - - -[[howto.actuator.map-health-indicators-to-metrics]] -=== Map Health Indicators to Micrometer Metrics -Spring Boot health indicators return a `Status` type to indicate the overall system health. -If you want to monitor or alert on levels of health for a particular application, you can export these statuses as metrics with Micrometer. -By default, the status codes "`UP`", "`DOWN`", "`OUT_OF_SERVICE`" and "`UNKNOWN`" are used by Spring Boot. -To export these, you will need to convert these states to some set of numbers so that they can be used with a Micrometer `Gauge`. - -The following example shows one way to write such an exporter: - -include::code:MyHealthMetricsExportConfiguration[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/aot.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/aot.adoc deleted file mode 100644 index 8ed5cae64214..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/aot.adoc +++ /dev/null @@ -1,53 +0,0 @@ -[[howto.aot]] -== Ahead-of-time processing - -A number of questions often arise when people use the ahead-of-time processing of Spring Boot applications. -This section addresses those questions. - -[[howto.aot.conditions]] -=== Conditions - -Ahead-of-time processing optimizes the application and evaluates {spring-framework-api}/context/annotation/Conditional.html[conditions] based on the environment at build time. -<> are implemented through conditions and are therefore affected, too. - -If you want beans that are created based on a condition in an ahead-of-time optimized application, you have to set up the environment when building the application. -The beans which are created while ahead-of-time processing at build time are then always created when running the application and can't be switched off. -To do this, you can set the profiles which should be used when building the application. - -For Maven, this works by setting the `profiles` configuration of the `spring-boot-maven-plugin:process-aot` execution: - -[source,xml,indent=0,subs="verbatim"] ----- - - native - - - - - org.springframework.boot - spring-boot-maven-plugin - - - process-aot - - profile-a,profile-b - - - - - - - - ----- - -For Gradle, you need to configure the `ProcessAot` task: - -[source,gradle,indent=0,subs="verbatim"] ----- - tasks.withType(org.springframework.boot.gradle.tasks.aot.ProcessAot).configureEach { - args('--spring.profiles.active=profile-a,profile-b') - } ----- - -Profiles which only change configuration properties that don't influence conditions are supported without limitations when running ahead-of-time optimized applications. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/application.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/application.adoc deleted file mode 100644 index 12220eda9229..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/application.adoc +++ /dev/null @@ -1,108 +0,0 @@ -[[howto.application]] -== Spring Boot Application -This section includes topics relating directly to Spring Boot applications. - - - -[[howto.application.failure-analyzer]] -=== Create Your Own FailureAnalyzer -{spring-boot-module-api}/diagnostics/FailureAnalyzer.html[`FailureAnalyzer`] is a great way to intercept an exception on startup and turn it into a human-readable message, wrapped in a {spring-boot-module-api}/diagnostics/FailureAnalysis.html[`FailureAnalysis`]. -Spring Boot provides such an analyzer for application-context-related exceptions, JSR-303 validations, and more. -You can also create your own. - -`AbstractFailureAnalyzer` is a convenient extension of `FailureAnalyzer` that checks the presence of a specified exception type in the exception to handle. -You can extend from that so that your implementation gets a chance to handle the exception only when it is actually present. -If, for whatever reason, you cannot handle the exception, return `null` to give another implementation a chance to handle the exception. - -`FailureAnalyzer` implementations must be registered in `META-INF/spring.factories`. -The following example registers `ProjectConstraintViolationFailureAnalyzer`: - -[source,properties,indent=0,subs="verbatim"] ----- - org.springframework.boot.diagnostics.FailureAnalyzer=\ - com.example.ProjectConstraintViolationFailureAnalyzer ----- - -NOTE: If you need access to the `BeanFactory` or the `Environment`, declare them as constructor arguments in your `FailureAnalyzer` implementation. - - - -[[howto.application.troubleshoot-auto-configuration]] -=== Troubleshoot Auto-configuration -The Spring Boot auto-configuration tries its best to "`do the right thing`", but sometimes things fail, and it can be hard to tell why. - -There is a really useful `ConditionEvaluationReport` available in any Spring Boot `ApplicationContext`. -You can see it if you enable `DEBUG` logging output. -If you use the `spring-boot-actuator` (see <>), there is also a `conditions` endpoint that renders the report in JSON. -Use that endpoint to debug the application and see what features have been added (and which have not been added) by Spring Boot at runtime. - -Many more questions can be answered by looking at the source code and the Javadoc. -When reading the code, remember the following rules of thumb: - -* Look for classes called `+*AutoConfiguration+` and read their sources. - Pay special attention to the `+@Conditional*+` annotations to find out what features they enable and when. - Add `--debug` to the command line or a System property `-Ddebug` to get a log on the console of all the auto-configuration decisions that were made in your app. - In a running application with actuator enabled, look at the `conditions` endpoint (`/actuator/conditions` or the JMX equivalent) for the same information. -* Look for classes that are `@ConfigurationProperties` (such as {spring-boot-autoconfigure-module-code}/web/ServerProperties.java[`ServerProperties`]) and read from there the available external configuration options. - The `@ConfigurationProperties` annotation has a `name` attribute that acts as a prefix to external properties. - Thus, `ServerProperties` has `prefix="server"` and its configuration properties are `server.port`, `server.address`, and others. - In a running application with actuator enabled, look at the `configprops` endpoint. -* Look for uses of the `bind` method on the `Binder` to pull configuration values explicitly out of the `Environment` in a relaxed manner. - It is often used with a prefix. -* Look for `@Value` annotations that bind directly to the `Environment`. -* Look for `@ConditionalOnExpression` annotations that switch features on and off in response to SpEL expressions, normally evaluated with placeholders resolved from the `Environment`. - - - -[[howto.application.customize-the-environment-or-application-context]] -=== Customize the Environment or ApplicationContext Before It Starts -A `SpringApplication` has `ApplicationListeners` and `ApplicationContextInitializers` that are used to apply customizations to the context or environment. -Spring Boot loads a number of such customizations for use internally from `META-INF/spring.factories`. -There is more than one way to register additional customizations: - -* Programmatically, per application, by calling the `addListeners` and `addInitializers` methods on `SpringApplication` before you run it. -* Declaratively, per application, by setting the `context.initializer.classes` or `context.listener.classes` properties. -* Declaratively, for all applications, by adding a `META-INF/spring.factories` and packaging a jar file that the applications all use as a library. - -The `SpringApplication` sends some special `ApplicationEvents` to the listeners (some even before the context is created) and then registers the listeners for events published by the `ApplicationContext` as well. -See "`<>`" in the '`Spring Boot features`' section for a complete list. - -It is also possible to customize the `Environment` before the application context is refreshed by using `EnvironmentPostProcessor`. -Each implementation should be registered in `META-INF/spring.factories`, as shown in the following example: - -[indent=0] ----- - org.springframework.boot.env.EnvironmentPostProcessor=com.example.YourEnvironmentPostProcessor ----- - -The implementation can load arbitrary files and add them to the `Environment`. -For instance, the following example loads a YAML configuration file from the classpath: - -include::code:MyEnvironmentPostProcessor[] - -TIP: The `Environment` has already been prepared with all the usual property sources that Spring Boot loads by default. -It is therefore possible to get the location of the file from the environment. -The preceding example adds the `custom-resource` property source at the end of the list so that a key defined in any of the usual other locations takes precedence. -A custom implementation may define another order. - -CAUTION: While using `@PropertySource` on your `@SpringBootApplication` may seem to be a convenient way to load a custom resource in the `Environment`, we do not recommend it. -Such property sources are not added to the `Environment` until the application context is being refreshed. -This is too late to configure certain properties such as `+logging.*+` and `+spring.main.*+` which are read before refresh begins. - - - -[[howto.application.context-hierarchy]] -=== Build an ApplicationContext Hierarchy (Adding a Parent or Root Context) -You can use the `ApplicationBuilder` class to create parent/child `ApplicationContext` hierarchies. -See "`<>`" in the '`Spring Boot features`' section for more information. - - - -[[howto.application.non-web-application]] -=== Create a Non-web Application -Not all Spring applications have to be web applications (or web services). -If you want to execute some code in a `main` method but also bootstrap a Spring application to set up the infrastructure to use, you can use the `SpringApplication` features of Spring Boot. -A `SpringApplication` changes its `ApplicationContext` class, depending on whether it thinks it needs a web application or not. -The first thing you can do to help it is to leave server-related dependencies (such as the servlet API) off the classpath. -If you cannot do that (for example, you run two applications from the same code base) then you can explicitly call `setWebApplicationType(WebApplicationType.NONE)` on your `SpringApplication` instance or set the `applicationContextClass` property (through the Java API or with external properties). -Application code that you want to run as your business logic can be implemented as a `CommandLineRunner` and dropped into the context as a `@Bean` definition. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/batch.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/batch.adoc deleted file mode 100644 index ee78bac2023e..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/batch.adoc +++ /dev/null @@ -1,72 +0,0 @@ -[[howto.batch]] -== Batch Applications -A number of questions often arise when people use Spring Batch from within a Spring Boot application. -This section addresses those questions. - - - -[[howto.batch.specifying-a-data-source]] -=== Specifying a Batch Data Source -By default, batch applications require a `DataSource` to store job details. -Spring Batch expects a single `DataSource` by default. -To have it use a `DataSource` other than the application’s main `DataSource`, declare a `DataSource` bean, annotating its `@Bean` method with `@BatchDataSource`. -If you do so and want two data sources, remember to mark the other one `@Primary`. -To take greater control, add `@EnableBatchProcessing` to one of your `@Configuration` classes or extend `DefaultBatchConfiguration`. -See the Javadoc of {spring-batch-api}/core/configuration/annotation/EnableBatchProcessing.html[`@EnableBatchProcessing`] -and {spring-batch-api}/core/configuration/support/DefaultBatchConfiguration.html[`DefaultBatchConfiguration`] for more details. - -For more info about Spring Batch, see the {spring-batch}[Spring Batch project page]. - - - -[[howto.batch.running-jobs-on-startup]] -=== Running Spring Batch Jobs on Startup -Spring Batch auto-configuration is enabled by adding `spring-boot-starter-batch` to your application's classpath. - -If a single `Job` is found in the application context, it is executed on startup (see {spring-boot-autoconfigure-module-code}/batch/JobLauncherApplicationRunner.java[`JobLauncherApplicationRunner`] for details). -If multiple `Job` beans are found, the job that should be executed must be specified using configprop:spring.batch.job.name[]. - -To disable running a `Job` found in the application context, set the configprop:spring.batch.job.enabled[] to `false`. - -See {spring-boot-autoconfigure-module-code}/batch/BatchAutoConfiguration.java[BatchAutoConfiguration] for more details. - - - -[[howto.batch.running-from-the-command-line]] -=== Running From the Command Line -Spring Boot converts any command line argument starting with `--` to a property to add to the `Environment`, see <>. -This should not be used to pass arguments to batch jobs. -To specify batch arguments on the command line, use the regular format (that is without `--`), as shown in the following example: - -[source,shell,indent=0,subs="verbatim"] ----- - $ java -jar myapp.jar someParameter=someValue anotherParameter=anotherValue ----- - -If you specify a property of the `Environment` on the command line, it is ignored by the job. -Consider the following command: - -[source,shell,indent=0,subs="verbatim"] ----- - $ java -jar myapp.jar --server.port=7070 someParameter=someValue ----- - -This provides only one argument to the batch job: `someParameter=someValue`. - - - -[[howto.batch.restarting-a-failed-job]] -=== Restarting a stopped or failed Job -To restart a failed `Job`, all parameters (identifying and non-identifying) must be re-specified on the command line. -Non-identifying parameters are *not* copied from the previous execution. -This allows them to be modified or removed. - -NOTE: When you're using a custom `JobParametersIncrementer`, you have to gather all parameters managed by the incrementer to restart a failed execution. - - - -[[howto.batch.storing-job-repository]] -=== Storing the Job Repository -Spring Batch requires a data store for the `Job` repository. -If you use Spring Boot, you must use an actual database. -Note that it can be an in-memory database, see {spring-batch-docs}job.html#configuringJobRepository[Configuring a Job Repository]. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/build.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/build.adoc deleted file mode 100644 index 97c24447a62d..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/build.adoc +++ /dev/null @@ -1,298 +0,0 @@ -[[howto.build]] -== Build -Spring Boot includes build plugins for Maven and Gradle. -This section answers common questions about these plugins. - - - -[[howto.build.generate-info]] -=== Generate Build Information -Both the Maven plugin and the Gradle plugin allow generating build information containing the coordinates, name, and version of the project. -The plugins can also be configured to add additional properties through configuration. -When such a file is present, Spring Boot auto-configures a `BuildProperties` bean. - -To generate build information with Maven, add an execution for the `build-info` goal, as shown in the following example: - -[source,xml,indent=0,subs="verbatim,attributes"] ----- - - - - org.springframework.boot - spring-boot-maven-plugin - {spring-boot-version} - - - - build-info - - - - - - ----- - -TIP: See the {spring-boot-maven-plugin-docs}#goals-build-info[Spring Boot Maven Plugin documentation] for more details. - -The following example does the same with Gradle: - -[source,gradle,indent=0,subs="verbatim"] ----- - springBoot { - buildInfo() - } ----- - -TIP: See the {spring-boot-gradle-plugin-docs}#integrating-with-actuator-build-info[Spring Boot Gradle Plugin documentation] for more details. - - - -[[howto.build.generate-git-info]] -=== Generate Git Information -Both Maven and Gradle allow generating a `git.properties` file containing information about the state of your `git` source code repository when the project was built. - -For Maven users, the `spring-boot-starter-parent` POM includes a pre-configured plugin to generate a `git.properties` file. -To use it, add the following declaration for the https://github.com/git-commit-id/git-commit-id-maven-plugin[`Git Commit Id Plugin`] to your POM: - -[source,xml,indent=0,subs="verbatim"] ----- - - - - io.github.git-commit-id - git-commit-id-maven-plugin - - - ----- - -Gradle users can achieve the same result by using the https://plugins.gradle.org/plugin/com.gorylenko.gradle-git-properties[`gradle-git-properties`] plugin, as shown in the following example: - -[source,gradle,indent=0,subs="verbatim"] ----- - plugins { - id "com.gorylenko.gradle-git-properties" version "2.4.1" - } ----- - -Both the Maven and Gradle plugins allow the properties that are included in `git.properties` to be configured. - -TIP: The commit time in `git.properties` is expected to match the following format: `yyyy-MM-dd'T'HH:mm:ssZ`. -This is the default format for both plugins listed above. -Using this format lets the time be parsed into a `Date` and its format, when serialized to JSON, to be controlled by Jackson's date serialization configuration settings. - - - -[[howto.build.customize-dependency-versions]] -=== Customize Dependency Versions -The `spring-boot-dependencies` POM manages the versions of common dependencies. -The Spring Boot plugins for Maven and Gradle allow these managed dependency versions to be customized using build properties. - -WARNING: Each Spring Boot release is designed and tested against this specific set of third-party dependencies. -Overriding versions may cause compatibility issues. - -To override dependency versions with Maven, see {spring-boot-maven-plugin-docs}#using[this section] of the Maven plugin's documentation. - -To override dependency versions in Gradle, see {spring-boot-gradle-plugin-docs}#managing-dependencies-dependency-management-plugin-customizing[this section] of the Gradle plugin's documentation. - - - -[[howto.build.create-an-executable-jar-with-maven]] -=== Create an Executable JAR with Maven -The `spring-boot-maven-plugin` can be used to create an executable "`fat`" JAR. -If you use the `spring-boot-starter-parent` POM, you can declare the plugin and your jars are repackaged as follows: - -[source,xml,indent=0,subs="verbatim"] ----- - - - - org.springframework.boot - spring-boot-maven-plugin - - - ----- - -If you do not use the parent POM, you can still use the plugin. -However, you must additionally add an `` section, as follows: - -[source,xml,indent=0,subs="verbatim,attributes"] ----- - - - - org.springframework.boot - spring-boot-maven-plugin - {spring-boot-version} - - - - repackage - - - - - - ----- - -See the {spring-boot-maven-plugin-docs}#repackage[plugin documentation] for full usage details. - - - -[[howto.build.use-a-spring-boot-application-as-dependency]] -=== Use a Spring Boot Application as a Dependency -Like a war file, a Spring Boot application is not intended to be used as a dependency. -If your application contains classes that you want to share with other projects, the recommended approach is to move that code into a separate module. -The separate module can then be depended upon by your application and other projects. - -If you cannot rearrange your code as recommended above, Spring Boot's Maven and Gradle plugins must be configured to produce a separate artifact that is suitable for use as a dependency. -The executable archive cannot be used as a dependency as the <> packages application classes in `BOOT-INF/classes`. -This means that they cannot be found when the executable jar is used as a dependency. - -To produce the two artifacts, one that can be used as a dependency and one that is executable, a classifier must be specified. -This classifier is applied to the name of the executable archive, leaving the default archive for use as a dependency. - -To configure a classifier of `exec` in Maven, you can use the following configuration: - -[source,xml,indent=0,subs="verbatim"] ----- - - - - org.springframework.boot - spring-boot-maven-plugin - - exec - - - - ----- - - - -[[howto.build.extract-specific-libraries-when-an-executable-jar-runs]] -=== Extract Specific Libraries When an Executable Jar Runs -Most nested libraries in an executable jar do not need to be unpacked in order to run. -However, certain libraries can have problems. -For example, JRuby includes its own nested jar support, which assumes that the `jruby-complete.jar` is always directly available as a file in its own right. - -To deal with any problematic libraries, you can flag that specific nested jars should be automatically unpacked when the executable jar first runs. -Such nested jars are written beneath the temporary directory identified by the `java.io.tmpdir` system property. - -WARNING: Care should be taken to ensure that your operating system is configured so that it will not delete the jars that have been unpacked to the temporary directory while the application is still running. - -For example, to indicate that JRuby should be flagged for unpacking by using the Maven Plugin, you would add the following configuration: - -[source,xml,indent=0,subs="verbatim"] ----- - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.jruby - jruby-complete - - - - - - ----- - - - -[[howto.build.create-a-nonexecutable-jar]] -=== Create a Non-executable JAR with Exclusions -Often, if you have an executable and a non-executable jar as two separate build products, the executable version has additional configuration files that are not needed in a library jar. -For example, the `application.yaml` configuration file might be excluded from the non-executable JAR. - -In Maven, the executable jar must be the main artifact and you can add a classified jar for the library, as follows: - -[source,xml,indent=0,subs="verbatim"] ----- - - - - org.springframework.boot - spring-boot-maven-plugin - - - maven-jar-plugin - - - lib - package - - jar - - - lib - - application.yaml - - - - - - - ----- - - - -[[howto.build.remote-debug-maven]] -=== Remote Debug a Spring Boot Application Started with Maven -To attach a remote debugger to a Spring Boot application that was started with Maven, you can use the `jvmArguments` property of the {spring-boot-maven-plugin-docs}[maven plugin]. - -See {spring-boot-maven-plugin-docs}#run-example-debug[this example] for more details. - - - -[[howto.build.build-an-executable-archive-with-ant-without-using-spring-boot-antlib]] -=== Build an Executable Archive From Ant without Using spring-boot-antlib -To build with Ant, you need to grab dependencies, compile, and then create a jar or war archive. -To make it executable, you can either use the `spring-boot-antlib` module or you can follow these instructions: - -. If you are building a jar, package the application's classes and resources in a nested `BOOT-INF/classes` directory. - If you are building a war, package the application's classes in a nested `WEB-INF/classes` directory as usual. -. Add the runtime dependencies in a nested `BOOT-INF/lib` directory for a jar or `WEB-INF/lib` for a war. - Remember *not* to compress the entries in the archive. -. Add the `provided` (embedded container) dependencies in a nested `BOOT-INF/lib` directory for a jar or `WEB-INF/lib-provided` for a war. - Remember *not* to compress the entries in the archive. -. Add the `spring-boot-loader` classes at the root of the archive (so that the `Main-Class` is available). -. Use the appropriate launcher (such as `JarLauncher` for a jar file) as a `Main-Class` attribute in the manifest and specify the other properties it needs as manifest entries -- principally, by setting a `Start-Class` property. - -The following example shows how to build an executable archive with Ant: - -[source,xml,indent=0,subs="verbatim"] ----- - - - - - - - - - - - - - - - - - - - - - ----- diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/data-access.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/data-access.adoc deleted file mode 100644 index 50d30a2c2d96..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/data-access.adoc +++ /dev/null @@ -1,366 +0,0 @@ -[[howto.data-access]] -== Data Access -Spring Boot includes a number of starters for working with data sources. -This section answers questions related to doing so. - - - -[[howto.data-access.configure-custom-datasource]] -=== Configure a Custom DataSource -To configure your own `DataSource`, define a `@Bean` of that type in your configuration. -Spring Boot reuses your `DataSource` anywhere one is required, including database initialization. -If you need to externalize some settings, you can bind your `DataSource` to the environment (see "`<>`"). - -The following example shows how to define a data source in a bean: - -include::code:custom/MyDataSourceConfiguration[] - -The following example shows how to define a data source by setting properties: - -[source,yaml,indent=0,subs="verbatim",configblocks] ----- - app: - datasource: - url: "jdbc:h2:mem:mydb" - username: "sa" - pool-size: 30 ----- - -Assuming that `SomeDataSource` has regular JavaBean properties for the URL, the username, and the pool size, these settings are bound automatically before the `DataSource` is made available to other components. - -Spring Boot also provides a utility builder class, called `DataSourceBuilder`, that can be used to create one of the standard data sources (if it is on the classpath). -The builder can detect the one to use based on what is available on the classpath. -It also auto-detects the driver based on the JDBC URL. - -The following example shows how to create a data source by using a `DataSourceBuilder`: - -include::code:builder/MyDataSourceConfiguration[] - -To run an app with that `DataSource`, all you need is the connection information. -Pool-specific settings can also be provided. -Check the implementation that is going to be used at runtime for more details. - -The following example shows how to define a JDBC data source by setting properties: - -[source,yaml,indent=0,subs="verbatim",configblocks] ----- - app: - datasource: - url: "jdbc:mysql://localhost/test" - username: "dbuser" - password: "dbpass" - pool-size: 30 ----- - -However, there is a catch. -Because the actual type of the connection pool is not exposed, no keys are generated in the metadata for your custom `DataSource` and no completion is available in your IDE (because the `DataSource` interface exposes no properties). -Also, if you happen to have Hikari on the classpath, this basic setup does not work, because Hikari has no `url` property (but does have a `jdbcUrl` property). -In that case, you must rewrite your configuration as follows: - -[source,yaml,indent=0,subs="verbatim",configblocks] ----- - app: - datasource: - jdbc-url: "jdbc:mysql://localhost/test" - username: "dbuser" - password: "dbpass" - pool-size: 30 ----- - -You can fix that by forcing the connection pool to use and return a dedicated implementation rather than `DataSource`. -You cannot change the implementation at runtime, but the list of options will be explicit. - -The following example shows how create a `HikariDataSource` with `DataSourceBuilder`: - -include::code:simple/MyDataSourceConfiguration[] - -You can even go further by leveraging what `DataSourceProperties` does for you -- that is, by providing a default embedded database with a sensible username and password if no URL is provided. -You can easily initialize a `DataSourceBuilder` from the state of any `DataSourceProperties` object, so you could also inject the DataSource that Spring Boot creates automatically. -However, that would split your configuration into two namespaces: `url`, `username`, `password`, `type`, and `driver` on `spring.datasource` and the rest on your custom namespace (`app.datasource`). -To avoid that, you can redefine a custom `DataSourceProperties` on your custom namespace, as shown in the following example: - -include::code:configurable/MyDataSourceConfiguration[] - -This setup puts you _in sync_ with what Spring Boot does for you by default, except that a dedicated connection pool is chosen (in code) and its settings are exposed in the `app.datasource.configuration` sub namespace. -Because `DataSourceProperties` is taking care of the `url`/`jdbcUrl` translation for you, you can configure it as follows: - -[source,yaml,indent=0,subs="verbatim",configblocks] ----- - app: - datasource: - url: "jdbc:mysql://localhost/test" - username: "dbuser" - password: "dbpass" - configuration: - maximum-pool-size: 30 ----- - -TIP: Spring Boot will expose Hikari-specific settings to `spring.datasource.hikari`. -This example uses a more generic `configuration` sub namespace as the example does not support multiple datasource implementations. - -NOTE: Because your custom configuration chooses to go with Hikari, `app.datasource.type` has no effect. -In practice, the builder is initialized with whatever value you might set there and then overridden by the call to `.type()`. - -See "`<>`" in the "`Spring Boot features`" section and the {spring-boot-autoconfigure-module-code}/jdbc/DataSourceAutoConfiguration.java[`DataSourceAutoConfiguration`] class for more details. - - - -[[howto.data-access.configure-two-datasources]] -=== Configure Two DataSources -If you need to configure multiple data sources, you can apply the same tricks that are described in the previous section. -You must, however, mark one of the `DataSource` instances as `@Primary`, because various auto-configurations down the road expect to be able to get one by type. - -If you create your own `DataSource`, the auto-configuration backs off. -In the following example, we provide the _exact_ same feature set as the auto-configuration provides on the primary data source: - -include::code:MyDataSourcesConfiguration[] - -TIP: `firstDataSourceProperties` has to be flagged as `@Primary` so that the database initializer feature uses your copy (if you use the initializer). - -Both data sources are also bound for advanced customizations. -For instance, you could configure them as follows: - -[source,yaml,indent=0,subs="verbatim",configblocks] ----- - app: - datasource: - first: - url: "jdbc:mysql://localhost/first" - username: "dbuser" - password: "dbpass" - configuration: - maximum-pool-size: 30 - - second: - url: "jdbc:mysql://localhost/second" - username: "dbuser" - password: "dbpass" - max-total: 30 ----- - -You can apply the same concept to the secondary `DataSource` as well, as shown in the following example: - -include::code:MyCompleteDataSourcesConfiguration[] - -The preceding example configures two data sources on custom namespaces with the same logic as Spring Boot would use in auto-configuration. -Note that each `configuration` sub namespace provides advanced settings based on the chosen implementation. - - - -[[howto.data-access.spring-data-repositories]] -=== Use Spring Data Repositories -Spring Data can create implementations of `@Repository` interfaces of various flavors. -Spring Boot handles all of that for you, as long as those `@Repositories` are included in one of the <>, typically the package (or a sub-package) of your main application class that is annotated with `@SpringBootApplication` or `@EnableAutoConfiguration`. - -For many applications, all you need is to put the right Spring Data dependencies on your classpath. -There is a `spring-boot-starter-data-jpa` for JPA, `spring-boot-starter-data-mongodb` for Mongodb, and various other starters for supported technologies. -To get started, create some repository interfaces to handle your `@Entity` objects. - -Spring Boot determines the location of your `@Repository` definitions by scanning the <>. -For more control, use the `@Enable…Repositories` annotations from Spring Data. - -For more about Spring Data, see the {spring-data}[Spring Data project page]. - - - -[[howto.data-access.separate-entity-definitions-from-spring-configuration]] -=== Separate @Entity Definitions from Spring Configuration -Spring Boot determines the location of your `@Entity` definitions by scanning the <>. -For more control, use the `@EntityScan` annotation, as shown in the following example: - -include::code:MyApplication[] - - - -[[howto.data-access.jpa-properties]] -=== Configure JPA Properties -Spring Data JPA already provides some vendor-independent configuration options (such as those for SQL logging), and Spring Boot exposes those options and a few more for Hibernate as external configuration properties. -Some of them are automatically detected according to the context so you should not have to set them. - -The `spring.jpa.hibernate.ddl-auto` is a special case, because, depending on runtime conditions, it has different defaults. -If an embedded database is used and no schema manager (such as Liquibase or Flyway) is handling the `DataSource`, it defaults to `create-drop`. -In all other cases, it defaults to `none`. - -The dialect to use is detected by the JPA provider. -If you prefer to set the dialect yourself, set the configprop:spring.jpa.database-platform[] property. - -The most common options to set are shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - jpa: - hibernate: - naming: - physical-strategy: "com.example.MyPhysicalNamingStrategy" - show-sql: true ----- - -In addition, all properties in `+spring.jpa.properties.*+` are passed through as normal JPA properties (with the prefix stripped) when the local `EntityManagerFactory` is created. - -[WARNING] -==== -You need to ensure that names defined under `+spring.jpa.properties.*+` exactly match those expected by your JPA provider. -Spring Boot will not attempt any kind of relaxed binding for these entries. - -For example, if you want to configure Hibernate's batch size you must use `+spring.jpa.properties.hibernate.jdbc.batch_size+`. -If you use other forms, such as `batchSize` or `batch-size`, Hibernate will not apply the setting. -==== - -TIP: If you need to apply advanced customization to Hibernate properties, consider registering a `HibernatePropertiesCustomizer` bean that will be invoked prior to creating the `EntityManagerFactory`. -This takes precedence to anything that is applied by the auto-configuration. - - - -[[howto.data-access.configure-hibernate-naming-strategy]] -=== Configure Hibernate Naming Strategy -Hibernate uses {hibernate-docs}#naming[two different naming strategies] to map names from the object model to the corresponding database names. -The fully qualified class name of the physical and the implicit strategy implementations can be configured by setting the `spring.jpa.hibernate.naming.physical-strategy` and `spring.jpa.hibernate.naming.implicit-strategy` properties, respectively. -Alternatively, if `ImplicitNamingStrategy` or `PhysicalNamingStrategy` beans are available in the application context, Hibernate will be automatically configured to use them. - -By default, Spring Boot configures the physical naming strategy with `CamelCaseToUnderscoresNamingStrategy`. -Using this strategy, all dots are replaced by underscores and camel casing is replaced by underscores as well. -Additionally, by default, all table names are generated in lower case. -For example, a `TelephoneNumber` entity is mapped to the `telephone_number` table. -If your schema requires mixed-case identifiers, define a custom `CamelCaseToUnderscoresNamingStrategy` bean, as shown in the following example: - -include::code:spring/MyHibernateConfiguration[] - -If you prefer to use Hibernate's default instead, set the following property: - -[indent=0,properties,subs="verbatim"] ----- - spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl ----- - -Alternatively, you can configure the following bean: - -include::code:standard/MyHibernateConfiguration[] - -See {spring-boot-autoconfigure-module-code}/orm/jpa/HibernateJpaAutoConfiguration.java[`HibernateJpaAutoConfiguration`] and {spring-boot-autoconfigure-module-code}/orm/jpa/JpaBaseConfiguration.java[`JpaBaseConfiguration`] for more details. - - - -[[howto.data-access.configure-hibernate-second-level-caching]] -=== Configure Hibernate Second-Level Caching -Hibernate {hibernate-docs}#caching[second-level cache] can be configured for a range of cache providers. -Rather than configuring Hibernate to lookup the cache provider again, it is better to provide the one that is available in the context whenever possible. - -To do this with JCache, first make sure that `org.hibernate.orm:hibernate-jcache` is available on the classpath. -Then, add a `HibernatePropertiesCustomizer` bean as shown in the following example: - -include::code:MyHibernateSecondLevelCacheConfiguration[] - -This customizer will configure Hibernate to use the same `CacheManager` as the one that the application uses. -It is also possible to use separate `CacheManager` instances. -For details, see {hibernate-docs}#caching-provider-jcache[the Hibernate user guide]. - - - -[[howto.data-access.dependency-injection-in-hibernate-components]] -=== Use Dependency Injection in Hibernate Components -By default, Spring Boot registers a `BeanContainer` implementation that uses the `BeanFactory` so that converters and entity listeners can use regular dependency injection. - -You can disable or tune this behavior by registering a `HibernatePropertiesCustomizer` that removes or changes the `hibernate.resource.beans.container` property. - - - -[[howto.data-access.use-custom-entity-manager]] -=== Use a Custom EntityManagerFactory -To take full control of the configuration of the `EntityManagerFactory`, you need to add a `@Bean` named '`entityManagerFactory`'. -Spring Boot auto-configuration switches off its entity manager in the presence of a bean of that type. - - - -[[howto.data-access.use-multiple-entity-managers]] -[[howto.data-access.use-multiple-entity-managers]] -=== Using Multiple EntityManagerFactories -If you need to use JPA against multiple data sources, you likely need one `EntityManagerFactory` per data source. -The `LocalContainerEntityManagerFactoryBean` from Spring ORM allows you to configure an `EntityManagerFactory` for your needs. -You can also reuse `JpaProperties` to bind settings for each `EntityManagerFactory`, as shown in the following example: - -include::code:MyEntityManagerFactoryConfiguration[] - -The example above creates an `EntityManagerFactory` using a `DataSource` bean named `firstDataSource`. -It scans entities located in the same package as `Order`. -It is possible to map additional JPA properties using the `app.first.jpa` namespace. - -NOTE: When you create a bean for `LocalContainerEntityManagerFactoryBean` yourself, any customization that was applied during the creation of the auto-configured `LocalContainerEntityManagerFactoryBean` is lost. -For example, in case of Hibernate, any properties under the `spring.jpa.hibernate` prefix will not be automatically applied to your `LocalContainerEntityManagerFactoryBean`. -If you were relying on these properties for configuring things like the naming strategy or the DDL mode, you will need to explicitly configure that when creating the `LocalContainerEntityManagerFactoryBean` bean. - -You should provide a similar configuration for any additional data sources for which you need JPA access. -To complete the picture, you need to configure a `JpaTransactionManager` for each `EntityManagerFactory` as well. -Alternatively, you might be able to use a JTA transaction manager that spans both. - -If you use Spring Data, you need to configure `@EnableJpaRepositories` accordingly, as shown in the following examples: - -include::code:OrderConfiguration[] - -include::code:CustomerConfiguration[] - - - -[[howto.data-access.use-traditional-persistence-xml]] -=== Use a Traditional persistence.xml File -Spring Boot will not search for or use a `META-INF/persistence.xml` by default. -If you prefer to use a traditional `persistence.xml`, you need to define your own `@Bean` of type `LocalEntityManagerFactoryBean` (with an ID of '`entityManagerFactory`') and set the persistence unit name there. - -See {spring-boot-autoconfigure-module-code}/orm/jpa/JpaBaseConfiguration.java[`JpaBaseConfiguration`] for the default settings. - - - -[[howto.data-access.use-spring-data-jpa-and-mongo-repositories]] -=== Use Spring Data JPA and Mongo Repositories -Spring Data JPA and Spring Data Mongo can both automatically create `Repository` implementations for you. -If they are both present on the classpath, you might have to do some extra configuration to tell Spring Boot which repositories to create. -The most explicit way to do that is to use the standard Spring Data `+@EnableJpaRepositories+` and `+@EnableMongoRepositories+` annotations and provide the location of your `Repository` interfaces. - -There are also flags (`+spring.data.*.repositories.enabled+` and `+spring.data.*.repositories.type+`) that you can use to switch the auto-configured repositories on and off in external configuration. -Doing so is useful, for instance, in case you want to switch off the Mongo repositories and still use the auto-configured `MongoTemplate`. - -The same obstacle and the same features exist for other auto-configured Spring Data repository types (Elasticsearch, Redis, and others). -To work with them, change the names of the annotations and flags accordingly. - - - -[[howto.data-access.customize-spring-data-web-support]] -=== Customize Spring Data's Web Support -Spring Data provides web support that simplifies the use of Spring Data repositories in a web application. -Spring Boot provides properties in the `spring.data.web` namespace for customizing its configuration. -Note that if you are using Spring Data REST, you must use the properties in the `spring.data.rest` namespace instead. - - - -[[howto.data-access.exposing-spring-data-repositories-as-rest]] -=== Expose Spring Data Repositories as REST Endpoint -Spring Data REST can expose the `Repository` implementations as REST endpoints for you, -provided Spring MVC has been enabled for the application. - -Spring Boot exposes a set of useful properties (from the `spring.data.rest` namespace) that customize the {spring-data-rest-api}/core/config/RepositoryRestConfiguration.html[`RepositoryRestConfiguration`]. -If you need to provide additional customization, you should use a {spring-data-rest-api}/webmvc/config/RepositoryRestConfigurer.html[`RepositoryRestConfigurer`] bean. - -NOTE: If you do not specify any order on your custom `RepositoryRestConfigurer`, it runs after the one Spring Boot uses internally. -If you need to specify an order, make sure it is higher than 0. - - - -[[howto.data-access.configure-a-component-that-is-used-by-jpa]] -=== Configure a Component that is Used by JPA -If you want to configure a component that JPA uses, then you need to ensure that the component is initialized before JPA. -When the component is auto-configured, Spring Boot takes care of this for you. -For example, when Flyway is auto-configured, Hibernate is configured to depend upon Flyway so that Flyway has a chance to initialize the database before Hibernate tries to use it. - -If you are configuring a component yourself, you can use an `EntityManagerFactoryDependsOnPostProcessor` subclass as a convenient way of setting up the necessary dependencies. -For example, if you use Hibernate Search with Elasticsearch as its index manager, any `EntityManagerFactory` beans must be configured to depend on the `elasticsearchClient` bean, as shown in the following example: - -include::code:ElasticsearchEntityManagerFactoryDependsOnPostProcessor[] - - - -[[howto.data-access.configure-jooq-with-multiple-datasources]] -=== Configure jOOQ with Two DataSources -If you need to use jOOQ with multiple data sources, you should create your own `DSLContext` for each one. -See {spring-boot-autoconfigure-module-code}/jooq/JooqAutoConfiguration.java[JooqAutoConfiguration] for more details. - -TIP: In particular, `JooqExceptionTranslator` and `SpringTransactionProvider` can be reused to provide similar features to what the auto-configuration does with a single `DataSource`. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/docker-compose.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/docker-compose.adoc deleted file mode 100644 index 9b59fadc5635..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/docker-compose.adoc +++ /dev/null @@ -1,38 +0,0 @@ -[[howto.docker-compose]] -== Docker Compose - -This section includes topics relating to the Docker Compose support in Spring Boot. - -[[howto.docker-compose.jdbc-url]] -=== Customizing the JDBC URL - -When using `JdbcConnectionDetails` with Docker Compose, the parameters of the JDBC URL -can be customized by applying the `org.springframework.boot.jdbc.parameters` label to the -service. For example: - -[source,yaml,indent=0] ----- -services: - postgres: - image: 'postgres:15.3' - environment: - - 'POSTGRES_USER=myuser' - - 'POSTGRES_PASSWORD=secret' - - 'POSTGRES_DB=mydb' - ports: - - '5432:5432' - labels: - org.springframework.boot.jdbc.parameters: 'ssl=true&sslmode=require' ----- - -With this Docker Compose file in place, the JDBC URL used is `jdbc:postgresql://127.0.0.1:5432/mydb?ssl=true&sslmode=require`. - - - -[[howto.docker-compose.sharing-services]] -=== Sharing services between multiple applications - -If you want to share services between multiple applications, create the `compose.yaml` file in one of the applications and then use the configuration property configprop:spring.docker.compose.file[] in the other applications to reference the `compose.yaml` file. -You should also set configprop:spring.docker.compose.lifecycle-management[] to `start-only`, as it defaults to `start-and-stop` and stopping one application would shut down the shared services for the other still running applications, too. -Setting it to `start-only` won't stop the shared services on application stop, but a caveat is that if you shut down all applications, the services stay running. -You can stop the services manually by running `docker compose stop` on the commandline in the directory which contains the `compose.yaml` file. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/hotswapping.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/hotswapping.adoc deleted file mode 100644 index 9a6ec3f495f7..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/hotswapping.adoc +++ /dev/null @@ -1,67 +0,0 @@ -[[howto.hotswapping]] -== Hot Swapping -Spring Boot supports hot swapping. -This section answers questions about how it works. - - - -[[howto.hotswapping.reload-static-content]] -=== Reload Static Content -There are several options for hot reloading. -The recommended approach is to use <>, as it provides additional development-time features, such as support for fast application restarts and LiveReload as well as sensible development-time configuration (such as template caching). -Devtools works by monitoring the classpath for changes. -This means that static resource changes must be "built" for the change to take effect. -By default, this happens automatically in Eclipse when you save your changes. -In IntelliJ IDEA, the Make Project command triggers the necessary build. -Due to the <>, changes to static resources do not trigger a restart of your application. -They do, however, trigger a live reload. - -Alternatively, running in an IDE (especially with debugging on) is a good way to do development (all modern IDEs allow reloading of static resources and usually also allow hot-swapping of Java class changes). - -Finally, the <> can be configured (see the `addResources` property) to support running from the command line with reloading of static files directly from source. -You can use that with an external css/js compiler process if you are writing that code with higher-level tools. - - - -[[howto.hotswapping.reload-templates]] -=== Reload Templates without Restarting the Container -Most of the templating technologies supported by Spring Boot include a configuration option to disable caching (described later in this document). -If you use the `spring-boot-devtools` module, these properties are <> for you at development time. - - - -[[howto.hotswapping.reload-templates.thymeleaf]] -==== Thymeleaf Templates -If you use Thymeleaf, set `spring.thymeleaf.cache` to `false`. -See {spring-boot-autoconfigure-module-code}/thymeleaf/ThymeleafAutoConfiguration.java[`ThymeleafAutoConfiguration`] for other Thymeleaf customization options. - - - -[[howto.hotswapping.reload-templates.freemarker]] -==== FreeMarker Templates -If you use FreeMarker, set `spring.freemarker.cache` to `false`. -See {spring-boot-autoconfigure-module-code}/freemarker/FreeMarkerAutoConfiguration.java[`FreeMarkerAutoConfiguration`] for other FreeMarker customization options. - - - -[[howto.hotswapping.reload-templates.groovy]] -==== Groovy Templates -If you use Groovy templates, set `spring.groovy.template.cache` to `false`. -See {spring-boot-autoconfigure-module-code}/groovy/template/GroovyTemplateAutoConfiguration.java[`GroovyTemplateAutoConfiguration`] for other Groovy customization options. - - - -[[howto.hotswapping.fast-application-restarts]] -=== Fast Application Restarts -The `spring-boot-devtools` module includes support for automatic application restarts. -While not as fast as technologies such as https://www.jrebel.com/products/jrebel[JRebel] it is usually significantly faster than a "`cold start`". -You should probably give it a try before investigating some of the more complex reload options discussed later in this document. - -For more details, see the <> section. - - - -[[howto.hotswapping.reload-java-classes-without-restarting]] -=== Reload Java Classes without Restarting the Container -Many modern IDEs (Eclipse, IDEA, and others) support hot swapping of bytecode. -Consequently, if you make a change that does not affect class or method signatures, it should reload cleanly with no side effects. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/http-clients.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/http-clients.adoc deleted file mode 100644 index 7d809dc9d93b..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/http-clients.adoc +++ /dev/null @@ -1,26 +0,0 @@ -[[howto.http-clients]] -== HTTP Clients -Spring Boot offers a number of starters that work with HTTP clients. -This section answers questions related to using them. - - - -[[howto.http-clients.rest-template-proxy-configuration]] -=== Configure RestTemplate to Use a Proxy -As described in <>, you can use a `RestTemplateCustomizer` with `RestTemplateBuilder` to build a customized `RestTemplate`. -This is the recommended approach for creating a `RestTemplate` configured to use a proxy. - -The exact details of the proxy configuration depend on the underlying client request factory that is being used. - - - -[[howto.http-clients.webclient-reactor-netty-customization]] -=== Configure the TcpClient used by a Reactor Netty-based WebClient -When Reactor Netty is on the classpath a Reactor Netty-based `WebClient` is auto-configured. -To customize the client's handling of network connections, provide a `ClientHttpConnector` bean. -The following example configures a 60 second connect timeout and adds a `ReadTimeoutHandler`: - -include::code:MyReactorNettyClientConfiguration[] - -TIP: Note the use of `ReactorResourceFactory` for the connection provider and event loop resources. -This ensures efficient sharing of resources for the server receiving requests and the client making requests. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/logging.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/logging.adoc deleted file mode 100644 index e0a3ebf2d677..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/logging.adoc +++ /dev/null @@ -1,195 +0,0 @@ -[[howto.logging]] -== Logging -Spring Boot has no mandatory logging dependency, except for the Commons Logging API, which is typically provided by Spring Framework's `spring-jcl` module. -To use https://logback.qos.ch[Logback], you need to include it and `spring-jcl` on the classpath. -The recommended way to do that is through the starters, which all depend on `spring-boot-starter-logging`. -For a web application, you need only `spring-boot-starter-web`, since it depends transitively on the logging starter. -If you use Maven, the following dependency adds logging for you: - -[source,xml,indent=0,subs="verbatim"] ----- - - org.springframework.boot - spring-boot-starter-web - ----- - -Spring Boot has a `LoggingSystem` abstraction that attempts to configure logging based on the content of the classpath. -If Logback is available, it is the first choice. - -If the only change you need to make to logging is to set the levels of various loggers, you can do so in `application.properties` by using the "logging.level" prefix, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - logging: - level: - org.springframework.web: "debug" - org.hibernate: "error" ----- - -You can also set the location of a file to which to write the log (in addition to the console) by using `logging.file.name`. - -To configure the more fine-grained settings of a logging system, you need to use the native configuration format supported by the `LoggingSystem` in question. -By default, Spring Boot picks up the native configuration from its default location for the system (such as `classpath:logback.xml` for Logback), but you can set the location of the config file by using the configprop:logging.config[] property. - - - -[[howto.logging.logback]] -=== Configure Logback for Logging -If you need to apply customizations to logback beyond those that can be achieved with `application.properties`, you will need to add a standard logback configuration file. -You can add a `logback.xml` file to the root of your classpath for logback to find. -You can also use `logback-spring.xml` if you want to use the <>. - -TIP: The Logback documentation has a https://logback.qos.ch/manual/configuration.html[dedicated section that covers configuration] in some detail. - -Spring Boot provides a number of logback configurations that can be `included` in your own configuration. -These includes are designed to allow certain common Spring Boot conventions to be re-applied. - -The following files are provided under `org/springframework/boot/logging/logback/`: - -* `defaults.xml` - Provides conversion rules, pattern properties and common logger configurations. -* `console-appender.xml` - Adds a `ConsoleAppender` using the `CONSOLE_LOG_PATTERN`. -* `file-appender.xml` - Adds a `RollingFileAppender` using the `FILE_LOG_PATTERN` and `ROLLING_FILE_NAME_PATTERN` with appropriate settings. - -In addition, a legacy `base.xml` file is provided for compatibility with earlier versions of Spring Boot. - -A typical custom `logback.xml` file would look something like this: - -[source,xml,indent=0,subs="verbatim"] ----- - - - - - - - - - ----- - -Your logback configuration file can also make use of System properties that the `LoggingSystem` takes care of creating for you: - -* `$\{PID}`: The current process ID. -* `$\{LOG_FILE}`: Whether `logging.file.name` was set in Boot's external configuration. -* `$\{LOG_PATH}`: Whether `logging.file.path` (representing a directory for log files to live in) was set in Boot's external configuration. -* `$\{LOG_EXCEPTION_CONVERSION_WORD}`: Whether `logging.exception-conversion-word` was set in Boot's external configuration. -* `$\{ROLLING_FILE_NAME_PATTERN}`: Whether `logging.pattern.rolling-file-name` was set in Boot's external configuration. - -Spring Boot also provides some nice ANSI color terminal output on a console (but not in a log file) by using a custom Logback converter. -See the `CONSOLE_LOG_PATTERN` in the `defaults.xml` configuration for an example. - -If Groovy is on the classpath, you should be able to configure Logback with `logback.groovy` as well. -If present, this setting is given preference. - -NOTE: Spring extensions are not supported with Groovy configuration. -Any `logback-spring.groovy` files will not be detected. - - - -[[howto.logging.logback.file-only-output]] -==== Configure Logback for File-only Output -If you want to disable console logging and write output only to a file, you need a custom `logback-spring.xml` that imports `file-appender.xml` but not `console-appender.xml`, as shown in the following example: - -[source,xml,indent=0,subs="verbatim"] ----- - - - - - - - - - ----- - -You also need to add `logging.file.name` to your `application.properties` or `application.yaml`, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - logging: - file: - name: "myapplication.log" ----- - - - -[[howto.logging.log4j]] -=== Configure Log4j for Logging -Spring Boot supports https://logging.apache.org/log4j/2.x/[Log4j 2] for logging configuration if it is on the classpath. -If you use the starters for assembling dependencies, you have to exclude Logback and then include Log4j 2 instead. -If you do not use the starters, you need to provide (at least) `spring-jcl` in addition to Log4j 2. - -The recommended path is through the starters, even though it requires some jiggling. -The following example shows how to set up the starters in Maven: - -[source,xml,indent=0,subs="verbatim"] ----- - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter - - - org.springframework.boot - spring-boot-starter-logging - - - - - org.springframework.boot - spring-boot-starter-log4j2 - ----- - -Gradle provides a few different ways to set up the starters. -One way is to use a {gradle-docs}/resolution_rules.html#sec:module_replacement[module replacement]. -To do so, declare a dependency on the Log4j 2 starter and tell Gradle that any occurrences of the default logging starter should be replaced by the Log4j 2 starter, as shown in the following example: - -[source,gradle,indent=0,subs="verbatim"] ----- - dependencies { - implementation "org.springframework.boot:spring-boot-starter-log4j2" - modules { - module("org.springframework.boot:spring-boot-starter-logging") { - replacedBy("org.springframework.boot:spring-boot-starter-log4j2", "Use Log4j2 instead of Logback") - } - } - } ----- - -NOTE: The Log4j starters gather together the dependencies for common logging requirements (such as having Tomcat use `java.util.logging` but configuring the output using Log4j 2). - -NOTE: To ensure that debug logging performed using `java.util.logging` is routed into Log4j 2, configure its https://logging.apache.org/log4j/2.x/log4j-jul.html[JDK logging adapter] by setting the `java.util.logging.manager` system property to `org.apache.logging.log4j.jul.LogManager`. - - - -[[howto.logging.log4j.yaml-or-json-config]] -==== Use YAML or JSON to Configure Log4j 2 -In addition to its default XML configuration format, Log4j 2 also supports YAML and JSON configuration files. -To configure Log4j 2 to use an alternative configuration file format, add the appropriate dependencies to the classpath and name your configuration files to match your chosen file format, as shown in the following example: - -[cols="10,75a,15a"] -|=== -| Format | Dependencies | File names - -|YAML -| `com.fasterxml.jackson.core:jackson-databind` + `com.fasterxml.jackson.dataformat:jackson-dataformat-yaml` -| `log4j2.yaml` + `log4j2.yml` - -|JSON -| `com.fasterxml.jackson.core:jackson-databind` -| `log4j2.json` + `log4j2.jsn` -|=== - - - -[[howto.logging.log4j.composite-configuration]] -==== Use Composite Configuration to Configure Log4j 2 -Log4j 2 has support for combining multiple configuration files into a single composite configuration. -To use this support in Spring Boot, configure configprop:logging.log4j2.config.override[] with the locations of one or more secondary configuration files. -The secondary configuration files will be merged with the primary configuration, whether the primary's source is Spring Boot's defaults, a standard location such as `log4j.xml`, or the location configured by the configprop:logging.config[] property. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/messaging.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/messaging.adoc deleted file mode 100644 index b0e8b9170745..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/messaging.adoc +++ /dev/null @@ -1,16 +0,0 @@ -[[howto.messaging]] -== Messaging -Spring Boot offers a number of starters to support messaging. -This section answers questions that arise from using messaging with Spring Boot. - - - -[[howto.messaging.disable-transacted-jms-session]] -=== Disable Transacted JMS Session -If your JMS broker does not support transacted sessions, you have to disable the support of transactions altogether. -If you create your own `JmsListenerContainerFactory`, there is nothing to do, since, by default it cannot be transacted. -If you want to use the `DefaultJmsListenerContainerFactoryConfigurer` to reuse Spring Boot's default, you can disable transacted sessions, as follows: - -include::code:MyJmsConfiguration[] - -The preceding example overrides the default factory, and it should be applied to any other factory that your application defines, if any. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/nosql.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/nosql.adoc deleted file mode 100644 index c7be21231a0b..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/nosql.adoc +++ /dev/null @@ -1,45 +0,0 @@ -[[howto.nosql]] -== NoSQL -Spring Boot offers a number of starters that support NoSQL technologies. -This section answers questions that arise from using NoSQL with Spring Boot. - - - -[[howto.nosql.jedis-instead-of-lettuce]] -=== Use Jedis Instead of Lettuce -By default, the Spring Boot starter (`spring-boot-starter-data-redis`) uses https://github.com/lettuce-io/lettuce-core/[Lettuce]. -You need to exclude that dependency and include the https://github.com/xetorthio/jedis/[Jedis] one instead. -Spring Boot manages both of these dependencies so you can switch to Jedis without specifying a version. - -The following example shows how to do so in Maven: - -[source,xml,indent=0,subs="verbatim"] ----- - - org.springframework.boot - spring-boot-starter-data-redis - - - io.lettuce - lettuce-core - - - - - redis.clients - jedis - ----- - -The following example shows how to do so in Gradle: - -[source,gradle,indent=0,subs="verbatim"] ----- - dependencies { - implementation('org.springframework.boot:spring-boot-starter-data-redis') { - exclude group: 'io.lettuce', module: 'lettuce-core' - } - implementation 'redis.clients:jedis' - // ... - } ----- diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/properties-and-configuration.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/properties-and-configuration.adoc deleted file mode 100644 index 208b3504a2f5..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/properties-and-configuration.adoc +++ /dev/null @@ -1,297 +0,0 @@ -[[howto.properties-and-configuration]] -== Properties and Configuration -This section includes topics about setting and reading properties and configuration settings and their interaction with Spring Boot applications. - - - -[[howto.properties-and-configuration.expand-properties]] -=== Automatically Expand Properties at Build Time -Rather than hardcoding some properties that are also specified in your project's build configuration, you can automatically expand them by instead using the existing build configuration. -This is possible in both Maven and Gradle. - - - -[[howto.properties-and-configuration.expand-properties.maven]] -==== Automatic Property Expansion Using Maven -You can automatically expand properties from the Maven project by using resource filtering. -If you use the `spring-boot-starter-parent`, you can then refer to your Maven '`project properties`' with `@..@` placeholders, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configblocks] ----- - app: - encoding: "@project.build.sourceEncoding@" - java: - version: "@java.version@" ----- - -NOTE: Only production configuration is filtered that way (in other words, no filtering is applied on `src/test/resources`). - -TIP: If you enable the `addResources` flag, the `spring-boot:run` goal can add `src/main/resources` directly to the classpath (for hot reloading purposes). -Doing so circumvents the resource filtering and this feature. -Instead, you can use the `exec:java` goal or customize the plugin's configuration. -See the {spring-boot-maven-plugin-docs}#getting-started[plugin usage page] for more details. - -If you do not use the starter parent, you need to include the following element inside the `` element of your `pom.xml`: - -[source,xml,indent=0,subs="verbatim"] ----- - - - src/main/resources - true - - ----- - -You also need to include the following element inside ``: - -[source,xml,indent=0,subs="verbatim"] ----- - - org.apache.maven.plugins - maven-resources-plugin - 2.7 - - - @ - - false - - ----- - -NOTE: The `useDefaultDelimiters` property is important if you use standard Spring placeholders (such as `$\{placeholder}`) in your configuration. -If that property is not set to `false`, these may be expanded by the build. - - - -[[howto.properties-and-configuration.expand-properties.gradle]] -==== Automatic Property Expansion Using Gradle -You can automatically expand properties from the Gradle project by configuring the Java plugin's `processResources` task to do so, as shown in the following example: - -[source,gradle,indent=0,subs="verbatim"] ----- - tasks.named('processResources') { - expand(project.properties) - } ----- - -You can then refer to your Gradle project's properties by using placeholders, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configblocks] ----- - app: - name: "${name}" - description: "${description}" ----- - -NOTE: Gradle's `expand` method uses Groovy's `SimpleTemplateEngine`, which transforms `${..}` tokens. -The `${..}` style conflicts with Spring's own property placeholder mechanism. -To use Spring property placeholders together with automatic expansion, escape the Spring property placeholders as follows: `\${..}`. - - - -[[howto.properties-and-configuration.externalize-configuration]] -=== Externalize the Configuration of SpringApplication -A `SpringApplication` has bean property setters, so you can use its Java API as you create the application to modify its behavior. -Alternatively, you can externalize the configuration by setting properties in `+spring.main.*+`. -For example, in `application.properties`, you might have the following settings: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - main: - web-application-type: "none" - banner-mode: "off" ----- - -Then the Spring Boot banner is not printed on startup, and the application is not starting an embedded web server. - -Properties defined in external configuration override and replace the values specified with the Java API, with the notable exception of the primary sources. -Primary sources are those provided to the `SpringApplication` constructor: - -include::code:application/MyApplication[] - -Or to `sources(...)` method of a `SpringApplicationBuilder`: - -include::code:builder/MyApplication[] - -Given the examples above, if we have the following configuration: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - main: - sources: "com.example.MyDatabaseConfig,com.example.MyJmsConfig" - banner-mode: "console" ----- - -The actual application will show the banner (as overridden by configuration) and uses three sources for the `ApplicationContext`. -The application sources are: - -. `MyApplication` (from the code) -. `MyDatabaseConfig` (from the external config) -. `MyJmsConfig`(from the external config) - - - -[[howto.properties-and-configuration.external-properties-location]] -=== Change the Location of External Properties of an Application -By default, properties from different sources are added to the Spring `Environment` in a defined order (see "`<>`" in the '`Spring Boot features`' section for the exact order). - -You can also provide the following System properties (or environment variables) to change the behavior: - -* configprop:spring.config.name[] (configprop:spring.config.name[format=envvar]): Defaults to `application` as the root of the file name. -* configprop:spring.config.location[] (configprop:spring.config.location[format=envvar]): The file to load (such as a classpath resource or a URL). - A separate `Environment` property source is set up for this document and it can be overridden by system properties, environment variables, or the command line. - -No matter what you set in the environment, Spring Boot always loads `application.properties` as described above. -By default, if YAML is used, then files with the '`.yaml`' and '`.yml`' extension are also added to the list. - -TIP: If you want detailed information about the files that are being loaded you can <> of `org.springframework.boot.context.config` to `trace`. - - - -[[howto.properties-and-configuration.short-command-line-arguments]] -=== Use '`Short`' Command Line Arguments -Some people like to use (for example) `--port=9000` instead of `--server.port=9000` to set configuration properties on the command line. -You can enable this behavior by using placeholders in `application.properties`, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - server: - port: "${port:8080}" ----- - -TIP: If you inherit from the `spring-boot-starter-parent` POM, the default filter token of the `maven-resources-plugins` has been changed from `+${*}+` to `@` (that is, `@maven.token@` instead of `${maven.token}`) to prevent conflicts with Spring-style placeholders. -If you have enabled Maven filtering for the `application.properties` directly, you may want to also change the default filter token to use https://maven.apache.org/plugins/maven-resources-plugin/resources-mojo.html#delimiters[other delimiters]. - -NOTE: In this specific case, the port binding works in a PaaS environment such as Heroku or Cloud Foundry. -In those two platforms, the `PORT` environment variable is set automatically and Spring can bind to capitalized synonyms for `Environment` properties. - - - -[[howto.properties-and-configuration.yaml]] -=== Use YAML for External Properties -YAML is a superset of JSON and, as such, is a convenient syntax for storing external properties in a hierarchical format, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim"] ----- - spring: - application: - name: "cruncher" - datasource: - driver-class-name: "com.mysql.jdbc.Driver" - url: "jdbc:mysql://localhost/test" - server: - port: 9000 ----- - -Create a file called `application.yaml` and put it in the root of your classpath. -Then add `snakeyaml` to your dependencies (Maven coordinates `org.yaml:snakeyaml`, already included if you use the `spring-boot-starter`). -A YAML file is parsed to a Java `Map` (like a JSON object), and Spring Boot flattens the map so that it is one level deep and has period-separated keys, as many people are used to with `Properties` files in Java. - -The preceding example YAML corresponds to the following `application.properties` file: - -[source,properties,indent=0,subs="verbatim",configprops] ----- - spring.application.name=cruncher - spring.datasource.driver-class-name=com.mysql.jdbc.Driver - spring.datasource.url=jdbc:mysql://localhost/test - server.port=9000 ----- - -See "`<>`" in the '`Spring Boot features`' section for more information about YAML. - - - -[[howto.properties-and-configuration.set-active-spring-profiles]] -=== Set the Active Spring Profiles -The Spring `Environment` has an API for this, but you would normally set a System property (configprop:spring.profiles.active[]) or an OS environment variable (configprop:spring.profiles.active[format=envvar]). -Also, you can launch your application with a `-D` argument (remember to put it before the main class or jar archive), as follows: - -[source,shell,indent=0,subs="verbatim"] ----- - $ java -jar -Dspring.profiles.active=production demo-0.0.1-SNAPSHOT.jar ----- - -In Spring Boot, you can also set the active profile in `application.properties`, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - profiles: - active: "production" ----- - -A value set this way is replaced by the System property or environment variable setting but not by the `SpringApplicationBuilder.profiles()` method. -Thus, the latter Java API can be used to augment the profiles without changing the defaults. - -See "`<>`" in the "`Spring Boot features`" section for more information. - - - -[[howto.properties-and-configuration.set-default-spring-profile-name]] -=== Set the Default Profile Name -The default profile is a profile that is enabled if no profile is active. -By default, the name of the default profile is `default`, but it could be changed using a System property (configprop:spring.profiles.default[]) or an OS environment variable (configprop:spring.profiles.default[format=envvar]). - -In Spring Boot, you can also set the default profile name in `application.properties`, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - profiles: - default: "dev" ----- - -See "`<>`" in the "`Spring Boot features`" section for more information. - - - -[[howto.properties-and-configuration.change-configuration-depending-on-the-environment]] -=== Change Configuration Depending on the Environment -Spring Boot supports multi-document YAML and Properties files (see <> for details) which can be activated conditionally based on the active profiles. - -If a document contains a `spring.config.activate.on-profile` key, then the profiles value (a comma-separated list of profiles or a profile expression) is fed into the Spring `Environment.acceptsProfiles()` method. -If the profile expression matches then that document is included in the final merge (otherwise, it is not), as shown in the following example: - -[source,yaml,indent=0,subs="verbatim,attributes",configprops,configblocks] ----- - server: - port: 9000 - --- - spring: - config: - activate: - on-profile: "development" - server: - port: 9001 - --- - spring: - config: - activate: - on-profile: "production" - server: - port: 0 ----- - -In the preceding example, the default port is 9000. -However, if the Spring profile called '`development`' is active, then the port is 9001. -If '`production`' is active, then the port is 0. - -NOTE: The documents are merged in the order in which they are encountered. -Later values override earlier values. - - - -[[howto.properties-and-configuration.discover-build-in-options-for-external-properties]] -=== Discover Built-in Options for External Properties -Spring Boot binds external properties from `application.properties` (or YAML files and other places) into an application at runtime. -There is not (and technically cannot be) an exhaustive list of all supported properties in a single location, because contributions can come from additional jar files on your classpath. - -A running application with the Actuator features has a `configprops` endpoint that shows all the bound and bindable properties available through `@ConfigurationProperties`. - -The appendix includes an <> example with a list of the most common properties supported by Spring Boot. -The definitive list comes from searching the source code for `@ConfigurationProperties` and `@Value` annotations as well as the occasional use of `Binder`. -For more about the exact ordering of loading properties, see "<>". diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/security.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/security.adoc deleted file mode 100644 index 9f7509e035f1..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/security.adoc +++ /dev/null @@ -1,45 +0,0 @@ -[[howto.security]] -== Security -This section addresses questions about security when working with Spring Boot, including questions that arise from using Spring Security with Spring Boot. - -For more about Spring Security, see the {spring-security}[Spring Security project page]. - - - -[[howto.security.switch-off-spring-boot-configuration]] -=== Switch off the Spring Boot Security Configuration -If you define a `@Configuration` with a `SecurityFilterChain` bean in your application, it switches off the default webapp security settings in Spring Boot. - - - -[[howto.security.change-user-details-service-and-add-user-accounts]] -=== Change the UserDetailsService and Add User Accounts -If you provide a `@Bean` of type `AuthenticationManager`, `AuthenticationProvider`, or `UserDetailsService`, the default `@Bean` for `InMemoryUserDetailsManager` is not created. -This means you have the full feature set of Spring Security available (such as {spring-security-docs}/servlet/authentication/index.html[various authentication options]). - -The easiest way to add user accounts is to provide your own `UserDetailsService` bean. - - - -[[howto.security.enable-https]] -=== Enable HTTPS When Running behind a Proxy Server -Ensuring that all your main endpoints are only available over HTTPS is an important chore for any application. -If you use Tomcat as a servlet container, then Spring Boot adds Tomcat's own `RemoteIpValve` automatically if it detects some environment settings, and you should be able to rely on the `HttpServletRequest` to report whether it is secure or not (even downstream of a proxy server that handles the real SSL termination). -The standard behavior is determined by the presence or absence of certain request headers (`x-forwarded-for` and `x-forwarded-proto`), whose names are conventional, so it should work with most front-end proxies. -You can switch on the valve by adding some entries to `application.properties`, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - server: - tomcat: - remoteip: - remote-ip-header: "x-forwarded-for" - protocol-header: "x-forwarded-proto" ----- - -(The presence of either of those properties switches on the valve. -Alternatively, you can add the `RemoteIpValve` by customizing the `TomcatServletWebServerFactory` using a `WebServerFactoryCustomizer` bean.) - -To configure Spring Security to require a secure channel for all (or some) requests, consider adding your own `SecurityFilterChain` bean that adds the following `HttpSecurity` configuration: - -include::code:MySecurityConfig[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/testing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/testing.adoc deleted file mode 100644 index 2c76b8689b8f..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/testing.adoc +++ /dev/null @@ -1,51 +0,0 @@ -[[howto.testing]] -== Testing -Spring Boot includes a number of testing utilities and support classes as well as a dedicated starter that provides common test dependencies. -This section answers common questions about testing. - - - -[[howto.testing.with-spring-security]] -=== Testing With Spring Security -Spring Security provides support for running tests as a specific user. -For example, the test in the snippet below will run with an authenticated user that has the `ADMIN` role. - -include::code:MySecurityTests[] - -Spring Security provides comprehensive integration with Spring MVC Test and this can also be used when testing controllers using the `@WebMvcTest` slice and `MockMvc`. - -For additional details on Spring Security's testing support, see Spring Security's {spring-security-docs}/servlet/test/index.html[reference documentation]. - - - - -[[howto.testing.slice-tests]] -=== Structure `@Configuration` classes for inclusion in slice tests -Slice tests work by restricting Spring Framework's component scanning to a limited set of components based on their type. -For any beans that are not created through component scanning, for example, beans that are created using the `@Bean` annotation, slice tests will not be able to include/exclude them from the application context. -Consider this example: - -[source,java,indent=0,subs="verbatim"] ----- -include::{docs-java}/howto/testing/slicetests/MyConfiguration.java[] ----- - -For a `@WebMvcTest` for an application with the above `@Configuration` class, you might expect to have the `SecurityFilterChain` bean in the application context so that you can test if your controller endpoints are secured properly. -However, `MyConfiguration` is not picked up by @WebMvcTest's component scanning filter because it doesn't match any of the types specified by the filter. -You can include the configuration explicitly by annotating the test class with `@Import(MyConfiguration.class)`. -This will load all the beans in `MyConfiguration` including the `BasicDataSource` bean which isn't required when testing the web tier. -Splitting the configuration class into two will enable importing just the security configuration. - -[source,java,indent=0,subs="verbatim"] ----- -include::{docs-java}/howto/testing/slicetests/MySecurityConfiguration.java[] ----- - -[source,java,indent=0,subs="verbatim"] ----- -include::{docs-java}/howto/testing/slicetests/MyDatasourceConfiguration.java[] ----- - -Having a single configuration class can be inefficient when beans of a certain domain need to be included in slice tests. -Instead, structuring the application's configuration as multiple granular classes with beans for a specific domain can enable importing them only for specific slice tests. - diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/webserver.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/webserver.adoc deleted file mode 100644 index 49a81caa449f..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/webserver.adoc +++ /dev/null @@ -1,512 +0,0 @@ -[[howto.webserver]] -== Embedded Web Servers -Each Spring Boot web application includes an embedded web server. -This feature leads to a number of how-to questions, including how to change the embedded server and how to configure the embedded server. -This section answers those questions. - - - -[[howto.webserver.use-another]] -=== Use Another Web Server -Many Spring Boot starters include default embedded containers. - -* For servlet stack applications, the `spring-boot-starter-web` includes Tomcat by including `spring-boot-starter-tomcat`, but you can use `spring-boot-starter-jetty` or `spring-boot-starter-undertow` instead. -* For reactive stack applications, the `spring-boot-starter-webflux` includes Reactor Netty by including `spring-boot-starter-reactor-netty`, but you can use `spring-boot-starter-tomcat`, `spring-boot-starter-jetty`, or `spring-boot-starter-undertow` instead. - -When switching to a different HTTP server, you need to swap the default dependencies for those that you need instead. -To help with this process, Spring Boot provides a separate starter for each of the supported HTTP servers. - -The following Maven example shows how to exclude Tomcat and include Jetty for Spring MVC: - -[source,xml,indent=0,subs="verbatim"] ----- - - 5.0.0 - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-tomcat - - - - - - org.springframework.boot - spring-boot-starter-jetty - ----- - -NOTE: The version of the Jakarta Servlet API has been overridden as, unlike Tomcat 10 and Undertow 2.3, Jetty 11 does not support Servlet 6.0. - -[WARNING] -==== -Downgrading the Servlet API to 5.0 breaks Spring Framework's Servlet-related mocks! - -As Jetty needs the Servlet API 5.0, this leaves you with two working arrangements: - -* Tests use the Servlet 5.0 API and avoid using Framework's Servlet mocks by only using a full-blown web environment -* Tests use the Servlet 6.0 API and avoid starting Jetty by only using a mock web environment - -If a mixture of web environments is required by your application's tests, your test setup may require some structural changes to strictly separate the two web environments. -==== - -The following Gradle example configures the necessary dependencies and a {gradle-docs}/resolution_rules.html#sec:module_replacement[module replacement] to use Undertow in place of Reactor Netty for Spring WebFlux: - -[source,gradle,indent=0,subs="verbatim"] ----- - dependencies { - implementation "org.springframework.boot:spring-boot-starter-undertow" - implementation "org.springframework.boot:spring-boot-starter-webflux" - modules { - module("org.springframework.boot:spring-boot-starter-reactor-netty") { - replacedBy("org.springframework.boot:spring-boot-starter-undertow", "Use Undertow instead of Reactor Netty") - } - } - } ----- - -NOTE: `spring-boot-starter-reactor-netty` is required to use the `WebClient` class, so you may need to keep a dependency on Netty even when you need to include a different HTTP server. - - - -[[howto.webserver.disable]] -=== Disabling the Web Server -If your classpath contains the necessary bits to start a web server, Spring Boot will automatically start it. -To disable this behavior configure the `WebApplicationType` in your `application.properties`, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - main: - web-application-type: "none" ----- - - - -[[howto.webserver.change-port]] -=== Change the HTTP Port -In a standalone application, the main HTTP port defaults to `8080` but can be set with configprop:server.port[] (for example, in `application.properties` or as a System property). -Thanks to relaxed binding of `Environment` values, you can also use configprop:server.port[format=envvar] (for example, as an OS environment variable). - -To switch off the HTTP endpoints completely but still create a `WebApplicationContext`, use `server.port=-1` (doing so is sometimes useful for testing). - -For more details, see "`<>`" in the '`Spring Boot Features`' section, or the {spring-boot-autoconfigure-module-code}/web/ServerProperties.java[`ServerProperties`] source code. - - - -[[howto.webserver.use-random-port]] -=== Use a Random Unassigned HTTP Port -To scan for a free port (using OS natives to prevent clashes) use `server.port=0`. - - - -[[howto.webserver.discover-port]] -=== Discover the HTTP Port at Runtime -You can access the port the server is running on from log output or from the `WebServerApplicationContext` through its `WebServer`. -The best way to get that and be sure it has been initialized is to add a `@Bean` of type `ApplicationListener` and pull the container out of the event when it is published. - -Tests that use `@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)` can also inject the actual port into a field by using the `@LocalServerPort` annotation, as shown in the following example: - -include::code:MyWebIntegrationTests[] - -[NOTE] -==== -`@LocalServerPort` is a meta-annotation for `@Value("${local.server.port}")`. -Do not try to inject the port in a regular application. -As we just saw, the value is set only after the container has been initialized. -Contrary to a test, application code callbacks are processed early (before the value is actually available). -==== - - - -[[howto.webserver.enable-response-compression]] -=== Enable HTTP Response Compression -HTTP response compression is supported by Jetty, Tomcat, Reactor Netty, and Undertow. -It can be enabled in `application.properties`, as follows: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - server: - compression: - enabled: true ----- - -By default, responses must be at least 2048 bytes in length for compression to be performed. -You can configure this behavior by setting the configprop:server.compression.min-response-size[] property. - -By default, responses are compressed only if their content type is one of the following: - -* `text/html` -* `text/xml` -* `text/plain` -* `text/css` -* `text/javascript` -* `application/javascript` -* `application/json` -* `application/xml` - -You can configure this behavior by setting the configprop:server.compression.mime-types[] property. - - - -[[howto.webserver.configure-ssl]] -=== Configure SSL -SSL can be configured declaratively by setting the various `+server.ssl.*+` properties, typically in `application.properties` or `application.yaml`. -The following example shows setting SSL properties using a Java KeyStore file: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - server: - port: 8443 - ssl: - key-store: "classpath:keystore.jks" - key-store-password: "secret" - key-password: "another-secret" ----- - -Using configuration such as the preceding example means the application no longer supports a plain HTTP connector at port 8080. -Spring Boot does not support the configuration of both an HTTP connector and an HTTPS connector through `application.properties`. -If you want to have both, you need to configure one of them programmatically. -We recommend using `application.properties` to configure HTTPS, as the HTTP connector is the easier of the two to configure programmatically. - - - -[[howto.webserver.configure-ssl.pem-files]] -==== Using PEM-encoded files -You can use PEM-encoded files instead of Java KeyStore files. -You should use PKCS#8 key files wherever possible. -PEM-encoded PKCS#8 key files start with a `-----BEGIN PRIVATE KEY-----` or `-----BEGIN ENCRYPTED PRIVATE KEY-----` header. - -If you have files in other formats, e.g., PKCS#1 (`-----BEGIN RSA PRIVATE KEY-----`) or SEC 1 (`-----BEGIN EC PRIVATE KEY-----`), you can convert them to PKCS#8 using OpenSSL: - -[source,shell,indent=0,subs="verbatim,attributes"] ----- -openssl pkcs8 -topk8 -nocrypt -in -out ----- - -The following example shows setting SSL properties using PEM-encoded certificate and private key files: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - server: - port: 8443 - ssl: - certificate: "classpath:my-cert.crt" - certificate-private-key: "classpath:my-cert.key" - trust-certificate: "classpath:ca-cert.crt" ----- - -Alternatively, the SSL trust material can be configured in an <> and applied to the web server as shown in this example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - server: - port: 8443 - ssl: - bundle: "example" ----- - -NOTE: The `server.ssl.bundle` property can not be combined with the discrete Java KeyStore or PEM property options under `server.ssl`. - -See {spring-boot-module-code}/web/server/Ssl.java[`Ssl`] for details of all of the supported properties. - - - -[[howto.webserver.configure-http2]] -=== Configure HTTP/2 -You can enable HTTP/2 support in your Spring Boot application with the configprop:server.http2.enabled[] configuration property. -Both `h2` (HTTP/2 over TLS) and `h2c` (HTTP/2 over TCP) are supported. -To use `h2`, SSL must also be enabled. -When SSL is not enabled, `h2c` will be used. -You may, for example, want to use `h2c` when your application is <> that is performing TLS termination. - - - -[[howto.webserver.configure-http2.tomcat]] -==== HTTP/2 With Tomcat -Spring Boot ships by default with Tomcat 10.1.x which supports `h2c` and `h2` out of the box. -Alternatively, you can use `libtcnative` for `h2` support if the library and its dependencies are installed on the host operating system. - -The library directory must be made available, if not already, to the JVM library path. -You can do so with a JVM argument such as `-Djava.library.path=/usr/local/opt/tomcat-native/lib`. -More on this in the {tomcat-docs}/apr.html[official Tomcat documentation]. - - - -[[howto.webserver.configure-http2.jetty]] -==== HTTP/2 With Jetty -For HTTP/2 support, Jetty requires the additional `org.eclipse.jetty.http2:http2-server` dependency. -To use `h2c` no other dependencies are required. -To use `h2`, you also need to choose one of the following dependencies, depending on your deployment: - -* `org.eclipse.jetty:jetty-alpn-java-server` to use the JDK built-in support -* `org.eclipse.jetty:jetty-alpn-conscrypt-server` and the https://www.conscrypt.org/[Conscrypt library] - - - -[[howto.webserver.configure-http2.netty]] -==== HTTP/2 With Reactor Netty -The `spring-boot-webflux-starter` is using by default Reactor Netty as a server. -Reactor Netty supports `h2c` and `h2` out of the box. -For optimal runtime performance, this server also supports `h2` with native libraries. -To enable that, your application needs to have an additional dependency. - -Spring Boot manages the version for the `io.netty:netty-tcnative-boringssl-static` "uber jar", containing native libraries for all platforms. -Developers can choose to import only the required dependencies using a classifier (see https://netty.io/wiki/forked-tomcat-native.html[the Netty official documentation]). - - - -[[howto.webserver.configure-http2.undertow]] -==== HTTP/2 With Undertow -Undertow supports `h2c` and `h2` out of the box. - - - -[[howto.webserver.configure]] -=== Configure the Web Server -Generally, you should first consider using one of the many available configuration keys and customize your web server by adding new entries in your `application.properties` or `application.yaml` file. -See "`<>`"). -The `server.{asterisk}` namespace is quite useful here, and it includes namespaces like `server.tomcat.{asterisk}`, `server.jetty.{asterisk}` and others, for server-specific features. -See the list of <>. - -The previous sections covered already many common use cases, such as compression, SSL or HTTP/2. -However, if a configuration key does not exist for your use case, you should then look at {spring-boot-module-api}/web/server/WebServerFactoryCustomizer.html[`WebServerFactoryCustomizer`]. -You can declare such a component and get access to the server factory relevant to your choice: you should select the variant for the chosen Server (Tomcat, Jetty, Reactor Netty, Undertow) and the chosen web stack (servlet or reactive). - -The example below is for Tomcat with the `spring-boot-starter-web` (servlet stack): - -include::code:MyTomcatWebServerCustomizer[] - -NOTE: Spring Boot uses that infrastructure internally to auto-configure the server. -Auto-configured `WebServerFactoryCustomizer` beans have an order of `0` and will be processed before any user-defined customizers, unless it has an explicit order that states otherwise. - -Once you have got access to a `WebServerFactory` using the customizer, you can use it to configure specific parts, like connectors, server resources, or the server itself - all using server-specific APIs. - -In addition Spring Boot provides: - -[[howto-configure-webserver-customizers]] -[cols="1,2,2", options="header"] -|=== -| Server | Servlet stack | Reactive stack - -| Tomcat -| `TomcatServletWebServerFactory` -| `TomcatReactiveWebServerFactory` - -| Jetty -| `JettyServletWebServerFactory` -| `JettyReactiveWebServerFactory` - -| Undertow -| `UndertowServletWebServerFactory` -| `UndertowReactiveWebServerFactory` - -| Reactor -| N/A -| `NettyReactiveWebServerFactory` -|=== - -As a last resort, you can also declare your own `WebServerFactory` bean, which will override the one provided by Spring Boot. -When you do so, auto-configured customizers are still applied on your custom factory, so use that option carefully. - - - -[[howto.webserver.add-servlet-filter-listener]] -=== Add a Servlet, Filter, or Listener to an Application -In a servlet stack application, that is with the `spring-boot-starter-web`, there are two ways to add `Servlet`, `Filter`, `ServletContextListener`, and the other listeners supported by the Servlet API to your application: - -* <> -* <> - - - -[[howto.webserver.add-servlet-filter-listener.spring-bean]] -==== Add a Servlet, Filter, or Listener by Using a Spring Bean -To add a `Servlet`, `Filter`, or servlet `*Listener` by using a Spring bean, you must provide a `@Bean` definition for it. -Doing so can be very useful when you want to inject configuration or dependencies. -However, you must be very careful that they do not cause eager initialization of too many other beans, because they have to be installed in the container very early in the application lifecycle. -(For example, it is not a good idea to have them depend on your `DataSource` or JPA configuration.) -You can work around such restrictions by initializing the beans lazily when first used instead of on initialization. - -In the case of filters and servlets, you can also add mappings and init parameters by adding a `FilterRegistrationBean` or a `ServletRegistrationBean` instead of or in addition to the underlying component. - -[NOTE] -==== -If no `dispatcherType` is specified on a filter registration, `REQUEST` is used. -This aligns with the servlet specification's default dispatcher type. -==== - -Like any other Spring bean, you can define the order of servlet filter beans; please make sure to check the "`<>`" section. - - - -[[howto.webserver.add-servlet-filter-listener.spring-bean.disable]] -===== Disable Registration of a Servlet or Filter -As <>, any `Servlet` or `Filter` beans are registered with the servlet container automatically. -To disable registration of a particular `Filter` or `Servlet` bean, create a registration bean for it and mark it as disabled, as shown in the following example: - -include::code:MyFilterConfiguration[] - - - -[[howto.webserver.add-servlet-filter-listener.using-scanning]] -==== Add Servlets, Filters, and Listeners by Using Classpath Scanning -`@WebServlet`, `@WebFilter`, and `@WebListener` annotated classes can be automatically registered with an embedded servlet container by annotating a `@Configuration` class with `@ServletComponentScan` and specifying the package(s) containing the components that you want to register. -By default, `@ServletComponentScan` scans from the package of the annotated class. - - - -[[howto.webserver.configure-access-logs]] -=== Configure Access Logging -Access logs can be configured for Tomcat, Undertow, and Jetty through their respective namespaces. - -For instance, the following settings log access on Tomcat with a {tomcat-docs}/config/valve.html#Access_Logging[custom pattern]. - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - server: - tomcat: - basedir: "my-tomcat" - accesslog: - enabled: true - pattern: "%t %a %r %s (%D microseconds)" ----- - -NOTE: The default location for logs is a `logs` directory relative to the Tomcat base directory. -By default, the `logs` directory is a temporary directory, so you may want to fix Tomcat's base directory or use an absolute path for the logs. -In the preceding example, the logs are available in `my-tomcat/logs` relative to the working directory of the application. - -Access logging for Undertow can be configured in a similar fashion, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - server: - undertow: - accesslog: - enabled: true - pattern: "%t %a %r %s (%D milliseconds)" - options: - server: - record-request-start-time: true ----- - -Note that, in addition to enabling access logging and configuring its pattern, recording request start times has also been enabled. -This is required when including the response time (`%D`) in the access log pattern. -Logs are stored in a `logs` directory relative to the working directory of the application. -You can customize this location by setting the configprop:server.undertow.accesslog.dir[] property. - -Finally, access logging for Jetty can also be configured as follows: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - server: - jetty: - accesslog: - enabled: true - filename: "/var/log/jetty-access.log" ----- - -By default, logs are redirected to `System.err`. -For more details, see the Jetty documentation. - - - -[[howto.webserver.use-behind-a-proxy-server]] -=== Running Behind a Front-end Proxy Server -If your application is running behind a proxy, a load-balancer or in the cloud, the request information (like the host, port, scheme...) might change along the way. -Your application may be running on `10.10.10.10:8080`, but HTTP clients should only see `example.org`. - -https://tools.ietf.org/html/rfc7239[RFC7239 "Forwarded Headers"] defines the `Forwarded` HTTP header; proxies can use this header to provide information about the original request. -You can configure your application to read those headers and automatically use that information when creating links and sending them to clients in HTTP 302 responses, JSON documents or HTML pages. -There are also non-standard headers, like `X-Forwarded-Host`, `X-Forwarded-Port`, `X-Forwarded-Proto`, `X-Forwarded-Ssl`, and `X-Forwarded-Prefix`. - -If the proxy adds the commonly used `X-Forwarded-For` and `X-Forwarded-Proto` headers, setting `server.forward-headers-strategy` to `NATIVE` is enough to support those. -With this option, the Web servers themselves natively support this feature; you can check their specific documentation to learn about specific behavior. - -If this is not enough, Spring Framework provides a {spring-framework-docs}/web/webmvc/filters.html#filters-forwarded-headers[ForwardedHeaderFilter] for the servlet stack and a {spring-framework-docs}/web/webflux/reactive-spring.html#webflux-forwarded-headers[ForwardedHeaderTransformer] for the reactive stack. -You can use them in your application by setting configprop:server.forward-headers-strategy[] to `FRAMEWORK`. - -TIP: If you are using Tomcat and terminating SSL at the proxy, configprop:server.tomcat.redirect-context-root[] should be set to `false`. -This allows the `X-Forwarded-Proto` header to be honored before any redirects are performed. - -NOTE: If your application runs in Cloud Foundry, Heroku or Kubernetes, the configprop:server.forward-headers-strategy[] property defaults to `NATIVE`. -In all other instances, it defaults to `NONE`. - - - -[[howto.webserver.use-behind-a-proxy-server.tomcat]] -==== Customize Tomcat's Proxy Configuration -If you use Tomcat, you can additionally configure the names of the headers used to carry "`forwarded`" information, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - server: - tomcat: - remoteip: - remote-ip-header: "x-your-remote-ip-header" - protocol-header: "x-your-protocol-header" ----- - -Tomcat is also configured with a regular expression that matches internal proxies that are to be trusted. -See the <> for its default value. -You can customize the valve's configuration by adding an entry to `application.properties`, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - server: - tomcat: - remoteip: - internal-proxies: "192\\.168\\.\\d{1,3}\\.\\d{1,3}" ----- - -NOTE: You can trust all proxies by setting the `internal-proxies` to empty (but do not do so in production). - -You can take complete control of the configuration of Tomcat's `RemoteIpValve` by switching the automatic one off (to do so, set `server.forward-headers-strategy=NONE`) and adding a new valve instance using a `WebServerFactoryCustomizer` bean. - - - -[[howto.webserver.enable-multiple-connectors-in-tomcat]] -=== Enable Multiple Connectors with Tomcat -You can add an `org.apache.catalina.connector.Connector` to the `TomcatServletWebServerFactory`, which can allow multiple connectors, including HTTP and HTTPS connectors, as shown in the following example: - -include::code:MyTomcatConfiguration[] - - - -[[howto.webserver.enable-tomcat-mbean-registry]] -=== Enable Tomcat's MBean Registry -Embedded Tomcat's MBean registry is disabled by default. -This minimizes Tomcat's memory footprint. -If you want to use Tomcat's MBeans, for example so that they can be used by Micrometer to expose metrics, you must use the configprop:server.tomcat.mbeanregistry.enabled[] property to do so, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- -server: - tomcat: - mbeanregistry: - enabled: true ----- - - - -[[howto.webserver.enable-multiple-listeners-in-undertow]] -=== Enable Multiple Listeners with Undertow -Add an `UndertowBuilderCustomizer` to the `UndertowServletWebServerFactory` and add a listener to the `Builder`, as shown in the following example: - -include::code:MyUndertowConfiguration[] - - - -[[howto.webserver.create-websocket-endpoints-using-serverendpoint]] -=== Create WebSocket Endpoints Using @ServerEndpoint -If you want to use `@ServerEndpoint` in a Spring Boot application that used an embedded container, you must declare a single `ServerEndpointExporter` `@Bean`, as shown in the following example: - -include::code:MyWebSocketConfiguration[] - -The bean shown in the preceding example registers any `@ServerEndpoint` annotated beans with the underlying WebSocket container. -When deployed to a standalone servlet container, this role is performed by a servlet container initializer, and the `ServerEndpointExporter` bean is not required. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/index-docinfo.xml b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/index-docinfo.xml deleted file mode 100644 index 39652531d31f..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/index-docinfo.xml +++ /dev/null @@ -1,13 +0,0 @@ -Spring Boot -{spring-boot-version} - - 2012-2024 - - - - Copies of this document may be made for your own use and for distribution to - others, provided that you do not charge any fee for such copies and further - provided that each copy contains this Copyright Notice, whether distributed in - print or electronically. - - diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/index.adoc deleted file mode 100644 index 3d6604e85b76..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/index.adoc +++ /dev/null @@ -1,39 +0,0 @@ -[[index]] -= Spring Boot Reference Documentation -include::authors.adoc[] -v{spring-boot-version} -include::attributes.adoc[] - -This document is also available as {spring-boot-docs}/htmlsingle/[a single HTML page] and as {spring-boot-docs}/pdf/spring-boot-reference.pdf[a PDF]. - -The reference documentation consists of the following sections: - -[horizontal] -<> :: Legal information. -<> :: Resources for getting help. -<> :: About the Documentation, First Steps, and more. -<> :: Introducing Spring Boot, System Requirements, Servlet Containers, Installing Spring Boot, and Developing Your First Spring Boot Application -<> :: Upgrading from 1.x, Upgrading to a new feature release, and Upgrading the Spring Boot CLI. -<> :: Build Systems, Structuring Your Code, Configuration, Spring Beans and Dependency Injection, DevTools, and more. -<> :: Profiles, Logging, Internationalization, Task Execution and Scheduling, Testing, and more. -<> :: Servlet Web, Reactive Web, Embedded Container Support, Graceful Shutdown, and more. -<> :: SQL and NOSQL data access. -<> :: Caching, Quartz Scheduler, REST clients, Sending email, Spring Web Services, and more. -<> :: JMS, AMQP, Apache Kafka, RSocket, WebSocket, and Spring Integration. -<> :: Efficient container images and Building container images with Dockerfiles and Cloud Native Buildpacks. -<> :: Monitoring, Metrics, Auditing, and more. -<> :: Deploying to the Cloud, and Installing as a Unix application. -<> :: Create a native executable from your application using GraalVM -<> :: Installing the CLI, Using the CLI, Configuring the CLI, and more. -<> :: Maven Plugin, Gradle Plugin, Antlib, and more. -<> :: Application Development, Configuration, Embedded Servers, Data Access, and many more. - -The reference documentation has the following appendices: - -[horizontal] -<> :: Common application properties that you can use to configure your application. -<> :: Metadata that you can use to describe configuration properties. -<> :: Auto-configuration classes provided by Spring Boot. -<> :: Test auto-configuration annotations that you can use to test slices of your application. -<> :: Spring Boot's executable jars, their launchers, and their format. -<> :: Details of the dependencies that are managed by Spring Boot. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/index.singleadoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/index.singleadoc deleted file mode 100644 index debab3c58faa..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/index.singleadoc +++ /dev/null @@ -1,66 +0,0 @@ -[[spring-boot-reference-documentation]] -= Spring Boot Reference Documentation -include::authors.adoc[] -v{spring-boot-version} -include::attributes.adoc[] - -ifdef::backend-spring-html[] -This document is also available as {spring-boot-docs}/html/[multiple HTML pages] and as {spring-boot-docs}/pdf/spring-boot-reference.pdf[a PDF]. -endif::[] -ifdef::backend-spring-pdf[] -This document is also available as {spring-boot-docs}/html/[multiple HTML pages] and as {spring-boot-docs}/htmlsingle/[a single HTML page]. -endif::[] - -include::legal.adoc[leveloffset=+1] - -include::getting-help.adoc[leveloffset=+1] - -include::documentation.adoc[leveloffset=+1] - -include::getting-started.adoc[leveloffset=+1] - -include::upgrading.adoc[leveloffset=+1] - -include::using.adoc[leveloffset=+1] - -include::features.adoc[leveloffset=+1] - -include::web.adoc[leveloffset=+1] - -include::data.adoc[leveloffset=+1] - -include::messaging.adoc[leveloffset=+1] - -include::io.adoc[leveloffset=+1] - -include::container-images.adoc[leveloffset=+1] - -include::actuator.adoc[leveloffset=+1] - -include::deployment.adoc[leveloffset=+1] - -include::native-image.adoc[leveloffset=+1] - -include::cli.adoc[leveloffset=+1] - -include::build-tool-plugins.adoc[leveloffset=+1] - -include::howto.adoc[leveloffset=+1] - - - -:sectnums!: -[[appendix]] -== Appendices - -include::application-properties.adoc[leveloffset=+2] - -include::configuration-metadata.adoc[leveloffset=+2] - -include::auto-configuration-classes.adoc[leveloffset=+2] - -include::test-auto-configuration.adoc[leveloffset=+2] - -include::executable-jar.adoc[leveloffset=+2] - -include::dependency-versions.adoc[leveloffset=+2] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io.adoc deleted file mode 100644 index f21f11ffc2ac..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io.adoc +++ /dev/null @@ -1,26 +0,0 @@ -[[io]] -= IO -include::attributes.adoc[] - -Most applications will need to deal with input and output concerns at some point. -Spring Boot provides utilities and integrations with a range of technologies to help when you need IO capabilities. -This section covers standard IO features such as caching and validation as well as more advanced topics such as scheduling and distributed transactions. -We will also cover calling remote REST or SOAP services and sending email. - -include::io/caching.adoc[] - -include::io/hazelcast.adoc[] - -include::io/quartz.adoc[] - -include::io/email.adoc[] - -include::io/validation.adoc[] - -include::io/rest-client.adoc[] - -include::io/webservices.adoc[] - -include::io/jta.adoc[] - -include::io/whats-next.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/email.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/email.adoc deleted file mode 100644 index 0646df468f43..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/email.adoc +++ /dev/null @@ -1,32 +0,0 @@ -[[io.email]] -== Sending Email -The Spring Framework provides an abstraction for sending email by using the `JavaMailSender` interface, and Spring Boot provides auto-configuration for it as well as a starter module. - -TIP: See the {spring-framework-docs}/integration/email.html[reference documentation] for a detailed explanation of how you can use `JavaMailSender`. - -If `spring.mail.host` and the relevant libraries (as defined by `spring-boot-starter-mail`) are available, a default `JavaMailSender` is created if none exists. -The sender can be further customized by configuration items from the `spring.mail` namespace. -See {spring-boot-autoconfigure-module-code}/mail/MailProperties.java[`MailProperties`] for more details. - -In particular, certain default timeout values are infinite, and you may want to change that to avoid having a thread blocked by an unresponsive mail server, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - mail: - properties: - "[mail.smtp.connectiontimeout]": 5000 - "[mail.smtp.timeout]": 3000 - "[mail.smtp.writetimeout]": 5000 ----- - -It is also possible to configure a `JavaMailSender` with an existing `Session` from JNDI: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - mail: - jndi-name: "mail/Session" ----- - -When a `jndi-name` is set, it takes precedence over all other Session-related settings. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/quartz.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/quartz.adoc deleted file mode 100644 index 285d826d6327..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/quartz.adoc +++ /dev/null @@ -1,53 +0,0 @@ -[[io.quartz]] -== Quartz Scheduler -Spring Boot offers several conveniences for working with the https://www.quartz-scheduler.org/[Quartz scheduler], including the `spring-boot-starter-quartz` "`Starter`". -If Quartz is available, a `Scheduler` is auto-configured (through the `SchedulerFactoryBean` abstraction). - -Beans of the following types are automatically picked up and associated with the `Scheduler`: - -* `JobDetail`: defines a particular Job. - `JobDetail` instances can be built with the `JobBuilder` API. -* `Calendar`. -* `Trigger`: defines when a particular job is triggered. - -By default, an in-memory `JobStore` is used. -However, it is possible to configure a JDBC-based store if a `DataSource` bean is available in your application and if the configprop:spring.quartz.job-store-type[] property is configured accordingly, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - quartz: - job-store-type: "jdbc" ----- - -When the JDBC store is used, the schema can be initialized on startup, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - quartz: - jdbc: - initialize-schema: "always" ----- - -WARNING: By default, the database is detected and initialized by using the standard scripts provided with the Quartz library. -These scripts drop existing tables, deleting all triggers on every restart. -It is also possible to provide a custom script by setting the configprop:spring.quartz.jdbc.schema[] property. - -To have Quartz use a `DataSource` other than the application's main `DataSource`, declare a `DataSource` bean, annotating its `@Bean` method with `@QuartzDataSource`. -Doing so ensures that the Quartz-specific `DataSource` is used by both the `SchedulerFactoryBean` and for schema initialization. -Similarly, to have Quartz use a `TransactionManager` other than the application's main `TransactionManager` declare a `TransactionManager` bean, annotating its `@Bean` method with `@QuartzTransactionManager`. - -By default, jobs created by configuration will not overwrite already registered jobs that have been read from a persistent job store. -To enable overwriting existing job definitions set the configprop:spring.quartz.overwrite-existing-jobs[] property. - -Quartz Scheduler configuration can be customized using `spring.quartz` properties and `SchedulerFactoryBeanCustomizer` beans, which allow programmatic `SchedulerFactoryBean` customization. -Advanced Quartz configuration properties can be customized using `spring.quartz.properties.*`. - -NOTE: In particular, an `Executor` bean is not associated with the scheduler as Quartz offers a way to configure the scheduler through `spring.quartz.properties`. -If you need to customize the task executor, consider implementing `SchedulerFactoryBeanCustomizer`. - -Jobs can define setters to inject data map properties. -Regular beans can also be injected in a similar manner, as shown in the following example: - -include::code:MySampleJob[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc deleted file mode 100644 index c4ede9b41c40..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc +++ /dev/null @@ -1,132 +0,0 @@ -[[io.rest-client]] -== Calling REST Services -If your application calls remote REST services, Spring Boot makes that very convenient using a `RestTemplate` or a `WebClient`. - -[[io.rest-client.resttemplate]] -=== RestTemplate -If you need to call remote REST services from your application, you can use the Spring Framework's {spring-framework-api}/web/client/RestTemplate.html[`RestTemplate`] class. -Since `RestTemplate` instances often need to be customized before being used, Spring Boot does not provide any single auto-configured `RestTemplate` bean. -It does, however, auto-configure a `RestTemplateBuilder`, which can be used to create `RestTemplate` instances when needed. -The auto-configured `RestTemplateBuilder` ensures that sensible `HttpMessageConverters` are applied to `RestTemplate` instances. - -The following code shows a typical example: - -include::code:MyService[] - -`RestTemplateBuilder` includes a number of useful methods that can be used to quickly configure a `RestTemplate`. -For example, to add BASIC authentication support, you can use `builder.basicAuthentication("user", "password").build()`. - - - -[[io.rest-client.resttemplate.http-client]] -==== RestTemplate HTTP Client -Spring Boot will auto-detect which HTTP client to use with `RestTemplate` depending on the libraries available on the application classpath. -In order of preference, the following clients are supported: - -. Apache HttpClient -. OkHttp -. Simple JDK client (`HttpURLConnection`) - -If multiple clients are available on the classpath, the most preferred client will be used. - - - -[[io.rest-client.resttemplate.customization]] -==== RestTemplate Customization -There are three main approaches to `RestTemplate` customization, depending on how broadly you want the customizations to apply. - -To make the scope of any customizations as narrow as possible, inject the auto-configured `RestTemplateBuilder` and then call its methods as required. -Each method call returns a new `RestTemplateBuilder` instance, so the customizations only affect this use of the builder. - -To make an application-wide, additive customization, use a `RestTemplateCustomizer` bean. -All such beans are automatically registered with the auto-configured `RestTemplateBuilder` and are applied to any templates that are built with it. - -The following example shows a customizer that configures the use of a proxy for all hosts except `192.168.0.5`: - -include::code:MyRestTemplateCustomizer[] - -Finally, you can define your own `RestTemplateBuilder` bean. -Doing so will replace the auto-configured builder. -If you want any `RestTemplateCustomizer` beans to be applied to your custom builder, as the auto-configuration would have done, configure it using a `RestTemplateBuilderConfigurer`. -The following example exposes a `RestTemplateBuilder` that matches what Spring Boot's auto-configuration would have done, except that custom connect and read timeouts are also specified: - -include::code:MyRestTemplateBuilderConfiguration[] - -The most extreme (and rarely used) option is to create your own `RestTemplateBuilder` bean without using a configurer. -In addition to replacing the auto-configured builder, this also prevents any `RestTemplateCustomizer` beans from being used. - - - -[[io.rest-client.resttemplate.ssl]] -==== RestTemplate SSL Support -If you need custom SSL configuration on the `RestTemplate`, you can apply an <> to the `RestTemplateBuilder` as shown in this example: - -include::code:MyService[] - - - -[[io.rest-client.webclient]] -=== WebClient -If you have Spring WebFlux on your classpath, you can also choose to use `WebClient` to call remote REST services. -Compared to `RestTemplate`, this client has a more functional feel and is fully reactive. -You can learn more about the `WebClient` in the dedicated {spring-framework-docs}/web/webflux-webclient.html[section in the Spring Framework docs]. - -Spring Boot creates and pre-configures a `WebClient.Builder` for you. -It is strongly advised to inject it in your components and use it to create `WebClient` instances. -Spring Boot is configuring that builder to share HTTP resources, reflect codecs setup in the same fashion as the server ones (see <>), and more. - -The following code shows a typical example: - -include::code:MyService[] - - - -[[io.rest-client.webclient.runtime]] -==== WebClient Runtime -Spring Boot will auto-detect which `ClientHttpConnector` to use to drive `WebClient` depending on the libraries available on the application classpath. -In order of preference, the following clients are supported: - -. Reactor Netty -. Jetty RS client -. Apache HttpClient -. JDK HttpClient - -If multiple clients are available on the classpath, the most preferred client will be used. - -The `spring-boot-starter-webflux` starter depends on `io.projectreactor.netty:reactor-netty` by default, which brings both server and client implementations. -If you choose to use Jetty as a reactive server instead, you should add a dependency on the Jetty Reactive HTTP client library, `org.eclipse.jetty:jetty-reactive-httpclient`. -Using the same technology for server and client has its advantages, as it will automatically share HTTP resources between client and server. - -Developers can override the resource configuration for Jetty and Reactor Netty by providing a custom `ReactorResourceFactory` or `JettyResourceFactory` bean - this will be applied to both clients and servers. - -If you wish to override that choice for the client, you can define your own `ClientHttpConnector` bean and have full control over the client configuration. - -You can learn more about the {spring-framework-docs}/web/webflux-webclient/client-builder.html[`WebClient` configuration options in the Spring Framework reference documentation]. - - - -[[io.rest-client.webclient.customization]] -==== WebClient Customization -There are three main approaches to `WebClient` customization, depending on how broadly you want the customizations to apply. - -To make the scope of any customizations as narrow as possible, inject the auto-configured `WebClient.Builder` and then call its methods as required. -`WebClient.Builder` instances are stateful: Any change on the builder is reflected in all clients subsequently created with it. -If you want to create several clients with the same builder, you can also consider cloning the builder with `WebClient.Builder other = builder.clone();`. - -To make an application-wide, additive customization to all `WebClient.Builder` instances, you can declare `WebClientCustomizer` beans and change the `WebClient.Builder` locally at the point of injection. - -Finally, you can fall back to the original API and use `WebClient.create()`. -In that case, no auto-configuration or `WebClientCustomizer` is applied. - - - -[[io.rest-client.webclient.ssl]] -==== WebClient SSL Support -If you need custom SSL configuration on the `ClientHttpConnector` used by the `WebClient`, you can inject a `WebClientSsl` instance that can be used with the builder's `apply` method. - -The `WebClientSsl` interface provides access to any <> that you have defined in your `application.properties` or `application.yaml` file. - -The following code shows a typical example: - -include::code:MyService[] - diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/webservices.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/webservices.adoc deleted file mode 100644 index 04b9e5fce22b..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/webservices.adoc +++ /dev/null @@ -1,33 +0,0 @@ -[[io.webservices]] -== Web Services -Spring Boot provides Web Services auto-configuration so that all you must do is define your `Endpoints`. - -The {spring-webservices-docs}[Spring Web Services features] can be easily accessed with the `spring-boot-starter-webservices` module. - -`SimpleWsdl11Definition` and `SimpleXsdSchema` beans can be automatically created for your WSDLs and XSDs respectively. -To do so, configure their location, as shown in the following example: - - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - webservices: - wsdl-locations: "classpath:/wsdl" ----- - - - -[[io.webservices.template]] -=== Calling Web Services with WebServiceTemplate -If you need to call remote Web services from your application, you can use the {spring-webservices-docs}#client-web-service-template[`WebServiceTemplate`] class. -Since `WebServiceTemplate` instances often need to be customized before being used, Spring Boot does not provide any single auto-configured `WebServiceTemplate` bean. -It does, however, auto-configure a `WebServiceTemplateBuilder`, which can be used to create `WebServiceTemplate` instances when needed. - -The following code shows a typical example: - -include::code:MyService[] - -By default, `WebServiceTemplateBuilder` detects a suitable HTTP-based `WebServiceMessageSender` using the available HTTP client libraries on the classpath. -You can also customize read and connection timeouts as follows: - -include::code:MyWebServiceTemplateConfiguration[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/whats-next.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/whats-next.adoc deleted file mode 100644 index 442122c66642..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/whats-next.adoc +++ /dev/null @@ -1,6 +0,0 @@ -[[io.whats-next]] -== What to Read Next -You should now have a good understanding of Spring Boot's <> and the various technologies that Spring Boot provides support for through auto-configuration. - -The next few sections go into detail about deploying applications to cloud platforms. -You can read about <> in the next section or skip to the <> section. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/legal.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/legal.adoc deleted file mode 100644 index 628bb5cd4205..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/legal.adoc +++ /dev/null @@ -1,9 +0,0 @@ -[[legal]] -= Legal - -Copyright © 2012-2024 - -Copies of this document may be made for your own use and for distribution to -others, provided that you do not charge any fee for such copies and further -provided that each copy contains this Copyright Notice, whether distributed in -print or electronically. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging.adoc deleted file mode 100644 index 8b6a5ec6e626..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging.adoc +++ /dev/null @@ -1,29 +0,0 @@ -[[messaging]] -= Messaging -include::attributes.adoc[] - -The Spring Framework provides extensive support for integrating with messaging systems, from simplified use of the JMS API using `JmsTemplate` to a complete infrastructure to receive messages asynchronously. -Spring AMQP provides a similar feature set for the Advanced Message Queuing Protocol. -Spring Boot also provides auto-configuration options for `RabbitTemplate` and RabbitMQ. -Spring WebSocket natively includes support for STOMP messaging, and Spring Boot has support for that through starters and a small amount of auto-configuration. -Spring Boot also has support for Apache Kafka. - - -include::messaging/jms.adoc[] - -include::messaging/amqp.adoc[] - -include::messaging/kafka.adoc[] - -include::messaging/rsocket.adoc[] - -include::messaging/spring-integration.adoc[] - -include::messaging/websockets.adoc[] - -include::messaging/whats-next.adoc[] - - - - - diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/jms.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/jms.adoc deleted file mode 100644 index d0a22ebe440c..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/jms.adoc +++ /dev/null @@ -1,163 +0,0 @@ -[[messaging.jms]] -== JMS -The `jakarta.jms.ConnectionFactory` interface provides a standard method of creating a `jakarta.jms.Connection` for interacting with a JMS broker. -Although Spring needs a `ConnectionFactory` to work with JMS, you generally need not use it directly yourself and can instead rely on higher level messaging abstractions. -(See the {spring-framework-docs}/integration/jms.html[relevant section] of the Spring Framework reference documentation for details.) -Spring Boot also auto-configures the necessary infrastructure to send and receive messages. - - - -[[messaging.jms.activemq]] -=== ActiveMQ "Classic" Support -When https://activemq.apache.org/components/classic[ActiveMQ "Classic"] is available on the classpath, Spring Boot can configure a `ConnectionFactory`. - -NOTE: If you use `spring-boot-starter-activemq`, the necessary dependencies to connect to an ActiveMQ "Classic" instance are provided, as is the Spring infrastructure to integrate with JMS. - -ActiveMQ "Classic" configuration is controlled by external configuration properties in `+spring.activemq.*+`. -By default, ActiveMQ "Classic" is auto-configured to use the https://activemq.apache.org/tcp-transport-reference[TCP transport], connecting by default to `tcp://localhost:61616`. The following example shows how to change the default broker URL: - -[source,yaml,indent=0,configprops,configblocks] ----- - spring: - activemq: - broker-url: "tcp://192.168.1.210:9876" - user: "admin" - password: "secret" ----- - -By default, a `CachingConnectionFactory` wraps the native `ConnectionFactory` with sensible settings that you can control by external configuration properties in `+spring.jms.*+`: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - jms: - cache: - session-cache-size: 5 ----- - -If you'd rather use native pooling, you can do so by adding a dependency to `org.messaginghub:pooled-jms` and configuring the `JmsPoolConnectionFactory` accordingly, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - activemq: - pool: - enabled: true - max-connections: 50 ----- - -TIP: See {spring-boot-autoconfigure-module-code}/jms/activemq/ActiveMQProperties.java[`ActiveMQProperties`] for more of the supported options. -You can also register an arbitrary number of beans that implement `ActiveMQConnectionFactoryCustomizer` for more advanced customizations. - -By default, ActiveMQ "Classic" creates a destination if it does not yet exist so that destinations are resolved against their provided names. - - - -[[messaging.jms.artemis]] -=== ActiveMQ Artemis Support -Spring Boot can auto-configure a `ConnectionFactory` when it detects that https://activemq.apache.org/components/artemis/[ActiveMQ Artemis] is available on the classpath. -If the broker is present, an embedded broker is automatically started and configured (unless the mode property has been explicitly set). -The supported modes are `embedded` (to make explicit that an embedded broker is required and that an error should occur if the broker is not available on the classpath) and `native` (to connect to a broker using the `netty` transport protocol). -When the latter is configured, Spring Boot configures a `ConnectionFactory` that connects to a broker running on the local machine with the default settings. - -NOTE: If you use `spring-boot-starter-artemis`, the necessary dependencies to connect to an existing ActiveMQ Artemis instance are provided, as well as the Spring infrastructure to integrate with JMS. -Adding `org.apache.activemq:artemis-jakarta-server` to your application lets you use embedded mode. - -ActiveMQ Artemis configuration is controlled by external configuration properties in `+spring.artemis.*+`. -For example, you might declare the following section in `application.properties`: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - artemis: - mode: native - broker-url: "tcp://192.168.1.210:9876" - user: "admin" - password: "secret" ----- - -When embedding the broker, you can choose if you want to enable persistence and list the destinations that should be made available. -These can be specified as a comma-separated list to create them with the default options, or you can define bean(s) of type `org.apache.activemq.artemis.jms.server.config.JMSQueueConfiguration` or `org.apache.activemq.artemis.jms.server.config.TopicConfiguration`, for advanced queue and topic configurations, respectively. - -By default, a `CachingConnectionFactory` wraps the native `ConnectionFactory` with sensible settings that you can control by external configuration properties in `+spring.jms.*+`: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - jms: - cache: - session-cache-size: 5 ----- - -If you'd rather use native pooling, you can do so by adding a dependency on `org.messaginghub:pooled-jms` and configuring the `JmsPoolConnectionFactory` accordingly, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - artemis: - pool: - enabled: true - max-connections: 50 ----- - -See {spring-boot-autoconfigure-module-code}/jms/artemis/ArtemisProperties.java[`ArtemisProperties`] for more supported options. - -No JNDI lookup is involved, and destinations are resolved against their names, using either the `name` attribute in the ActiveMQ Artemis configuration or the names provided through configuration. - - - -[[messaging.jms.jndi]] -=== Using a JNDI ConnectionFactory -If you are running your application in an application server, Spring Boot tries to locate a JMS `ConnectionFactory` by using JNDI. -By default, the `java:/JmsXA` and `java:/XAConnectionFactory` location are checked. -You can use the configprop:spring.jms.jndi-name[] property if you need to specify an alternative location, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - jms: - jndi-name: "java:/MyConnectionFactory" ----- - - - -[[messaging.jms.sending]] -=== Sending a Message -Spring's `JmsTemplate` is auto-configured, and you can autowire it directly into your own beans, as shown in the following example: - -include::code:MyBean[] - -NOTE: {spring-framework-api}/jms/core/JmsMessagingTemplate.html[`JmsMessagingTemplate`] can be injected in a similar manner. -If a `DestinationResolver` or a `MessageConverter` bean is defined, it is associated automatically to the auto-configured `JmsTemplate`. - - - -[[messaging.jms.receiving]] -=== Receiving a Message - -When the JMS infrastructure is present, any bean can be annotated with `@JmsListener` to create a listener endpoint. -If no `JmsListenerContainerFactory` has been defined, a default one is configured automatically. -If a `DestinationResolver`, a `MessageConverter`, or a `jakarta.jms.ExceptionListener` beans are defined, they are associated automatically with the default factory. - -By default, the default factory is transactional. -If you run in an infrastructure where a `JtaTransactionManager` is present, it is associated to the listener container by default. -If not, the `sessionTransacted` flag is enabled. -In that latter scenario, you can associate your local data store transaction to the processing of an incoming message by adding `@Transactional` on your listener method (or a delegate thereof). -This ensures that the incoming message is acknowledged, once the local transaction has completed. -This also includes sending response messages that have been performed on the same JMS session. - -The following component creates a listener endpoint on the `someQueue` destination: - -include::code:MyBean[] - -TIP: See {spring-framework-api}/jms/annotation/EnableJms.html[the Javadoc of `@EnableJms`] for more details. - -If you need to create more `JmsListenerContainerFactory` instances or if you want to override the default, Spring Boot provides a `DefaultJmsListenerContainerFactoryConfigurer` that you can use to initialize a `DefaultJmsListenerContainerFactory` with the same settings as the one that is auto-configured. - -For instance, the following example exposes another factory that uses a specific `MessageConverter`: - -include::code:custom/MyJmsConfiguration[] - -Then you can use the factory in any `@JmsListener`-annotated method as follows: - -include::code:custom/MyBean[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/kafka.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/kafka.adoc deleted file mode 100644 index 9956d84f5f8e..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/kafka.adoc +++ /dev/null @@ -1,165 +0,0 @@ -[[messaging.kafka]] -== Apache Kafka Support -https://kafka.apache.org/[Apache Kafka] is supported by providing auto-configuration of the `spring-kafka` project. - -Kafka configuration is controlled by external configuration properties in `spring.kafka.*`. -For example, you might declare the following section in `application.properties`: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - kafka: - bootstrap-servers: "localhost:9092" - consumer: - group-id: "myGroup" ----- - -TIP: To create a topic on startup, add a bean of type `NewTopic`. -If the topic already exists, the bean is ignored. - -See {spring-boot-autoconfigure-module-code}/kafka/KafkaProperties.java[`KafkaProperties`] for more supported options. - - - -[[messaging.kafka.sending]] -=== Sending a Message -Spring's `KafkaTemplate` is auto-configured, and you can autowire it directly in your own beans, as shown in the following example: - -include::code:MyBean[] - -NOTE: If the property configprop:spring.kafka.producer.transaction-id-prefix[] is defined, a `KafkaTransactionManager` is automatically configured. -Also, if a `RecordMessageConverter` bean is defined, it is automatically associated to the auto-configured `KafkaTemplate`. - - - -[[messaging.kafka.receiving]] -=== Receiving a Message -When the Apache Kafka infrastructure is present, any bean can be annotated with `@KafkaListener` to create a listener endpoint. -If no `KafkaListenerContainerFactory` has been defined, a default one is automatically configured with keys defined in `spring.kafka.listener.*`. - -The following component creates a listener endpoint on the `someTopic` topic: - -include::code:MyBean[] - -If a `KafkaTransactionManager` bean is defined, it is automatically associated to the container factory. -Similarly, if a `RecordFilterStrategy`, `CommonErrorHandler`, `AfterRollbackProcessor` or `ConsumerAwareRebalanceListener` bean is defined, it is automatically associated to the default factory. - -Depending on the listener type, a `RecordMessageConverter` or `BatchMessageConverter` bean is associated to the default factory. -If only a `RecordMessageConverter` bean is present for a batch listener, it is wrapped in a `BatchMessageConverter`. - -TIP: A custom `ChainedKafkaTransactionManager` must be marked `@Primary` as it usually references the auto-configured `KafkaTransactionManager` bean. - - - -[[messaging.kafka.streams]] -=== Kafka Streams -Spring for Apache Kafka provides a factory bean to create a `StreamsBuilder` object and manage the lifecycle of its streams. -Spring Boot auto-configures the required `KafkaStreamsConfiguration` bean as long as `kafka-streams` is on the classpath and Kafka Streams is enabled by the `@EnableKafkaStreams` annotation. - -Enabling Kafka Streams means that the application id and bootstrap servers must be set. -The former can be configured using `spring.kafka.streams.application-id`, defaulting to `spring.application.name` if not set. -The latter can be set globally or specifically overridden only for streams. - -Several additional properties are available using dedicated properties; other arbitrary Kafka properties can be set using the `spring.kafka.streams.properties` namespace. -See also <> for more information. - -To use the factory bean, wire `StreamsBuilder` into your `@Bean` as shown in the following example: - -include::code:MyKafkaStreamsConfiguration[] - -By default, the streams managed by the `StreamBuilder` object are started automatically. -You can customize this behavior using the configprop:spring.kafka.streams.auto-startup[] property. - - - -[[messaging.kafka.additional-properties]] -=== Additional Kafka Properties -The properties supported by auto configuration are shown in the <> section of the Appendix. -Note that, for the most part, these properties (hyphenated or camelCase) map directly to the Apache Kafka dotted properties. -See the Apache Kafka documentation for details. - -Properties that don't include a client type (`producer`, `consumer`, `admin`, or `streams`) in their name are considered to be common and apply to all clients. -Most of these common properties can be overridden for one or more of the client types, if needed. - -Apache Kafka designates properties with an importance of HIGH, MEDIUM, or LOW. -Spring Boot auto-configuration supports all HIGH importance properties, some selected MEDIUM and LOW properties, and any properties that do not have a default value. - -Only a subset of the properties supported by Kafka are available directly through the `KafkaProperties` class. -If you wish to configure the individual client types with additional properties that are not directly supported, use the following properties: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - kafka: - properties: - "[prop.one]": "first" - admin: - properties: - "[prop.two]": "second" - consumer: - properties: - "[prop.three]": "third" - producer: - properties: - "[prop.four]": "fourth" - streams: - properties: - "[prop.five]": "fifth" ----- - -This sets the common `prop.one` Kafka property to `first` (applies to producers, consumers, admins, and streams), the `prop.two` admin property to `second`, the `prop.three` consumer property to `third`, the `prop.four` producer property to `fourth` and the `prop.five` streams property to `fifth`. - -You can also configure the Spring Kafka `JsonDeserializer` as follows: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - kafka: - consumer: - value-deserializer: "org.springframework.kafka.support.serializer.JsonDeserializer" - properties: - "[spring.json.value.default.type]": "com.example.Invoice" - "[spring.json.trusted.packages]": "com.example.main,com.example.another" ----- - -Similarly, you can disable the `JsonSerializer` default behavior of sending type information in headers: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - kafka: - producer: - value-serializer: "org.springframework.kafka.support.serializer.JsonSerializer" - properties: - "[spring.json.add.type.headers]": false ----- - -IMPORTANT: Properties set in this way override any configuration item that Spring Boot explicitly supports. - - - -[[messaging.kafka.embedded]] -=== Testing with Embedded Kafka -Spring for Apache Kafka provides a convenient way to test projects with an embedded Apache Kafka broker. -To use this feature, annotate a test class with `@EmbeddedKafka` from the `spring-kafka-test` module. -For more information, please see the Spring for Apache Kafka {spring-kafka-docs}#embedded-kafka-annotation[reference manual]. - -To make Spring Boot auto-configuration work with the aforementioned embedded Apache Kafka broker, you need to remap a system property for embedded broker addresses (populated by the `EmbeddedKafkaBroker`) into the Spring Boot configuration property for Apache Kafka. -There are several ways to do that: - -* Provide a system property to map embedded broker addresses into configprop:spring.kafka.bootstrap-servers[] in the test class: - -include::code:property/MyTest[tag=*] - -* Configure a property name on the `@EmbeddedKafka` annotation: - -include::code:annotation/MyTest[] - -* Use a placeholder in configuration properties: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - kafka: - bootstrap-servers: "${spring.embedded.kafka.brokers}" ----- diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/spring-integration.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/spring-integration.adoc deleted file mode 100644 index 67b55870b5c0..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/spring-integration.adoc +++ /dev/null @@ -1,48 +0,0 @@ -[[messaging.spring-integration]] -== Spring Integration -Spring Boot offers several conveniences for working with {spring-integration}[Spring Integration], including the `spring-boot-starter-integration` "`Starter`". -Spring Integration provides abstractions over messaging and also other transports such as HTTP, TCP, and others. -If Spring Integration is available on your classpath, it is initialized through the `@EnableIntegration` annotation. - -Spring Integration polling logic relies <>. -The default `PollerMetadata` (poll unbounded number of messages every second) can be customized with `spring.integration.poller.*` configuration properties. - -Spring Boot also configures some features that are triggered by the presence of additional Spring Integration modules. -If `spring-integration-jmx` is also on the classpath, message processing statistics are published over JMX. -If `spring-integration-jdbc` is available, the default database schema can be created on startup, as shown in the following line: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - integration: - jdbc: - initialize-schema: "always" ----- - -If `spring-integration-rsocket` is available, developers can configure an RSocket server using `"spring.rsocket.server.*"` properties and let it use `IntegrationRSocketEndpoint` or `RSocketOutboundGateway` components to handle incoming RSocket messages. -This infrastructure can handle Spring Integration RSocket channel adapters and `@MessageMapping` handlers (given `"spring.integration.rsocket.server.message-mapping-enabled"` is configured). - -Spring Boot can also auto-configure an `ClientRSocketConnector` using configuration properties: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - # Connecting to a RSocket server over TCP - spring: - integration: - rsocket: - client: - host: "example.org" - port: 9898 ----- - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - # Connecting to a RSocket Server over WebSocket - spring: - integration: - rsocket: - client: - uri: "ws://example.org" ----- - -See the {spring-boot-autoconfigure-module-code}/integration/IntegrationAutoConfiguration.java[`IntegrationAutoConfiguration`] and {spring-boot-autoconfigure-module-code}/integration/IntegrationProperties.java[`IntegrationProperties`] classes for more details. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/websockets.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/websockets.adoc deleted file mode 100644 index 4a3e0f23d59c..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/websockets.adoc +++ /dev/null @@ -1,16 +0,0 @@ -[[messaging.websockets]] -== WebSockets -Spring Boot provides WebSockets auto-configuration for embedded Tomcat, Jetty, and Undertow. -If you deploy a war file to a standalone container, Spring Boot assumes that the container is responsible for the configuration of its WebSocket support. - -Spring Framework provides {spring-framework-docs}/web/websocket.html[rich WebSocket support] for MVC web applications that can be easily accessed through the `spring-boot-starter-websocket` module. - -WebSocket support is also available for {spring-framework-docs}/web/webflux-websocket.html[reactive web applications] and requires to include the WebSocket API alongside `spring-boot-starter-webflux`: - -[source,xml,indent=0,subs="verbatim"] ----- - - jakarta.websocket - jakarta.websocket-api - ----- diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/whats-next.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/whats-next.adoc deleted file mode 100644 index 36841ba1a172..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/whats-next.adoc +++ /dev/null @@ -1,4 +0,0 @@ -[[messaging.whats-next]] -== What to Read Next -The next section describes how to enable <> in your application. -You can read about <>, <>, <>, <> and more in this section. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/native-image.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/native-image.adoc deleted file mode 100644 index 80a6b732ea9e..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/native-image.adoc +++ /dev/null @@ -1,16 +0,0 @@ -[[native-image]] -= GraalVM Native Image Support -include::attributes.adoc[] - -https://www.graalvm.org/native-image/[GraalVM Native Images] are standalone executables that can be generated by processing compiled Java applications ahead-of-time. -Native Images generally have a smaller memory footprint and start faster than their JVM counterparts. - -include::native-image/introducing-graalvm-native-images.adoc[] - -include::native-image/developing-your-first-application.adoc[] - -include::native-image/testing-native-applications.adoc[] - -include::native-image/advanced-topics.adoc[] - -include::native-image/whats-next.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/native-image/advanced-topics.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/native-image/advanced-topics.adoc deleted file mode 100644 index d4bc685f82ca..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/native-image/advanced-topics.adoc +++ /dev/null @@ -1,184 +0,0 @@ -[[native-image.advanced]] -== Advanced Native Images Topics - - - -[[native-image.advanced.nested-configuration-properties]] -=== Nested Configuration Properties - -Reflection hints are automatically created for configuration properties by the Spring ahead-of-time engine. -Nested configuration properties which are not inner classes, however, *must* be annotated with `@NestedConfigurationProperty`, otherwise they won't be detected and will not be bindable. - -include::code:MyProperties[] - -where `Nested` is: - -include::code:Nested[] - -The example above produces configuration properties for `my.properties.name` and `my.properties.nested.number`. -Without the `@NestedConfigurationProperty` annotation on the `nested` field, the `my.properties.nested.number` property would not be bindable in a native image. - -When using constructor binding, you have to annotate the field with `@NestedConfigurationProperty`: - -include::code:MyPropertiesCtor[] - -When using records, you have to annotate the parameter with `@NestedConfigurationProperty`: - -include::code:MyPropertiesRecord[] - -When using Kotlin, you need to annotate the parameter of a data class with `@NestedConfigurationProperty`: - -include::code:MyPropertiesKotlin[] - -NOTE: Please use public getters and setters in all cases, otherwise the properties will not be bindable. - -[[native-image.advanced.converting-executable-jars]] -=== Converting a Spring Boot Executable Jar - -It is possible to convert a Spring Boot <> into a native image as long as the jar contains the AOT generated assets. -This can be useful for a number of reasons, including: - -* You can keep your regular JVM pipeline and turn the JVM application into a native image on your CI/CD platform. -* As `native-image` https://github.com/oracle/graal/issues/407[does not support cross-compilation], you can keep an OS neutral deployment artifact which you convert later to different OS architectures. - -You can convert a Spring Boot executable jar into a native image using Cloud Native Buildpacks, or using the `native-image` tool that is shipped with GraalVM. - -NOTE: Your executable jar must include AOT generated assets such as generated classes and JSON hint files. - - - -[[native-image.advanced.converting-executable-jars.buildpacks]] -==== Using Buildpacks -Spring Boot applications usually use Cloud Native Buildpacks through the Maven (`mvn spring-boot:build-image`) or Gradle (`gradle bootBuildImage`) integrations. -You can, however, also use https://buildpacks.io//docs/tools/pack/[`pack`] to turn an AOT processed Spring Boot executable jar into a native container image. - - -First, make sure that a Docker daemon is available (see https://docs.docker.com/installation/#installation[Get Docker] for more details). -https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user[Configure it to allow non-root user] if you are on Linux. - -You also need to install `pack` by following https://buildpacks.io//docs/tools/pack/#install[the installation guide on buildpacks.io]. - -Assuming an AOT processed Spring Boot executable jar built as `myproject-0.0.1-SNAPSHOT.jar` is in the `target` directory, run: - -[source,shell,indent=0,subs="verbatim"] ----- - $ pack build --builder paketobuildpacks/builder-jammy-tiny \ - --path target/myproject-0.0.1-SNAPSHOT.jar \ - --env 'BP_NATIVE_IMAGE=true' \ - my-application:0.0.1-SNAPSHOT ----- - -NOTE: You do not need to have a local GraalVM installation to generate an image in this way. - -Once `pack` has finished, you can launch the application using `docker run`: - -[source,shell,indent=0,subs="verbatim"] ----- - $ docker run --rm -p 8080:8080 docker.io/library/myproject:0.0.1-SNAPSHOT ----- - - - -[[native-image.advanced.converting-executable-jars.native-image]] -==== Using GraalVM native-image -Another option to turn an AOT processed Spring Boot executable jar into a native executable is to use the GraalVM `native-image` tool. -For this to work, you'll need a GraalVM distribution on your machine. -You can either download it manually on the {liberica-nik-download}[Liberica Native Image Kit page] or you can use a download manager like SDKMAN!. - -Assuming an AOT processed Spring Boot executable jar built as `myproject-0.0.1-SNAPSHOT.jar` is in the `target` directory, run: - -[source,shell,indent=0,subs="verbatim"] ----- - $ rm -rf target/native - $ mkdir -p target/native - $ cd target/native - $ jar -xvf ../myproject-0.0.1-SNAPSHOT.jar - $ native-image -H:Name=myproject @META-INF/native-image/argfile -cp .:BOOT-INF/classes:`find BOOT-INF/lib | tr '\n' ':'` - $ mv myproject ../ ----- - -NOTE: These commands work on Linux or macOS machines, but you will need to adapt them for Windows. - -TIP: The `@META-INF/native-image/argfile` might not be packaged in your jar. -It is only included when reachability metadata overrides are needed. - -WARNING: The `native-image` `-cp` flag does not accept wildcards. -You need to ensure that all jars are listed (the command above uses `find` and `tr` to do this). - - - -[[native-image.advanced.using-the-tracing-agent]] -=== Using the Tracing Agent -The GraalVM native image {graal-native-image-docs}/metadata/AutomaticMetadataCollection[tracing agent] allows you to intercept reflection, resources or proxy usage on the JVM in order to generate the related hints. -Spring should generate most of these hints automatically, but the tracing agent can be used to quickly identify the missing entries. - -When using the agent to generate hints for a native image, there are a couple of approaches: - -* Launch the application directly and exercise it. -* Run application tests to exercise the application. - -The first option is interesting for identifying the missing hints when a library or a pattern is not recognized by Spring. - -The second option sounds more appealing for a repeatable setup, but by default the generated hints will include anything required by the test infrastructure. -Some of these will be unnecessary when the application runs for real. -To address this problem the agent supports an access-filter file that will cause certain data to be excluded from the generated output. - - - -[[native-image.advanced.using-the-tracing-agent.launch]] -==== Launch the Application Directly -Use the following command to launch the application with the native image tracing agent attached: - -[source,shell,indent=0,subs="verbatim,attributes"] ----- - $ java -Dspring.aot.enabled=true \ - -agentlib:native-image-agent=config-output-dir=/path/to/config-dir/ \ - -jar target/myproject-0.0.1-SNAPSHOT.jar ----- - -Now you can exercise the code paths you want to have hints for and then stop the application with `ctrl-c`. - -On application shutdown the native image tracing agent will write the hint files to the given config output directory. -You can either manually inspect these files, or use them as input to the native image build process. -To use them as input, copy them into the `src/main/resources/META-INF/native-image/` directory. -The next time you build the native image, GraalVM will take these files into consideration. - -There are more advanced options which can be set on the native image tracing agent, for example filtering the recorded hints by caller classes, etc. -For further reading, please see {graal-native-image-docs}/metadata/AutomaticMetadataCollection[the official documentation]. - - - -[[native-image.advanced.custom-hints]] -=== Custom Hints -If you need to provide your own hints for reflection, resources, serialization, proxy usage etc. you can use the `RuntimeHintsRegistrar` API. -Create a class that implements the `RuntimeHintsRegistrar` interface, and then make appropriate calls to the provided `RuntimeHints` instance: - -include::code:MyRuntimeHints[] - -You can then use `@ImportRuntimeHints` on any `@Configuration` class (for example your `@SpringBootApplication` annotated application class) to activate those hints. - -If you have classes which need binding (mostly needed when serializing or deserializing JSON), you can use {spring-framework-docs}/core/aot.html#aot.hints.register-reflection-for-binding[`@RegisterReflectionForBinding`] on any bean. -Most of the hints are automatically inferred, for example when accepting or returning data from a `@RestController` method. -But when you work with `WebClient` or `RestTemplate` directly, you might need to use `@RegisterReflectionForBinding`. - -[[native-image.advanced.custom-hints.testing]] -==== Testing custom hints -The `RuntimeHintsPredicates` API can be used to test your hints. -The API provides methods that build a `Predicate` that can be used to test a `RuntimeHints` instance. - -If you're using AssertJ, your test would look like this: - -include::code:MyRuntimeHintsTests[] - - - -[[native-image.advanced.known-limitations]] -=== Known Limitations -GraalVM native images are an evolving technology and not all libraries provide support. -The GraalVM community is helping by providing https://github.com/oracle/graalvm-reachability-metadata[reachability metadata] for projects that don't yet ship their own. -Spring itself doesn't contain hints for 3rd party libraries and instead relies on the reachability metadata project. - -If you encounter problems when generating native images for Spring Boot applications, please check the {github-wiki}/Spring-Boot-with-GraalVM[Spring Boot with GraalVM] page of the Spring Boot wiki. -You can also contribute issues to the https://github.com/spring-projects/spring-aot-smoke-tests[spring-aot-smoke-tests] project on GitHub which is used to confirm that common application types are working as expected. - -If you find a library which doesn't work with GraalVM, please raise an issue on the https://github.com/oracle/graalvm-reachability-metadata[reachability metadata project]. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/native-image/developing-your-first-application.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/native-image/developing-your-first-application.adoc deleted file mode 100644 index fe5a0d568380..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/native-image/developing-your-first-application.adoc +++ /dev/null @@ -1,264 +0,0 @@ -[[native-image.developing-your-first-application]] -== Developing Your First GraalVM Native Application -Now that we have a good overview of GraalVM Native Images and how the Spring ahead-of-time engine works, we can look at how to create an application. - -There are two main ways to build a Spring Boot native image application: - -* Using Spring Boot support for Cloud Native Buildpacks to generate a lightweight container containing a native executable. -* Using GraalVM Native Build Tools to generate a native executable. - -TIP: The easiest way to start a new native Spring Boot project is to go to https://start.spring.io[start.spring.io], add the "`GraalVM Native Support`" dependency and generate the project. -The included `HELP.md` file will provide getting started hints. - - - -[[native-image.developing-your-first-application.sample-application]] -=== Sample Application -We need an example application that we can use to create our native image. -For our purposes, the simple "`Hello World!`" web application that's covered in the "`<>`" section will suffice. - -To recap, our main application code looks like this: - -include::code:MyApplication[] - -This application uses Spring MVC and embedded Tomcat, both of which have been tested and verified to work with GraalVM native images. - - - -[[native-image.developing-your-first-application.buildpacks]] -=== Building a Native Image Using Buildpacks -Spring Boot includes buildpack support for native images directly for both Maven and Gradle. -This means you can just type a single command and quickly get a sensible image into your locally running Docker daemon. -The resulting image doesn't contain a JVM, instead the native image is compiled statically. -This leads to smaller images. - -NOTE: The builder used for the images is `paketobuildpacks/builder-jammy-tiny:latest`. -It has small footprint and reduced attack surface, but you can also use `paketobuildpacks/builder-jammy-base:latest` or `paketobuildpacks/builder-jammy-full:latest` to have more tools available in the image if required. - - - -[[native-image.developing-your-first-application.buildpacks.system-requirements]] -==== System Requirements -Docker should be installed. See https://docs.docker.com/installation/#installation[Get Docker] for more details. -https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user[Configure it to allow non-root user] if you are on Linux. - -NOTE: You can run `docker run hello-world` (without `sudo`) to check the Docker daemon is reachable as expected. -Check the {spring-boot-maven-plugin-docs}/#build-image-docker-daemon[Maven] or {spring-boot-gradle-plugin-docs}/#build-image-docker-daemon[Gradle] Spring Boot plugin documentation for more details. - -TIP: On macOS, it is recommended to increase the memory allocated to Docker to at least `8GB`, and potentially add more CPUs as well. -See this https://stackoverflow.com/questions/44533319/how-to-assign-more-memory-to-docker-container/44533437#44533437[Stack Overflow answer] for more details. -On Microsoft Windows, make sure to enable the https://docs.docker.com/docker-for-windows/wsl/[Docker WSL 2 backend] for better performance. - - - -[[native-image.developing-your-first-application.buildpacks.maven]] -==== Using Maven -To build a native image container using Maven you should ensure that your `pom.xml` file uses the `spring-boot-starter-parent` and the `org.graalvm.buildtools:native-maven-plugin`. -You should have a `` section that looks like this: - -[source,xml,indent=0,subs="verbatim,attributes"] ----- - - org.springframework.boot - spring-boot-starter-parent - {spring-boot-version} - ----- - -You additionally should have this in the ` ` section: - -[source,xml,indent=0,subs="verbatim,attributes"] ----- - - org.graalvm.buildtools - native-maven-plugin - ----- - -The `spring-boot-starter-parent` declares a `native` profile that configures the executions that need to run in order to create a native image. -You can activate profiles using the `-P` flag on the command line. - -TIP: If you don't want to use `spring-boot-starter-parent` you'll need to configure executions for the `process-aot` goal from Spring Boot's plugin and the `add-reachability-metadata` goal from the Native Build Tools plugin. - -To build the image, you can run the `spring-boot:build-image` goal with the `native` profile active: - -[source,shell,indent=0,subs="verbatim"] ----- - $ mvn -Pnative spring-boot:build-image ----- - - - -[[native-image.developing-your-first-application.buildpacks.gradle]] -==== Using Gradle -The Spring Boot Gradle plugin automatically configures AOT tasks when the GraalVM Native Image plugin is applied. -You should check that your Gradle build contains a `plugins` block that includes `org.graalvm.buildtools.native`. - -As long as the `org.graalvm.buildtools.native` plugin is applied, the `bootBuildImage` task will generate a native image rather than a JVM one. -You can run the task using: - -[source,shell,indent=0,subs="verbatim"] ----- - $ gradle bootBuildImage ----- - - - -[[native-image.developing-your-first-application.buildpacks.running]] -==== Running the example -Once you have run the appropriate build command, a Docker image should be available. -You can start your application using `docker run`: - -[source,shell,indent=0,subs="verbatim"] ----- - $ docker run --rm -p 8080:8080 docker.io/library/myproject:0.0.1-SNAPSHOT ----- - -You should see output similar to the following: - -[source,shell,indent=0,subs="verbatim,attributes"] ----- - . ____ _ __ _ _ - /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ - ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ - \\/ ___)| |_)| | | | | || (_| | ) ) ) ) - ' |____| .__|_| |_|_| |_\__, | / / / / - =========|_|==============|___/=/_/_/_/ - :: Spring Boot :: (v{spring-boot-version}) - ....... . . . - ....... . . . (log output here) - ....... . . . - ........ Started MyApplication in 0.08 seconds (process running for 0.095) ----- - -NOTE: The startup time differs from machine to machine, but it should be much faster than a Spring Boot application running on a JVM. - -If you open a web browser to `http://localhost:8080`, you should see the following output: - -[indent=0] ----- - Hello World! ----- - -To gracefully exit the application, press `ctrl-c`. - - - -[[native-image.developing-your-first-application.native-build-tools]] -=== Building a Native Image using Native Build Tools -If you want to generate a native executable directly without using Docker, you can use GraalVM Native Build Tools. -Native Build Tools are plugins shipped by GraalVM for both Maven and Gradle. -You can use them to perform a variety of GraalVM tasks, including generating a native image. - - - -[[native-image.developing-your-first-application.native-build-tools.prerequisites]] -==== Prerequisites -To build a native image using the Native Build Tools, you'll need a GraalVM distribution on your machine. -You can either download it manually on the {liberica-nik-download}[Liberica Native Image Kit page], or you can use a download manager like SDKMAN!. - -[[native-image.developing-your-first-application.native-build-tools.prerequisites.linux-macos]] -===== Linux and macOS - -To install the native image compiler on macOS or Linux, we recommend using SDKMAN!. -Get SDKMAN! from https://sdkman.io and install the Liberica GraalVM distribution by using the following commands: - -[source,shell,indent=0,subs="verbatim,attributes"] ----- - $ sdk install java {graal-version}.r17-nik - $ sdk use java {graal-version}.r17-nik ----- - -Verify that the correct version has been configured by checking the output of `java -version`: - -[source,shell,indent=0,subs="verbatim,attributes"] ----- - $ java -version - openjdk version "17.0.5" 2022-10-18 LTS - OpenJDK Runtime Environment GraalVM 22.3.0 (build 17.0.5+8-LTS) - OpenJDK 64-Bit Server VM GraalVM 22.3.0 (build 17.0.5+8-LTS, mixed mode) ----- - - - -[[native-image.developing-your-first-application.native-build-tools.prerequisites.windows]] -===== Windows - -On Windows, follow https://medium.com/graalvm/using-graalvm-and-native-image-on-windows-10-9954dc071311[these instructions] to install either https://www.graalvm.org/downloads/[GraalVM] or {liberica-nik-download}[Liberica Native Image Kit] in version {graal-version}, the Visual Studio Build Tools and the Windows SDK. -Due to the https://docs.microsoft.com/en-US/troubleshoot/windows-client/shell-experience/command-line-string-limitation[Windows related command-line maximum length], make sure to use x64 Native Tools Command Prompt instead of the regular Windows command line to run Maven or Gradle plugins. - - - -[[native-image.developing-your-first-application.native-build-tools.maven]] -==== Using Maven - -As with the <>, you need to make sure that you're using `spring-boot-starter-parent` in order to inherit the `native` profile and that the `org.graalvm.buildtools:native-maven-plugin` plugin is used. - -With the `native` profile active, you can invoke the `native:compile` goal to trigger `native-image` compilation: - -[source,shell,indent=0,subs="verbatim"] ----- - $ mvn -Pnative native:compile ----- - -The native image executable can be found in the `target` directory. - - - -[[native-image.developing-your-first-application.native-build-tools.gradle]] -==== Using Gradle -When the Native Build Tools Gradle plugin is applied to your project, the Spring Boot Gradle plugin will automatically trigger the Spring AOT engine. -Task dependencies are automatically configured, so you can just run the standard `nativeCompile` task to generate a native image: - -[source,shell,indent=0,subs="verbatim"] ----- - $ gradle nativeCompile ----- - -The native image executable can be found in the `build/native/nativeCompile` directory. - - - -[[native-image.developing-your-first-application.native-build-tools.running]] -==== Running the Example -At this point, your application should work. You can now start the application by running it directly: - -[source,shell,indent=0,subs="verbatim",role="primary"] -.Maven ----- - $ target/myproject ----- - -[source,shell,indent=0,subs="verbatim",role="secondary"] -.Gradle ----- - $ build/native/nativeCompile/myproject ----- - -You should see output similar to the following: - -[source,shell,indent=0,subs="verbatim,attributes"] ----- - . ____ _ __ _ _ - /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ - ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ - \\/ ___)| |_)| | | | | || (_| | ) ) ) ) - ' |____| .__|_| |_|_| |_\__, | / / / / - =========|_|==============|___/=/_/_/_/ - :: Spring Boot :: (v{spring-boot-version}) - ....... . . . - ....... . . . (log output here) - ....... . . . - ........ Started MyApplication in 0.08 seconds (process running for 0.095) ----- - -NOTE: The startup time differs from machine to machine, but it should be much faster than a Spring Boot application running on a JVM. - -If you open a web browser to `http://localhost:8080`, you should see the following output: - -[indent=0] ----- - Hello World! ----- - -To gracefully exit the application, press `ctrl-c`. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/native-image/whats-next.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/native-image/whats-next.adoc deleted file mode 100644 index ad8fbd8a54a8..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/native-image/whats-next.adoc +++ /dev/null @@ -1,8 +0,0 @@ -[[native-image.whats-next]] -== What to Read Next -If you want to learn more about the ahead-of-time processing provided by our build plugins, see the {spring-boot-maven-plugin-docs}[Maven] and {spring-boot-gradle-plugin-docs}[Gradle] plugin documentation. -To learn more about the APIs used to perform the processing, browse the `org.springframework.aot.generate` and `org.springframework.beans.factory.aot` packages of the Spring Framework sources. - -For known limitations with Spring and GraalVM, please see the {github-wiki}/Spring-Boot-with-GraalVM[Spring Boot wiki]. - -The next section goes on to cover the _<>_. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/test-auto-configuration.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/test-auto-configuration.adoc deleted file mode 100644 index 23b985725e56..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/test-auto-configuration.adoc +++ /dev/null @@ -1,12 +0,0 @@ -[appendix] -[[appendix.test-auto-configuration]] -= Test Auto-configuration Annotations -include::attributes.adoc[] - - - -This appendix describes the `@...Test` auto-configuration annotations that Spring Boot provides to test slices of your application. - - - -include::test-auto-configuration/slices.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading.adoc deleted file mode 100644 index 1292c64da3a5..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading.adoc +++ /dev/null @@ -1,17 +0,0 @@ -[[upgrading]] -= Upgrading Spring Boot -include::attributes.adoc[] - -Instructions for how to upgrade from earlier versions of Spring Boot are provided on the project {github-wiki}[wiki]. -Follow the links in the {github-wiki}#release-notes[release notes] section to find the version that you want to upgrade to. - -Upgrading instructions are always the first item in the release notes. -If you are more than one release behind, please make sure that you also review the release notes of the versions that you jumped. - -include::upgrading/from-1x.adoc[] - -include::upgrading/to-feature.adoc[] - -include::upgrading/cli.adoc[] - -include::upgrading/whats-next.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/cli.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/cli.adoc deleted file mode 100644 index b6b3ea202640..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/cli.adoc +++ /dev/null @@ -1,5 +0,0 @@ -[[upgrading.cli]] -== Upgrading the Spring Boot CLI - -To upgrade an existing CLI installation, use the appropriate package manager command (for example, `brew upgrade`). -If you manually installed the CLI, follow the <>, remembering to update your `PATH` environment variable to remove any older references. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/from-1x.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/from-1x.adoc deleted file mode 100644 index e899fe09acc5..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/from-1x.adoc +++ /dev/null @@ -1,5 +0,0 @@ -[[upgrading.from-1x]] -== Upgrading From 1.x - -If you are upgrading from the `1.x` release of Spring Boot, check the {github-wiki}/Spring-Boot-2.0-Migration-Guide["`migration guide`" on the project wiki] that provides detailed upgrade instructions. -Check also the {github-wiki}["`release notes`"] for a list of "`new and noteworthy`" features for each release. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/to-feature.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/to-feature.adoc deleted file mode 100644 index c901c572e1d1..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/to-feature.adoc +++ /dev/null @@ -1,19 +0,0 @@ -[[upgrading.to-feature]] -== Upgrading to a New Feature Release - -When upgrading to a new feature release, some properties may have been renamed or removed. -Spring Boot provides a way to analyze your application's environment and print diagnostics at startup, but also temporarily migrate properties at runtime for you. -To enable that feature, add the following dependency to your project: - -[source,xml,indent=0,subs="verbatim"] ----- - - org.springframework.boot - spring-boot-properties-migrator - runtime - ----- - -WARNING: Properties that are added late to the environment, such as when using `@PropertySource`, will not be taken into account. - -NOTE: Once you finish the migration, please make sure to remove this module from your project's dependencies. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/whats-next.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/whats-next.adoc deleted file mode 100644 index ff0bd6e76855..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/whats-next.adoc +++ /dev/null @@ -1,5 +0,0 @@ -[[upgrading.whats-next]] -== What to Read Next -Once you've decided to upgrade your application, you can find detailed information regarding specific features in the rest of the document. - -Spring Boot's documentation is specific to that version, so any information that you find in here will contain the most up-to-date changes that are in that version. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using.adoc deleted file mode 100644 index 832abd9e8679..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using.adoc +++ /dev/null @@ -1,34 +0,0 @@ -[[using]] -= Developing with Spring Boot -include::attributes.adoc[] - - - -This section goes into more detail about how you should use Spring Boot. -It covers topics such as build systems, auto-configuration, and how to run your applications. -We also cover some Spring Boot best practices. -Although there is nothing particularly special about Spring Boot (it is just another library that you can consume), there are a few recommendations that, when followed, make your development process a little easier. - -If you are starting out with Spring Boot, you should probably read the _<>_ guide before diving into this section. - - - -include::using/build-systems.adoc[] - -include::using/structuring-your-code.adoc[] - -include::using/configuration-classes.adoc[] - -include::using/auto-configuration.adoc[] - -include::using/spring-beans-and-dependency-injection.adoc[] - -include::using/using-the-springbootapplication-annotation.adoc[] - -include::using/running-your-application.adoc[] - -include::using/devtools.adoc[] - -include::using/packaging-for-production.adoc[] - -include::using/whats-next.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/build-systems.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/build-systems.adoc deleted file mode 100644 index 34a5734244db..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/build-systems.adoc +++ /dev/null @@ -1,145 +0,0 @@ -[[using.build-systems]] -== Build Systems -It is strongly recommended that you choose a build system that supports <> and that can consume artifacts published to the "`Maven Central`" repository. -We would recommend that you choose Maven or Gradle. -It is possible to get Spring Boot to work with other build systems (Ant, for example), but they are not particularly well supported. - - - -[[using.build-systems.dependency-management]] -=== Dependency Management -Each release of Spring Boot provides a curated list of dependencies that it supports. -In practice, you do not need to provide a version for any of these dependencies in your build configuration, as Spring Boot manages that for you. -When you upgrade Spring Boot itself, these dependencies are upgraded as well in a consistent way. - -NOTE: You can still specify a version and override Spring Boot's recommendations if you need to do so. - -The curated list contains all the Spring modules that you can use with Spring Boot as well as a refined list of third party libraries. -The list is available as a standard Bills of Materials (`spring-boot-dependencies`) that can be used with both <> and <>. - -WARNING: Each release of Spring Boot is associated with a base version of the Spring Framework. -We **highly** recommend that you do not specify its version. - - - -[[using.build-systems.maven]] -=== Maven -To learn about using Spring Boot with Maven, see the documentation for Spring Boot's Maven plugin: - -* Reference ({spring-boot-maven-plugin-docs}[HTML] and {spring-boot-maven-plugin-pdfdocs}[PDF]) -* {spring-boot-maven-plugin-api}[API] - - - -[[using.build-systems.gradle]] -=== Gradle -To learn about using Spring Boot with Gradle, see the documentation for Spring Boot's Gradle plugin: - -* Reference ({spring-boot-gradle-plugin-docs}[HTML] and {spring-boot-gradle-plugin-pdfdocs}[PDF]) -* {spring-boot-gradle-plugin-api}[API] - - - -[[using.build-systems.ant]] -=== Ant -It is possible to build a Spring Boot project using Apache Ant+Ivy. -The `spring-boot-antlib` "`AntLib`" module is also available to help Ant create executable jars. - -To declare dependencies, a typical `ivy.xml` file looks something like the following example: - -[source,xml,indent=0,subs="verbatim"] ----- - - - - - - - - - - ----- - -A typical `build.xml` looks like the following example: - -[source,xml,indent=0,subs="verbatim,attributes"] ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ----- - -TIP: If you do not want to use the `spring-boot-antlib` module, see the _<>_ "`How-to`" . - - - -[[using.build-systems.starters]] -=== Starters -Starters are a set of convenient dependency descriptors that you can include in your application. -You get a one-stop shop for all the Spring and related technologies that you need without having to hunt through sample code and copy-paste loads of dependency descriptors. -For example, if you want to get started using Spring and JPA for database access, include the `spring-boot-starter-data-jpa` dependency in your project. - -The starters contain a lot of the dependencies that you need to get a project up and running quickly and with a consistent, supported set of managed transitive dependencies. - -.What is in a name -**** -All **official** starters follow a similar naming pattern; `+spring-boot-starter-*+`, where `+*+` is a particular type of application. -This naming structure is intended to help when you need to find a starter. -The Maven integration in many IDEs lets you search dependencies by name. -For example, with the appropriate Eclipse or Spring Tools plugin installed, you can press `ctrl-space` in the POM editor and type "`spring-boot-starter`" for a complete list. - -As explained in the "`<>`" section, third party starters should not start with `spring-boot`, as it is reserved for official Spring Boot artifacts. -Rather, a third-party starter typically starts with the name of the project. -For example, a third-party starter project called `thirdpartyproject` would typically be named `thirdpartyproject-spring-boot-starter`. -**** - -The following application starters are provided by Spring Boot under the `org.springframework.boot` group: - -.Spring Boot application starters -include::starters/application-starters.adoc[] - -In addition to the application starters, the following starters can be used to add _<>_ features: - -.Spring Boot production starters -include::starters/production-starters.adoc[] - -Finally, Spring Boot also includes the following starters that can be used if you want to exclude or swap specific technical facets: - -.Spring Boot technical starters -include::starters/technical-starters.adoc[] - -To learn how to swap technical facets, please see the how-to documentation for <> and <>. - -TIP: For a list of additional community contributed starters, see the {spring-boot-latest-code}/spring-boot-project/spring-boot-starters/README.adoc[README file] in the `spring-boot-starters` module on GitHub. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/packaging-for-production.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/packaging-for-production.adoc deleted file mode 100644 index 2adc987456e9..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/packaging-for-production.adoc +++ /dev/null @@ -1,7 +0,0 @@ -[[using.packaging-for-production]] -== Packaging Your Application for Production -Executable jars can be used for production deployment. -As they are self-contained, they are also ideally suited for cloud-based deployment. - -For additional "`production ready`" features, such as health, auditing, and metric REST or JMX end-points, consider adding `spring-boot-actuator`. -See _<>_ for details. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/structuring-your-code.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/structuring-your-code.adoc deleted file mode 100644 index 896651f20550..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/structuring-your-code.adoc +++ /dev/null @@ -1,51 +0,0 @@ -[[using.structuring-your-code]] -== Structuring Your Code -Spring Boot does not require any specific code layout to work. -However, there are some best practices that help. - - - -[[using.structuring-your-code.using-the-default-package]] -=== Using the "`default`" Package -When a class does not include a `package` declaration, it is considered to be in the "`default package`". -The use of the "`default package`" is generally discouraged and should be avoided. -It can cause particular problems for Spring Boot applications that use the `@ComponentScan`, `@ConfigurationPropertiesScan`, `@EntityScan`, or `@SpringBootApplication` annotations, since every class from every jar is read. - -TIP: We recommend that you follow Java's recommended package naming conventions and use a reversed domain name (for example, `com.example.project`). - - - -[[using.structuring-your-code.locating-the-main-class]] -=== Locating the Main Application Class -We generally recommend that you locate your main application class in a root package above other classes. -The <> is often placed on your main class, and it implicitly defines a base "`search package`" for certain items. -For example, if you are writing a JPA application, the package of the `@SpringBootApplication` annotated class is used to search for `@Entity` items. -Using a root package also allows component scan to apply only on your project. - -TIP: If you do not want to use `@SpringBootApplication`, the `@EnableAutoConfiguration` and `@ComponentScan` annotations that it imports defines that behavior so you can also use those instead. - -The following listing shows a typical layout: - -[indent=0] ----- - com - +- example - +- myapplication - +- MyApplication.java - | - +- customer - | +- Customer.java - | +- CustomerController.java - | +- CustomerService.java - | +- CustomerRepository.java - | - +- order - +- Order.java - +- OrderController.java - +- OrderService.java - +- OrderRepository.java ----- - -The `MyApplication.java` file would declare the `main` method, along with the basic `@SpringBootApplication`, as follows: - -include::code:MyApplication[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/using-the-springbootapplication-annotation.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/using-the-springbootapplication-annotation.adoc deleted file mode 100644 index 04695f1b6c74..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/using-the-springbootapplication-annotation.adoc +++ /dev/null @@ -1,23 +0,0 @@ -[[using.using-the-springbootapplication-annotation]] -== Using the @SpringBootApplication Annotation -Many Spring Boot developers like their apps to use auto-configuration, component scan and be able to define extra configuration on their "application class". -A single `@SpringBootApplication` annotation can be used to enable those three features, that is: - -* `@EnableAutoConfiguration`: enable <> -* `@ComponentScan`: enable `@Component` scan on the package where the application is located (see <>) -* `@SpringBootConfiguration`: enable registration of extra beans in the context or the import of additional configuration classes. -An alternative to Spring's standard `@Configuration` that aids <> in your integration tests. - -include::code:springapplication/MyApplication[] - -NOTE: `@SpringBootApplication` also provides aliases to customize the attributes of `@EnableAutoConfiguration` and `@ComponentScan`. - -[NOTE] -==== -None of these features are mandatory and you may choose to replace this single annotation by any of the features that it enables. -For instance, you may not want to use component scan or configuration properties scan in your application: - -include::code:individualannotations/MyApplication[] - -In this example, `MyApplication` is just like any other Spring Boot application except that `@Component`-annotated classes and `@ConfigurationProperties`-annotated classes are not detected automatically and the user-defined beans are imported explicitly (see `@Import`). -==== diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/whats-next.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/whats-next.adoc deleted file mode 100644 index 6aaf2da0ba21..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/whats-next.adoc +++ /dev/null @@ -1,4 +0,0 @@ -[[using.whats-next]] -== What to Read Next -You should now understand how you can use Spring Boot and some best practices that you should follow. -You can now go on to learn about specific _<>_ in depth, or you could skip ahead and read about the "`<>`" aspects of Spring Boot. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web.adoc deleted file mode 100644 index 0bf2c5be459d..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web.adoc +++ /dev/null @@ -1,26 +0,0 @@ -[[web]] -= Web -include::attributes.adoc[] - -Spring Boot is well suited for web application development. -You can create a self-contained HTTP server by using embedded Tomcat, Jetty, Undertow, or Netty. -Most web applications use the `spring-boot-starter-web` module to get up and running quickly. -You can also choose to build reactive web applications by using the `spring-boot-starter-webflux` module. - -If you have not yet developed a Spring Boot web application, you can follow the "Hello World!" example in the _<>_ section. - -include::web/servlet.adoc[] - -include::web/reactive.adoc[] - -include::web/graceful-shutdown.adoc[] - -include::web/spring-security.adoc[] - -include::web/spring-session.adoc[] - -include::web/spring-graphql.adoc[] - -include::web/spring-hateoas.adoc[] - -include::web/whats-next.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/graceful-shutdown.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/graceful-shutdown.adoc deleted file mode 100644 index fce4f166aa48..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/graceful-shutdown.adoc +++ /dev/null @@ -1,36 +0,0 @@ -[[web.graceful-shutdown]] -== Graceful Shutdown -Graceful shutdown is supported with all four embedded web servers (Jetty, Reactor Netty, Tomcat, and Undertow) and with both reactive and servlet-based web applications. -It occurs as part of closing the application context and is performed in the earliest phase of stopping `SmartLifecycle` beans. -This stop processing uses a timeout which provides a grace period during which existing requests will be allowed to complete but no new requests will be permitted. - -The exact way in which new requests are not permitted varies depending on the web server that is being used. -Implementations may stop accepting requests at the network layer, or they may return a response with a specific HTTP status code or HTTP header. -The use of persistent connections can also change the way that requests stop being accepted. - -TIP: To learn about more the specific method used with your web server, see the `shutDownGracefully` javadoc for {spring-boot-module-api}/web/embedded/tomcat/TomcatWebServer.html#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[TomcatWebServer], {spring-boot-module-api}/web/embedded/netty/NettyWebServer.html#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[NettyWebServer], {spring-boot-module-api}/web/embedded/jetty/JettyWebServer.html#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[JettyWebServer] or {spring-boot-module-api}/web/embedded/undertow/UndertowWebServer.html#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[UndertowWebServer]. - -Jetty, Reactor Netty, and Tomcat will stop accepting new requests at the network layer. -Undertow will accept new connections but respond immediately with a service unavailable (503) response. - -NOTE: Graceful shutdown with Tomcat requires Tomcat 9.0.33 or later. - -To enable graceful shutdown, configure the configprop:server.shutdown[] property, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- -server: - shutdown: "graceful" ----- - -To configure the timeout period, configure the configprop:spring.lifecycle.timeout-per-shutdown-phase[] property, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- -spring: - lifecycle: - timeout-per-shutdown-phase: "20s" ----- - -IMPORTANT: Using graceful shutdown with your IDE may not work properly if it does not send a proper `SIGTERM` signal. -See the documentation of your IDE for more details. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/reactive.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/reactive.adoc deleted file mode 100644 index 33c52b1d743c..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/reactive.adoc +++ /dev/null @@ -1,314 +0,0 @@ -[[web.reactive]] -== Reactive Web Applications -Spring Boot simplifies development of reactive web applications by providing auto-configuration for Spring Webflux. - -[[web.reactive.webflux]] -=== The "`Spring WebFlux Framework`" -Spring WebFlux is the new reactive web framework introduced in Spring Framework 5.0. -Unlike Spring MVC, it does not require the servlet API, is fully asynchronous and non-blocking, and implements the https://www.reactive-streams.org/[Reactive Streams] specification through https://projectreactor.io/[the Reactor project]. - -Spring WebFlux comes in two flavors: functional and annotation-based. -The annotation-based one is quite close to the Spring MVC model, as shown in the following example: - -include::code:MyRestController[] - -WebFlux is part of the Spring Framework and detailed information is available in its {spring-framework-docs}/web/webflux.html[reference documentation]. - -"`WebFlux.fn`", the functional variant, separates the routing configuration from the actual handling of the requests, as shown in the following example: - -include::code:MyRoutingConfiguration[] - -include::code:MyUserHandler[] - -"`WebFlux.fn`" is part of the Spring Framework and detailed information is available in its {spring-framework-docs}/web/webflux-functional.html[reference documentation]. - -TIP: You can define as many `RouterFunction` beans as you like to modularize the definition of the router. -Beans can be ordered if you need to apply a precedence. - -To get started, add the `spring-boot-starter-webflux` module to your application. - -NOTE: Adding both `spring-boot-starter-web` and `spring-boot-starter-webflux` modules in your application results in Spring Boot auto-configuring Spring MVC, not WebFlux. -This behavior has been chosen because many Spring developers add `spring-boot-starter-webflux` to their Spring MVC application to use the reactive `WebClient`. -You can still enforce your choice by setting the chosen application type to `SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE)`. - - - -[[web.reactive.webflux.auto-configuration]] -==== Spring WebFlux Auto-configuration -Spring Boot provides auto-configuration for Spring WebFlux that works well with most applications. - -The auto-configuration adds the following features on top of Spring's defaults: - -* Configuring codecs for `HttpMessageReader` and `HttpMessageWriter` instances (described <>). -* Support for serving static resources, including support for WebJars (described <>). - -If you want to keep Spring Boot WebFlux features and you want to add additional {spring-framework-docs}/web/webflux/config.html[WebFlux configuration], you can add your own `@Configuration` class of type `WebFluxConfigurer` but *without* `@EnableWebFlux`. - -If you want to take complete control of Spring WebFlux, you can add your own `@Configuration` annotated with `@EnableWebFlux`. - - - -[[web.reactive.webflux.conversion-service]] -==== Spring WebFlux Conversion Service -If you want to customize the `ConversionService` used by Spring WebFlux, you can provide a `WebFluxConfigurer` bean with an `addFormatters` method. - -Conversion can also be customized using the `spring.webflux.format.*` configuration properties. -When not configured, the following defaults are used: - -|=== -|Property |`DateTimeFormatter` - -|configprop:spring.webflux.format.date[] -|`ofLocalizedDate(FormatStyle.SHORT)` - -|configprop:spring.webflux.format.time[] -|`ofLocalizedTime(FormatStyle.SHORT)` - -|configprop:spring.webflux.format.date-time[] -|`ofLocalizedDateTime(FormatStyle.SHORT)` -|=== - - - -[[web.reactive.webflux.httpcodecs]] -==== HTTP Codecs with HttpMessageReaders and HttpMessageWriters -Spring WebFlux uses the `HttpMessageReader` and `HttpMessageWriter` interfaces to convert HTTP requests and responses. -They are configured with `CodecConfigurer` to have sensible defaults by looking at the libraries available in your classpath. - -Spring Boot provides dedicated configuration properties for codecs, `+spring.codec.*+`. -It also applies further customization by using `CodecCustomizer` instances. -For example, `+spring.jackson.*+` configuration keys are applied to the Jackson codec. - -If you need to add or customize codecs, you can create a custom `CodecCustomizer` component, as shown in the following example: - -include::code:MyCodecsConfiguration[] - -You can also leverage <>. - - - -[[web.reactive.webflux.static-content]] -==== Static Content -By default, Spring Boot serves static content from a directory called `/static` (or `/public` or `/resources` or `/META-INF/resources`) in the classpath. -It uses the `ResourceWebHandler` from Spring WebFlux so that you can modify that behavior by adding your own `WebFluxConfigurer` and overriding the `addResourceHandlers` method. - -By default, resources are mapped on `+/**+`, but you can tune that by setting the configprop:spring.webflux.static-path-pattern[] property. -For instance, relocating all resources to `/resources/**` can be achieved as follows: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - webflux: - static-path-pattern: "/resources/**" ----- - -You can also customize the static resource locations by using `spring.web.resources.static-locations`. -Doing so replaces the default values with a list of directory locations. -If you do so, the default welcome page detection switches to your custom locations. -So, if there is an `index.html` in any of your locations on startup, it is the home page of the application. - -In addition to the "`standard`" static resource locations listed earlier, a special case is made for https://www.webjars.org/[Webjars content]. -By default, any resources with a path in `+/webjars/**+` are served from jar files if they are packaged in the Webjars format. -The path can be customized with the configprop:spring.webflux.webjars-path-pattern[] property. - -TIP: Spring WebFlux applications do not strictly depend on the servlet API, so they cannot be deployed as war files and do not use the `src/main/webapp` directory. - - - -[[web.reactive.webflux.welcome-page]] -==== Welcome Page -Spring Boot supports both static and templated welcome pages. -It first looks for an `index.html` file in the configured static content locations. -If one is not found, it then looks for an `index` template. -If either is found, it is automatically used as the welcome page of the application. - - - -[[web.reactive.webflux.template-engines]] -==== Template Engines -As well as REST web services, you can also use Spring WebFlux to serve dynamic HTML content. -Spring WebFlux supports a variety of templating technologies, including Thymeleaf, FreeMarker, and Mustache. - -Spring Boot includes auto-configuration support for the following templating engines: - -* https://freemarker.apache.org/docs/[FreeMarker] -* https://www.thymeleaf.org[Thymeleaf] -* https://mustache.github.io/[Mustache] - -When you use one of these templating engines with the default configuration, your templates are picked up automatically from `src/main/resources/templates`. - - - -[[web.reactive.webflux.error-handling]] -==== Error Handling -Spring Boot provides a `WebExceptionHandler` that handles all errors in a sensible way. -Its position in the processing order is immediately before the handlers provided by WebFlux, which are considered last. -For machine clients, it produces a JSON response with details of the error, the HTTP status, and the exception message. -For browser clients, there is a "`whitelabel`" error handler that renders the same data in HTML format. -You can also provide your own HTML templates to display errors (see the <>). - -Before customizing error handling in Spring Boot directly, you can leverage the {spring-framework-docs}/web/webflux/ann-rest-exceptions.html[RFC 7807 Problem Details] support in Spring WebFlux. -Spring WebFlux can produce custom error messages with the `application/problem+json` media type, like: - -[source,json,indent=0,subs="verbatim"] ----- -{ - "type": "https://example.org/problems/unknown-project", - "title": "Unknown project", - "status": 404, - "detail": "No project found for id 'spring-unknown'", - "instance": "/projects/spring-unknown" -} ----- - -This support can be enabled by setting configprop:spring.webflux.problemdetails.enabled[] to `true`. - - -The first step to customizing this feature often involves using the existing mechanism but replacing or augmenting the error contents. -For that, you can add a bean of type `ErrorAttributes`. - -To change the error handling behavior, you can implement `ErrorWebExceptionHandler` and register a bean definition of that type. -Because an `ErrorWebExceptionHandler` is quite low-level, Spring Boot also provides a convenient `AbstractErrorWebExceptionHandler` to let you handle errors in a WebFlux functional way, as shown in the following example: - -include::code:MyErrorWebExceptionHandler[] - -For a more complete picture, you can also subclass `DefaultErrorWebExceptionHandler` directly and override specific methods. - -In some cases, errors handled at the controller or handler function level are not recorded by the <>. -Applications can ensure that such exceptions are recorded with the request metrics by setting the handled exception as a request attribute: - -include::code:MyExceptionHandlingController[] - - - -[[web.reactive.webflux.error-handling.error-pages]] -===== Custom Error Pages -If you want to display a custom HTML error page for a given status code, you can add views that resolve from `error/*`, for example by adding files to a `/error` directory. -Error pages can either be static HTML (that is, added under any of the static resource directories) or built with templates. -The name of the file should be the exact status code, a status code series mask, or `error` for a default if nothing else matches. -Note that the path to the default error view is `error/error`, whereas with Spring MVC the default error view is `error`. - -For example, to map `404` to a static HTML file, your directory structure would be as follows: - -[source,indent=0,subs="verbatim"] ----- - src/ - +- main/ - +- java/ - | + - +- resources/ - +- public/ - +- error/ - | +- 404.html - +- ----- - -To map all `5xx` errors by using a Mustache template, your directory structure would be as follows: - -[source,indent=0,subs="verbatim"] ----- - src/ - +- main/ - +- java/ - | + - +- resources/ - +- templates/ - +- error/ - | +- 5xx.mustache - +- ----- - - - -[[web.reactive.webflux.web-filters]] -==== Web Filters -Spring WebFlux provides a `WebFilter` interface that can be implemented to filter HTTP request-response exchanges. -`WebFilter` beans found in the application context will be automatically used to filter each exchange. - -Where the order of the filters is important they can implement `Ordered` or be annotated with `@Order`. -Spring Boot auto-configuration may configure web filters for you. -When it does so, the orders shown in the following table will be used: - -|=== -| Web Filter | Order - -| `ServerHttpObservationFilter` (Micrometer Observability) -| `Ordered.HIGHEST_PRECEDENCE + 1` - -| `WebFilterChainProxy` (Spring Security) -| `-100` - -| `HttpExchangesWebFilter` -| `Ordered.LOWEST_PRECEDENCE - 10` -|=== - - - -[[web.reactive.reactive-server]] -=== Embedded Reactive Server Support -Spring Boot includes support for the following embedded reactive web servers: Reactor Netty, Tomcat, Jetty, and Undertow. -Most developers use the appropriate “Starter” to obtain a fully configured instance. -By default, the embedded server listens for HTTP requests on port 8080. - - - -[[web.reactive.reactive-server.customizing]] -==== Customizing Reactive Servers -Common reactive web server settings can be configured by using Spring `Environment` properties. -Usually, you would define the properties in your `application.properties` or `application.yaml` file. - -Common server settings include: - -* Network settings: Listen port for incoming HTTP requests (`server.port`), interface address to bind to `server.address`, and so on. -* Error management: Location of the error page (`server.error.path`) and so on. -* <> -* <> - -Spring Boot tries as much as possible to expose common settings, but this is not always possible. -For those cases, dedicated namespaces such as `server.netty.*` offer server-specific customizations. - -TIP: See the {spring-boot-autoconfigure-module-code}/web/ServerProperties.java[`ServerProperties`] class for a complete list. - - - -[[web.reactive.reactive-server.customizing.programmatic]] -===== Programmatic Customization -If you need to programmatically configure your reactive web server, you can register a Spring bean that implements the `WebServerFactoryCustomizer` interface. -`WebServerFactoryCustomizer` provides access to the `ConfigurableReactiveWebServerFactory`, which includes numerous customization setter methods. -The following example shows programmatically setting the port: - -include::code:MyWebServerFactoryCustomizer[] - -`JettyReactiveWebServerFactory`, `NettyReactiveWebServerFactory`, `TomcatReactiveWebServerFactory`, and `UndertowServletWebServerFactory` are dedicated variants of `ConfigurableReactiveWebServerFactory` that have additional customization setter methods for Jetty, Reactor Netty, Tomcat, and Undertow respectively. -The following example shows how to customize `NettyReactiveWebServerFactory` that provides access to Reactor Netty-specific configuration options: - -include::code:MyNettyWebServerFactoryCustomizer[] - - - -[[web.reactive.reactive-server.customizing.direct]] -===== Customizing ConfigurableReactiveWebServerFactory Directly -For more advanced use cases that require you to extend from `ReactiveWebServerFactory`, you can expose a bean of such type yourself. - -Setters are provided for many configuration options. -Several protected method "`hooks`" are also provided should you need to do something more exotic. -See the {spring-boot-module-api}/web/reactive/server/ConfigurableReactiveWebServerFactory.html[source code documentation] for details. - -NOTE: Auto-configured customizers are still applied on your custom factory, so use that option carefully. - - - -[[web.reactive.reactive-server-resources-configuration]] -=== Reactive Server Resources Configuration -When auto-configuring a Reactor Netty or Jetty server, Spring Boot will create specific beans that will provide HTTP resources to the server instance: `ReactorResourceFactory` or `JettyResourceFactory`. - -By default, those resources will be also shared with the Reactor Netty and Jetty clients for optimal performances, given: - -* the same technology is used for server and client -* the client instance is built using the `WebClient.Builder` bean auto-configured by Spring Boot - -Developers can override the resource configuration for Jetty and Reactor Netty by providing a custom `ReactorResourceFactory` or `JettyResourceFactory` bean - this will be applied to both clients and servers. - -You can learn more about the resource configuration on the client side in the <>. - - diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-security.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-security.adoc deleted file mode 100644 index 2fc187ddba33..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-security.adoc +++ /dev/null @@ -1,384 +0,0 @@ -[[web.security]] -== Spring Security -If {spring-security}[Spring Security] is on the classpath, then web applications are secured by default. -Spring Boot relies on Spring Security’s content-negotiation strategy to determine whether to use `httpBasic` or `formLogin`. -To add method-level security to a web application, you can also add `@EnableGlobalMethodSecurity` with your desired settings. -Additional information can be found in the {spring-security-docs}/servlet/authorization/method-security.html[Spring Security Reference Guide]. - -The default `UserDetailsService` has a single user. -The user name is `user`, and the password is random and is printed at WARN level when the application starts, as shown in the following example: - -[indent=0] ----- - Using generated security password: 78fa095d-3f4c-48b1-ad50-e24c31d5cf35 - - This generated password is for development use only. Your security configuration must be updated before running your application in production. ----- - -NOTE: If you fine-tune your logging configuration, ensure that the `org.springframework.boot.autoconfigure.security` category is set to log `WARN`-level messages. -Otherwise, the default password is not printed. - -You can change the username and password by providing a `spring.security.user.name` and `spring.security.user.password`. - -The basic features you get by default in a web application are: - -* A `UserDetailsService` (or `ReactiveUserDetailsService` in case of a WebFlux application) bean with in-memory store and a single user with a generated password (see {spring-boot-module-api}/autoconfigure/security/SecurityProperties.User.html[`SecurityProperties.User`] for the properties of the user). -* Form-based login or HTTP Basic security (depending on the `Accept` header in the request) for the entire application (including actuator endpoints if actuator is on the classpath). -* A `DefaultAuthenticationEventPublisher` for publishing authentication events. - -You can provide a different `AuthenticationEventPublisher` by adding a bean for it. - - - -[[web.security.spring-mvc]] -=== MVC Security -The default security configuration is implemented in `SecurityAutoConfiguration` and `UserDetailsServiceAutoConfiguration`. -`SecurityAutoConfiguration` imports `SpringBootWebSecurityConfiguration` for web security and `UserDetailsServiceAutoConfiguration` configures authentication, which is also relevant in non-web applications. -To switch off the default web application security configuration completely or to combine multiple Spring Security components such as OAuth2 Client and Resource Server, add a bean of type `SecurityFilterChain` (doing so does not disable the `UserDetailsService` configuration or Actuator's security). - -To also switch off the `UserDetailsService` configuration, you can add a bean of type `UserDetailsService`, `AuthenticationProvider`, or `AuthenticationManager`. - -Access rules can be overridden by adding a custom `SecurityFilterChain` bean. -Spring Boot provides convenience methods that can be used to override access rules for actuator endpoints and static resources. -`EndpointRequest` can be used to create a `RequestMatcher` that is based on the configprop:management.endpoints.web.base-path[] property. -`PathRequest` can be used to create a `RequestMatcher` for resources in commonly used locations. - - - -[[web.security.spring-webflux]] -=== WebFlux Security -Similar to Spring MVC applications, you can secure your WebFlux applications by adding the `spring-boot-starter-security` dependency. -The default security configuration is implemented in `ReactiveSecurityAutoConfiguration` and `UserDetailsServiceAutoConfiguration`. -`ReactiveSecurityAutoConfiguration` imports `WebFluxSecurityConfiguration` for web security and `UserDetailsServiceAutoConfiguration` configures authentication, which is also relevant in non-web applications. -To switch off the default web application security configuration completely, you can add a bean of type `WebFilterChainProxy` (doing so does not disable the `UserDetailsService` configuration or Actuator's security). - -To also switch off the `UserDetailsService` configuration, you can add a bean of type `ReactiveUserDetailsService` or `ReactiveAuthenticationManager`. - -Access rules and the use of multiple Spring Security components such as OAuth 2 Client and Resource Server can be configured by adding a custom `SecurityWebFilterChain` bean. -Spring Boot provides convenience methods that can be used to override access rules for actuator endpoints and static resources. -`EndpointRequest` can be used to create a `ServerWebExchangeMatcher` that is based on the configprop:management.endpoints.web.base-path[] property. - -`PathRequest` can be used to create a `ServerWebExchangeMatcher` for resources in commonly used locations. - -For example, you can customize your security configuration by adding something like: - -include::code:MyWebFluxSecurityConfiguration[] - - - -[[web.security.oauth2]] -=== OAuth2 -https://oauth.net/2/[OAuth2] is a widely used authorization framework that is supported by Spring. - - - -[[web.security.oauth2.client]] -==== Client -If you have `spring-security-oauth2-client` on your classpath, you can take advantage of some auto-configuration to set up OAuth2/Open ID Connect clients. -This configuration makes use of the properties under `OAuth2ClientProperties`. -The same properties are applicable to both servlet and reactive applications. - -You can register multiple OAuth2 clients and providers under the `spring.security.oauth2.client` prefix, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - security: - oauth2: - client: - registration: - my-login-client: - client-id: "abcd" - client-secret: "password" - client-name: "Client for OpenID Connect" - provider: "my-oauth-provider" - scope: "openid,profile,email,phone,address" - redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" - client-authentication-method: "client_secret_basic" - authorization-grant-type: "authorization_code" - - my-client-1: - client-id: "abcd" - client-secret: "password" - client-name: "Client for user scope" - provider: "my-oauth-provider" - scope: "user" - redirect-uri: "{baseUrl}/authorized/user" - client-authentication-method: "client_secret_basic" - authorization-grant-type: "authorization_code" - - my-client-2: - client-id: "abcd" - client-secret: "password" - client-name: "Client for email scope" - provider: "my-oauth-provider" - scope: "email" - redirect-uri: "{baseUrl}/authorized/email" - client-authentication-method: "client_secret_basic" - authorization-grant-type: "authorization_code" - - provider: - my-oauth-provider: - authorization-uri: "https://my-auth-server.com/oauth2/authorize" - token-uri: "https://my-auth-server.com/oauth2/token" - user-info-uri: "https://my-auth-server.com/userinfo" - user-info-authentication-method: "header" - jwk-set-uri: "https://my-auth-server.com/oauth2/jwks" - user-name-attribute: "name" ----- - -For OpenID Connect providers that support https://openid.net/specs/openid-connect-discovery-1_0.html[OpenID Connect discovery], the configuration can be further simplified. -The provider needs to be configured with an `issuer-uri` which is the URI that it asserts as its Issuer Identifier. -For example, if the `issuer-uri` provided is "https://example.com", then an "OpenID Provider Configuration Request" will be made to "https://example.com/.well-known/openid-configuration". -The result is expected to be an "OpenID Provider Configuration Response". -The following example shows how an OpenID Connect Provider can be configured with the `issuer-uri`: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - security: - oauth2: - client: - provider: - oidc-provider: - issuer-uri: "https://dev-123456.oktapreview.com/oauth2/default/" ----- - -By default, Spring Security's `OAuth2LoginAuthenticationFilter` only processes URLs matching `/login/oauth2/code/*`. -If you want to customize the `redirect-uri` to use a different pattern, you need to provide configuration to process that custom pattern. -For example, for servlet applications, you can add your own `SecurityFilterChain` that resembles the following: - -include::code:MyOAuthClientConfiguration[] - -TIP: Spring Boot auto-configures an `InMemoryOAuth2AuthorizedClientService` which is used by Spring Security for the management of client registrations. -The `InMemoryOAuth2AuthorizedClientService` has limited capabilities and we recommend using it only for development environments. -For production environments, consider using a `JdbcOAuth2AuthorizedClientService` or creating your own implementation of `OAuth2AuthorizedClientService`. - - - -[[web.security.oauth2.client.common-providers]] -===== OAuth2 Client Registration for Common Providers -For common OAuth2 and OpenID providers, including Google, Github, Facebook, and Okta, we provide a set of provider defaults (`google`, `github`, `facebook`, and `okta`, respectively). - -If you do not need to customize these providers, you can set the `provider` attribute to the one for which you need to infer defaults. -Also, if the key for the client registration matches a default supported provider, Spring Boot infers that as well. - -In other words, the two configurations in the following example use the Google provider: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - security: - oauth2: - client: - registration: - my-client: - client-id: "abcd" - client-secret: "password" - provider: "google" - google: - client-id: "abcd" - client-secret: "password" ----- - - - -[[web.security.oauth2.server]] -==== Resource Server -If you have `spring-security-oauth2-resource-server` on your classpath, Spring Boot can set up an OAuth2 Resource Server. -For JWT configuration, a JWK Set URI or OIDC Issuer URI needs to be specified, as shown in the following examples: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - security: - oauth2: - resourceserver: - jwt: - jwk-set-uri: "https://example.com/oauth2/default/v1/keys" ----- - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - security: - oauth2: - resourceserver: - jwt: - issuer-uri: "https://dev-123456.oktapreview.com/oauth2/default/" ----- - -NOTE: If the authorization server does not support a JWK Set URI, you can configure the resource server with the Public Key used for verifying the signature of the JWT. -This can be done using the configprop:spring.security.oauth2.resourceserver.jwt.public-key-location[] property, where the value needs to point to a file containing the public key in the PEM-encoded x509 format. - -The configprop:spring.security.oauth2.resourceserver.jwt.audiences[] property can be used to specify the expected values of the aud claim in JWTs. -For example, to require JWTs to contain an aud claim with the value `my-audience`: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - security: - oauth2: - resourceserver: - jwt: - audiences: - - "my-audience" ----- - -The same properties are applicable for both servlet and reactive applications. -Alternatively, you can define your own `JwtDecoder` bean for servlet applications or a `ReactiveJwtDecoder` for reactive applications. - -In cases where opaque tokens are used instead of JWTs, you can configure the following properties to validate tokens through introspection: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - security: - oauth2: - resourceserver: - opaquetoken: - introspection-uri: "https://example.com/check-token" - client-id: "my-client-id" - client-secret: "my-client-secret" ----- - -Again, the same properties are applicable for both servlet and reactive applications. -Alternatively, you can define your own `OpaqueTokenIntrospector` bean for servlet applications or a `ReactiveOpaqueTokenIntrospector` for reactive applications. - - - -[[web.security.oauth2.authorization-server]] -==== Authorization Server -If you have `spring-security-oauth2-authorization-server` on your classpath, you can take advantage of some auto-configuration to set up a Servlet-based OAuth2 Authorization Server. - -You can register multiple OAuth2 clients under the `spring.security.oauth2.authorizationserver.client` prefix, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - security: - oauth2: - authorizationserver: - client: - my-client-1: - registration: - client-id: "abcd" - client-secret: "{noop}secret1" - client-authentication-methods: - - "client_secret_basic" - authorization-grant-types: - - "authorization_code" - - "refresh_token" - redirect-uris: - - "https://my-client-1.com/login/oauth2/code/abcd" - - "https://my-client-1.com/authorized" - scopes: - - "openid" - - "profile" - - "email" - - "phone" - - "address" - require-authorization-consent: true - my-client-2: - registration: - client-id: "efgh" - client-secret: "{noop}secret2" - client-authentication-methods: - - "client_secret_jwt" - authorization-grant-types: - - "client_credentials" - scopes: - - "user.read" - - "user.write" - jwk-set-uri: "https://my-client-2.com/jwks" - token-endpoint-authentication-signing-algorithm: "RS256" ----- - -NOTE: The `client-secret` property must be in a format that can be matched by the configured `PasswordEncoder`. -The default instance of `PasswordEncoder` is created via `PasswordEncoderFactories.createDelegatingPasswordEncoder()`. - -The auto-configuration Spring Boot provides for Spring Authorization Server is designed for getting started quickly. -Most applications will require customization and will want to define several beans to override auto-configuration. - -The following components can be defined as beans to override auto-configuration specific to Spring Authorization Server: - -* `RegisteredClientRepository` -* `AuthorizationServerSettings` -* `SecurityFilterChain` -* `com.nimbusds.jose.jwk.source.JWKSource` -* `JwtDecoder` - -TIP: Spring Boot auto-configures an `InMemoryRegisteredClientRepository` which is used by Spring Authorization Server for the management of registered clients. -The `InMemoryRegisteredClientRepository` has limited capabilities and we recommend using it only for development environments. -For production environments, consider using a `JdbcRegisteredClientRepository` or creating your own implementation of `RegisteredClientRepository`. - -Additional information can be found in the {spring-authorization-server-docs}/getting-started.html[Getting Started] chapter of the {spring-authorization-server-docs}/index.html[Spring Authorization Server Reference Guide]. - - - -[[web.security.saml2]] -=== SAML 2.0 - - - -[[web.security.saml2.relying-party]] -==== Relying Party -If you have `spring-security-saml2-service-provider` on your classpath, you can take advantage of some auto-configuration to set up a SAML 2.0 Relying Party. -This configuration makes use of the properties under `Saml2RelyingPartyProperties`. - -A relying party registration represents a paired configuration between an Identity Provider, IDP, and a Service Provider, SP. -You can register multiple relying parties under the `spring.security.saml2.relyingparty` prefix, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - security: - saml2: - relyingparty: - registration: - my-relying-party1: - signing: - credentials: - - private-key-location: "path-to-private-key" - certificate-location: "path-to-certificate" - decryption: - credentials: - - private-key-location: "path-to-private-key" - certificate-location: "path-to-certificate" - singlelogout: - url: "https://myapp/logout/saml2/slo" - response-url: "https://remoteidp2.slo.url" - binding: "POST" - assertingparty: - verification: - credentials: - - certificate-location: "path-to-verification-cert" - entity-id: "remote-idp-entity-id1" - sso-url: "https://remoteidp1.sso.url" - - my-relying-party2: - signing: - credentials: - - private-key-location: "path-to-private-key" - certificate-location: "path-to-certificate" - decryption: - credentials: - - private-key-location: "path-to-private-key" - certificate-location: "path-to-certificate" - assertingparty: - verification: - credentials: - - certificate-location: "path-to-other-verification-cert" - entity-id: "remote-idp-entity-id2" - sso-url: "https://remoteidp2.sso.url" - singlelogout: - url: "https://remoteidp2.slo.url" - response-url: "https://myapp/logout/saml2/slo" - binding: "POST" ----- - -For SAML2 logout, by default, Spring Security's `Saml2LogoutRequestFilter` and `Saml2LogoutResponseFilter` only process URLs matching `/logout/saml2/slo`. -If you want to customize the `url` to which AP-initiated logout requests get sent to or the `response-url` to which an AP sends logout responses to, to use a different pattern, you need to provide configuration to process that custom pattern. -For example, for servlet applications, you can add your own `SecurityFilterChain` that resembles the following: - -include::code:MySamlRelyingPartyConfiguration[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/whats-next.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/whats-next.adoc deleted file mode 100644 index 94ddb160801d..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/whats-next.adoc +++ /dev/null @@ -1,5 +0,0 @@ -[[web.whats-next]] -== What to Read Next -You should now have a good understanding of how to develop web applications with Spring Boot. -The next few sections describe how Spring Boot integrates with various <>, <>, and other IO capabilities. -You can pick any of these based on your application's needs. diff --git a/spring-boot-project/spring-boot-docs/src/docs/dokkatoo/dokka-overview.md b/spring-boot-project/spring-boot-docs/src/docs/dokkatoo/dokka-overview.md new file mode 100644 index 000000000000..9b2f14d814e5 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/dokkatoo/dokka-overview.md @@ -0,0 +1,2 @@ +# All Modules +_See also the Java API documentation (Javadoc)._ diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/actuator/observability/preventingobservations/MyObservationPredicate.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/actuator/observability/preventingobservations/MyObservationPredicate.java new file mode 100644 index 000000000000..f065c8e2ca83 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/actuator/observability/preventingobservations/MyObservationPredicate.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.actuator.observability.preventingobservations; + +import io.micrometer.observation.Observation.Context; +import io.micrometer.observation.ObservationPredicate; + +import org.springframework.stereotype.Component; + +@Component +class MyObservationPredicate implements ObservationPredicate { + + @Override + public boolean test(String name, Context context) { + return !name.contains("denied"); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/data/sql/h2webconsole/springsecurity/DevProfileSecurityConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/data/sql/h2webconsole/springsecurity/DevProfileSecurityConfiguration.java index 22cef68bd47c..09d43d8f1dcb 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/data/sql/h2webconsole/springsecurity/DevProfileSecurityConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/data/sql/h2webconsole/springsecurity/DevProfileSecurityConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,8 @@ import org.springframework.core.annotation.Order; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig; import org.springframework.security.web.SecurityFilterChain; @Profile("dev") @@ -35,8 +37,8 @@ public class DevProfileSecurityConfiguration { SecurityFilterChain h2ConsoleSecurityFilterChain(HttpSecurity http) throws Exception { http.securityMatcher(PathRequest.toH2Console()); http.authorizeHttpRequests(yourCustomAuthorization()); - http.csrf((csrf) -> csrf.disable()); - http.headers((headers) -> headers.frameOptions((frame) -> frame.sameOrigin())); + http.csrf(CsrfConfigurer::disable); + http.headers((headers) -> headers.frameOptions(FrameOptionsConfig::sameOrigin)); return http.build(); } diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/data/sql/jdbcclient/MyBean.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/data/sql/jdbcclient/MyBean.java new file mode 100644 index 000000000000..4441070de213 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/data/sql/jdbcclient/MyBean.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.data.sql.jdbcclient; + +import org.springframework.jdbc.core.simple.JdbcClient; +import org.springframework.stereotype.Component; + +@Component +public class MyBean { + + private final JdbcClient jdbcClient; + + public MyBean(JdbcClient jdbcClient) { + this.jdbcClient = jdbcClient; + } + + public void doSomething() { + /* @chomp:line this.jdbcClient ... */ this.jdbcClient.sql("delete from customer").update(); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/deployment/cloud/cloudfoundry/bindingtoservices/MyBean.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/deployment/cloud/cloudfoundry/bindingtoservices/MyBean.java deleted file mode 100644 index cf625dc4d92d..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/deployment/cloud/cloudfoundry/bindingtoservices/MyBean.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.deployment.cloud.cloudfoundry.bindingtoservices; - -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.Environment; -import org.springframework.stereotype.Component; - -@Component -public class MyBean implements EnvironmentAware { - - @SuppressWarnings("unused") - private String instanceId; - - @Override - public void setEnvironment(Environment environment) { - this.instanceId = environment.getProperty("vcap.application.instance_id"); - } - - // ... - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/devtools/MyContainersConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/devtools/MyContainersConfiguration.java new file mode 100644 index 000000000000..ba44f9f2c340 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/devtools/MyContainersConfiguration.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.features.devservices.testcontainers.atdevelopmenttime.devtools; + +import org.testcontainers.containers.MongoDBContainer; + +import org.springframework.boot.devtools.restart.RestartScope; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.Bean; + +@TestConfiguration(proxyBeanMethods = false) +public class MyContainersConfiguration { + + @Bean + @RestartScope + @ServiceConnection + public MongoDBContainer mongoDbContainer() { + return new MongoDBContainer("mongo:5.0"); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/dynamicproperties/MyContainersConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/dynamicproperties/MyContainersConfiguration.java new file mode 100644 index 000000000000..d099931f286e --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/dynamicproperties/MyContainersConfiguration.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.features.devservices.testcontainers.atdevelopmenttime.dynamicproperties; + +import org.testcontainers.containers.MongoDBContainer; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.DynamicPropertyRegistry; + +@TestConfiguration(proxyBeanMethods = false) +public class MyContainersConfiguration { + + @Bean + public MongoDBContainer mongoDbContainer(DynamicPropertyRegistry properties) { + MongoDBContainer container = new MongoDBContainer("mongo:5.0"); + properties.add("spring.data.mongodb.host", container::getHost); + properties.add("spring.data.mongodb.port", container::getFirstMappedPort); + return container; + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/importingcontainerdeclarations/MyContainers.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/importingcontainerdeclarations/MyContainers.java similarity index 85% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/importingcontainerdeclarations/MyContainers.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/importingcontainerdeclarations/MyContainers.java index 5afd1d6b9d0b..2dd7c1eb8598 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/importingcontainerdeclarations/MyContainers.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/importingcontainerdeclarations/MyContainers.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testcontainers.atdevelopmenttime.importingcontainerdeclarations; +package org.springframework.boot.docs.features.devservices.testcontainers.atdevelopmenttime.importingcontainerdeclarations; import org.testcontainers.containers.MongoDBContainer; import org.testcontainers.containers.Neo4jContainer; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/importingcontainerdeclarations/MyContainersConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/importingcontainerdeclarations/MyContainersConfiguration.java new file mode 100644 index 000000000000..9178ae96467c --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/importingcontainerdeclarations/MyContainersConfiguration.java @@ -0,0 +1,26 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.features.devservices.testcontainers.atdevelopmenttime.importingcontainerdeclarations; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.testcontainers.context.ImportTestcontainers; + +@TestConfiguration(proxyBeanMethods = false) +@ImportTestcontainers(MyContainers.class) +public class MyContainersConfiguration { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/launch/MyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/launch/MyApplication.java new file mode 100644 index 000000000000..5e931bfb5d72 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/launch/MyApplication.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.features.devservices.testcontainers.atdevelopmenttime.launch; + +public class MyApplication { + + public static void main(String[] args) { + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/launch/TestMyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/launch/TestMyApplication.java new file mode 100644 index 000000000000..42c7a8ceaf42 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/launch/TestMyApplication.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.features.devservices.testcontainers.atdevelopmenttime.launch; + +import org.springframework.boot.SpringApplication; + +public class TestMyApplication { + + public static void main(String[] args) { + SpringApplication.from(MyApplication::main).run(args); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/test/MyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/test/MyApplication.java new file mode 100644 index 000000000000..8b7e699121bc --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/test/MyApplication.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.features.devservices.testcontainers.atdevelopmenttime.test; + +public class MyApplication { + + public static void main(String[] args) { + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/test/MyContainersConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/test/MyContainersConfiguration.java new file mode 100644 index 000000000000..39117a15912f --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/test/MyContainersConfiguration.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.features.devservices.testcontainers.atdevelopmenttime.test; + +import org.testcontainers.containers.Neo4jContainer; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.Bean; + +@TestConfiguration(proxyBeanMethods = false) +public class MyContainersConfiguration { + + @Bean + @ServiceConnection + public Neo4jContainer neo4jContainer() { + return new Neo4jContainer<>("neo4j:5"); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/test/TestMyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/test/TestMyApplication.java new file mode 100644 index 000000000000..608c2d4c07e8 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/test/TestMyApplication.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.features.devservices.testcontainers.atdevelopmenttime.test; + +import org.springframework.boot.SpringApplication; + +public class TestMyApplication { + + public static void main(String[] args) { + SpringApplication.from(MyApplication::main).with(MyContainersConfiguration.class).run(args); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/logging/structured/customformat/MyCustomFormat.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/logging/structured/customformat/MyCustomFormat.java new file mode 100644 index 000000000000..e80a189a6b22 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/logging/structured/customformat/MyCustomFormat.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.features.logging.structured.customformat; + +import ch.qos.logback.classic.spi.ILoggingEvent; + +import org.springframework.boot.logging.structured.StructuredLogFormatter; + +class MyCustomFormat implements StructuredLogFormatter { + + @Override + public String format(ILoggingEvent event) { + return "time=" + event.getInstant() + " level=" + event.getLevel() + " message=" + event.getMessage() + "\n"; + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/devtools/MyContainersConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/devtools/MyContainersConfiguration.java deleted file mode 100644 index 541cdae30a86..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/devtools/MyContainersConfiguration.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testcontainers.atdevelopmenttime.devtools; - -import org.testcontainers.containers.MongoDBContainer; - -import org.springframework.boot.devtools.restart.RestartScope; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.context.annotation.Bean; - -@TestConfiguration(proxyBeanMethods = false) -public class MyContainersConfiguration { - - @Bean - @RestartScope - @ServiceConnection - public MongoDBContainer mongoDbContainer() { - return new MongoDBContainer("mongo:5.0"); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/dynamicproperties/MyContainersConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/dynamicproperties/MyContainersConfiguration.java deleted file mode 100644 index 143a28f6a843..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/dynamicproperties/MyContainersConfiguration.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testcontainers.atdevelopmenttime.dynamicproperties; - -import org.testcontainers.containers.MongoDBContainer; - -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.test.context.DynamicPropertyRegistry; - -@TestConfiguration(proxyBeanMethods = false) -public class MyContainersConfiguration { - - @Bean - public MongoDBContainer mongoDbContainer(DynamicPropertyRegistry properties) { - MongoDBContainer container = new MongoDBContainer("mongo:5.0"); - properties.add("spring.data.mongodb.host", container::getHost); - properties.add("spring.data.mongodb.port", container::getFirstMappedPort); - return container; - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/importingcontainerdeclarations/MyContainersConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/importingcontainerdeclarations/MyContainersConfiguration.java deleted file mode 100644 index 1e6efc47b1b2..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/importingcontainerdeclarations/MyContainersConfiguration.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testcontainers.atdevelopmenttime.importingcontainerdeclarations; - -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.boot.testcontainers.context.ImportTestcontainers; - -@TestConfiguration(proxyBeanMethods = false) -@ImportTestcontainers(MyContainers.class) -public class MyContainersConfiguration { - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/launch/MyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/launch/MyApplication.java deleted file mode 100644 index 1c44bb1a0270..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/launch/MyApplication.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testcontainers.atdevelopmenttime.launch; - -public class MyApplication { - - public static void main(String[] args) { - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/launch/TestMyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/launch/TestMyApplication.java deleted file mode 100644 index 54ca0ebee36e..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/launch/TestMyApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testcontainers.atdevelopmenttime.launch; - -import org.springframework.boot.SpringApplication; - -public class TestMyApplication { - - public static void main(String[] args) { - SpringApplication.from(MyApplication::main).run(args); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/test/MyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/test/MyApplication.java deleted file mode 100644 index 08c049ab5044..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/test/MyApplication.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testcontainers.atdevelopmenttime.test; - -public class MyApplication { - - public static void main(String[] args) { - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/test/MyContainersConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/test/MyContainersConfiguration.java deleted file mode 100644 index 11a9e78fb474..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/test/MyContainersConfiguration.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testcontainers.atdevelopmenttime.test; - -import org.testcontainers.containers.Neo4jContainer; - -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.context.annotation.Bean; - -@TestConfiguration(proxyBeanMethods = false) -public class MyContainersConfiguration { - - @Bean - @ServiceConnection - public Neo4jContainer neo4jContainer() { - return new Neo4jContainer<>("neo4j:5"); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/test/TestMyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/test/TestMyApplication.java deleted file mode 100644 index 199e4cf98e82..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/test/TestMyApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testcontainers.atdevelopmenttime.test; - -import org.springframework.boot.SpringApplication; - -public class TestMyApplication { - - public static void main(String[] args) { - SpringApplication.from(MyApplication::main).with(MyContainersConfiguration.class).run(args); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientTests.java deleted file mode 100644 index 90a1a82c2e4e..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientTests.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredrestclient; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.client.RestClientTest; -import org.springframework.http.MediaType; -import org.springframework.test.web.client.MockRestServiceServer; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; -import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; - -@RestClientTest(RemoteVehicleDetailsService.class) -class MyRestClientTests { - - @Autowired - private RemoteVehicleDetailsService service; - - @Autowired - private MockRestServiceServer server; - - @Test - void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() { - this.server.expect(requestTo("/greet/details")).andRespond(withSuccess("hello", MediaType.TEXT_PLAIN)); - String greeting = this.service.callRestService(); - assertThat(greeting).isEqualTo("hello"); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacassandra/SomeRepository.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacassandra/SomeRepository.java deleted file mode 100644 index 7cf8ce720303..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacassandra/SomeRepository.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdatacassandra; - -interface SomeRepository { - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacouchbase/SomeRepository.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacouchbase/SomeRepository.java deleted file mode 100644 index ccce557307c2..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacouchbase/SomeRepository.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdatacouchbase; - -interface SomeRepository { - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataelasticsearch/SomeRepository.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataelasticsearch/SomeRepository.java deleted file mode 100644 index 3a41f2cae667..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataelasticsearch/SomeRepository.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdataelasticsearch; - -public interface SomeRepository { - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withdb/MyRepositoryTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withdb/MyRepositoryTests.java deleted file mode 100644 index 16aca2b2822e..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withdb/MyRepositoryTests.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdatajpa.withdb; - -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; - -@DataJpaTest -@AutoConfigureTestDatabase(replace = Replace.NONE) -class MyRepositoryTests { - - // ... - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/MyRepositoryTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/MyRepositoryTests.java deleted file mode 100644 index 74205bc8a3d5..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/MyRepositoryTests.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdatajpa.withoutdb; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; - -import static org.assertj.core.api.Assertions.assertThat; - -@DataJpaTest -class MyRepositoryTests { - - @Autowired - private TestEntityManager entityManager; - - @Autowired - private UserRepository repository; - - @Test - void testExample() { - this.entityManager.persist(new User("sboot", "1234")); - User user = this.repository.findByUsername("sboot"); - assertThat(user.getUsername()).isEqualTo("sboot"); - assertThat(user.getEmployeeNumber()).isEqualTo("1234"); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataldap/inmemory/MyDataLdapTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataldap/inmemory/MyDataLdapTests.java deleted file mode 100644 index cb95fd2df009..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataldap/inmemory/MyDataLdapTests.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdataldap.inmemory; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest; -import org.springframework.ldap.core.LdapTemplate; - -@DataLdapTest -class MyDataLdapTests { - - @Autowired - @SuppressWarnings("unused") - private LdapTemplate ldapTemplate; - - // ... - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataldap/server/MyDataLdapTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataldap/server/MyDataLdapTests.java deleted file mode 100644 index 81a21ddec4e9..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataldap/server/MyDataLdapTests.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdataldap.server; - -import org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration; -import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest; - -@DataLdapTest(excludeAutoConfiguration = EmbeddedLdapAutoConfiguration.class) -class MyDataLdapTests { - - // ... - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataneo4j/nopropagation/MyDataNeo4jTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataneo4j/nopropagation/MyDataNeo4jTests.java deleted file mode 100644 index ff923a9e83ce..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataneo4j/nopropagation/MyDataNeo4jTests.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdataneo4j.nopropagation; - -import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; - -@DataNeo4jTest -@Transactional(propagation = Propagation.NOT_SUPPORTED) -class MyDataNeo4jTests { - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/MyDataNeo4jTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/MyDataNeo4jTests.java deleted file mode 100644 index f7a4cfe592c2..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/MyDataNeo4jTests.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdataneo4j.propagation; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest; - -@DataNeo4jTest -class MyDataNeo4jTests { - - @Autowired - @SuppressWarnings("unused") - private SomeRepository repository; - - // ... - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/SomeRepository.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/SomeRepository.java deleted file mode 100644 index b905dbe206a5..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/SomeRepository.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdataneo4j.propagation; - -interface SomeRepository { - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataredis/SomeRepository.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataredis/SomeRepository.java deleted file mode 100644 index 1a792e60dd71..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataredis/SomeRepository.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdataredis; - -interface SomeRepository { - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyRestDocsConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyRestDocsConfiguration.java deleted file mode 100644 index 149dc47d4d94..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyRestDocsConfiguration.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc; - -import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer; -import org.springframework.restdocs.templates.TemplateFormats; - -@TestConfiguration(proxyBeanMethods = false) -public class MyRestDocsConfiguration implements RestDocsMockMvcConfigurationCustomizer { - - @Override - public void customize(MockMvcRestDocumentationConfigurer configurer) { - configurer.snippets().withTemplateFormat(TemplateFormats.markdown()); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyUserDocumentationTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyUserDocumentationTests.java deleted file mode 100644 index 539e87799f09..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyUserDocumentationTests.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; - -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@WebMvcTest(UserController.class) -@AutoConfigureRestDocs -class MyUserDocumentationTests { - - @Autowired - private MockMvc mvc; - - @Test - void listUsers() throws Exception { - this.mvc.perform(get("/users").accept(MediaType.TEXT_PLAIN)) - .andExpect(status().isOk()) - .andDo(document("list-users")); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/UserController.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/UserController.java deleted file mode 100644 index f920a838057e..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/UserController.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc; - -class UserController { - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyRestDocsConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyRestDocsConfiguration.java deleted file mode 100644 index 699b5e3747f7..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyRestDocsConfiguration.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringrestdocs.withrestassured; - -import org.springframework.boot.test.autoconfigure.restdocs.RestDocsRestAssuredConfigurationCustomizer; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.restdocs.restassured.RestAssuredRestDocumentationConfigurer; -import org.springframework.restdocs.templates.TemplateFormats; - -@TestConfiguration(proxyBeanMethods = false) -public class MyRestDocsConfiguration implements RestDocsRestAssuredConfigurationCustomizer { - - @Override - public void customize(RestAssuredRestDocumentationConfigurer configurer) { - configurer.snippets().withTemplateFormat(TemplateFormats.markdown()); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyUserDocumentationTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyUserDocumentationTests.java deleted file mode 100644 index d3a4f8c0620b..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyUserDocumentationTests.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringrestdocs.withrestassured; - -import io.restassured.specification.RequestSpecification; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.is; -import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document; - -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@AutoConfigureRestDocs -class MyUserDocumentationTests { - - @Test - void listUsers(@Autowired RequestSpecification documentationSpec, @LocalServerPort int port) { - // @formatter:off - given(documentationSpec) - .filter(document("list-users")) - .when() - .port(port) - .get("/") - .then().assertThat() - .statusCode(is(200)); - // @formatter:on - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyRestDocsConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyRestDocsConfiguration.java deleted file mode 100644 index b1d7694ceb39..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyRestDocsConfiguration.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringrestdocs.withwebtestclient; - -import org.springframework.boot.test.autoconfigure.restdocs.RestDocsWebTestClientConfigurationCustomizer; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentationConfigurer; - -@TestConfiguration(proxyBeanMethods = false) -public class MyRestDocsConfiguration implements RestDocsWebTestClientConfigurationCustomizer { - - @Override - public void customize(WebTestClientRestDocumentationConfigurer configurer) { - configurer.snippets().withEncoding("UTF-8"); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/excludingconfiguration/MyTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/excludingconfiguration/MyTests.java deleted file mode 100644 index 34ddeb8edefa..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/excludingconfiguration/MyTests.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.excludingconfiguration; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Import; - -@SpringBootTest -@Import(MyTestsConfiguration.class) -class MyTests { - - @Test - void exampleTest() { - // ... - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/VehicleDetails.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/VehicleDetails.java deleted file mode 100644 index 8737b8fbaf9d..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/VehicleDetails.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.jsontests; - -class VehicleDetails { - - private final String make; - - private final String model; - - VehicleDetails(String make, String model) { - this.make = make; - this.model = model; - } - - String getMake() { - return this.make; - } - - String getModel() { - return this.model; - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/bean/MyTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/bean/MyTests.java deleted file mode 100644 index 3afcdda54819..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/bean/MyTests.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.mockingbeans.bean; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; - -@SpringBootTest -class MyTests { - - @Autowired - private Reverser reverser; - - @MockBean - private RemoteService remoteService; - - @Test - void exampleTest() { - given(this.remoteService.getValue()).willReturn("spring"); - String reverse = this.reverser.getReverseValue(); // Calls injected RemoteService - assertThat(reverse).isEqualTo("gnirps"); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/bean/RemoteService.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/bean/RemoteService.java deleted file mode 100644 index 1c0792316415..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/bean/RemoteService.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.mockingbeans.bean; - -class RemoteService { - - Object getValue() { - return null; - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/bean/Reverser.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/bean/Reverser.java deleted file mode 100644 index f98a35807f30..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/bean/Reverser.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.mockingbeans.bean; - -class Reverser { - - String getReverseValue() { - return null; - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/listener/MyConfig.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/listener/MyConfig.java deleted file mode 100644 index c0a7835cac27..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/listener/MyConfig.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.mockingbeans.listener; - -class MyConfig { - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/listener/MyTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/listener/MyTests.java deleted file mode 100644 index 66f43b2f3d5a..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/listener/MyTests.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.mockingbeans.listener; - -import org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener; -import org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; - -@ContextConfiguration(classes = MyConfig.class) -@TestExecutionListeners({ MockitoTestExecutionListener.class, ResetMocksTestExecutionListener.class }) -class MyTests { - - // ... - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/MyControllerTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/MyControllerTests.java deleted file mode 100644 index cce533a272be..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/MyControllerTests.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.springmvctests; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; - -import static org.mockito.BDDMockito.given; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@WebMvcTest(UserVehicleController.class) -class MyControllerTests { - - @Autowired - private MockMvc mvc; - - @MockBean - private UserVehicleService userVehicleService; - - @Test - void testExample() throws Exception { - // @formatter:off - given(this.userVehicleService.getVehicleDetails("sboot")) - .willReturn(new VehicleDetails("Honda", "Civic")); - this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN)) - .andExpect(status().isOk()) - .andExpect(content().string("Honda Civic")); - // @formatter:on - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/UserVehicleController.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/UserVehicleController.java deleted file mode 100644 index 504d724e10cf..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/UserVehicleController.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.springmvctests; - -class UserVehicleController { - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/UserVehicleService.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/UserVehicleService.java deleted file mode 100644 index f7b4f7c9fadc..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/UserVehicleService.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.springmvctests; - -class UserVehicleService { - - VehicleDetails getVehicleDetails(String name) { - return null; - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/VehicleDetails.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/VehicleDetails.java deleted file mode 100644 index 73b81924fc71..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/VehicleDetails.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.springmvctests; - -class VehicleDetails { - - VehicleDetails(String make, String model) { - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/MyControllerTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/MyControllerTests.java deleted file mode 100644 index fba3badfb0f2..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/MyControllerTests.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.springwebfluxtests; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.MediaType; -import org.springframework.test.web.reactive.server.WebTestClient; - -import static org.mockito.BDDMockito.given; - -@WebFluxTest(UserVehicleController.class) -class MyControllerTests { - - @Autowired - private WebTestClient webClient; - - @MockBean - private UserVehicleService userVehicleService; - - @Test - void testExample() { - // @formatter:off - given(this.userVehicleService.getVehicleDetails("sboot")) - .willReturn(new VehicleDetails("Honda", "Civic")); - this.webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN).exchange() - .expectStatus().isOk() - .expectBody(String.class).isEqualTo("Honda Civic"); - // @formatter:on - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/UserVehicleController.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/UserVehicleController.java deleted file mode 100644 index 4e2460933226..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/UserVehicleController.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.springwebfluxtests; - -class UserVehicleController { - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/UserVehicleService.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/UserVehicleService.java deleted file mode 100644 index c571ac3dd96d..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/UserVehicleService.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.springwebfluxtests; - -class UserVehicleService { - - VehicleDetails getVehicleDetails(String name) { - return null; - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/VehicleDetails.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/VehicleDetails.java deleted file mode 100644 index 0645577d06d4..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/VehicleDetails.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.springwebfluxtests; - -class VehicleDetails { - - VehicleDetails(String make, String model) { - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyApplication.java deleted file mode 100644 index 5cd33b1fc781..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyApplication.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.userconfigurationandslicing; - -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.data.mongodb.config.EnableMongoAuditing; - -@SpringBootApplication -@EnableMongoAuditing -public class MyApplication { - - // ... - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/scan/MyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/scan/MyApplication.java deleted file mode 100644 index b1828362527f..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/scan/MyApplication.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.userconfigurationandslicing.scan; - -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.ComponentScan; - -@SpringBootApplication -@ComponentScan({ "com.example.app", "com.example.another" }) -public class MyApplication { - - // ... - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/usingmain/custom/MyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/usingmain/custom/MyApplication.java deleted file mode 100644 index 26a9687fb81f..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/usingmain/custom/MyApplication.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.usingmain.custom; - -import org.springframework.boot.Banner; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class MyApplication { - - public static void main(String[] args) { - SpringApplication application = new SpringApplication(MyApplication.class); - application.setBannerMode(Banner.Mode.OFF); - application.setAdditionalProfiles("myprofile"); - application.run(args); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/usingmain/typical/MyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/usingmain/typical/MyApplication.java deleted file mode 100644 index 1c2234101930..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/usingmain/typical/MyApplication.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.usingmain.typical; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class MyApplication { - - public static void main(String[] args) { - SpringApplication.run(MyApplication.class, args); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/testcontainers/dynamicproperties/MyIntegrationTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/testcontainers/dynamicproperties/MyIntegrationTests.java deleted file mode 100644 index c18823972dda..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/testcontainers/dynamicproperties/MyIntegrationTests.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.testcontainers.dynamicproperties; - -import org.junit.jupiter.api.Test; -import org.testcontainers.containers.Neo4jContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; - -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.DynamicPropertyRegistry; -import org.springframework.test.context.DynamicPropertySource; - -@Testcontainers -@SpringBootTest -class MyIntegrationTests { - - @Container - static Neo4jContainer neo4j = new Neo4jContainer<>("neo4j:5"); - - @Test - void myTest() { - // ... - } - - @DynamicPropertySource - static void neo4jProperties(DynamicPropertyRegistry registry) { - registry.add("spring.neo4j.uri", neo4j::getBoltUrl); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/testcontainers/serviceconnections/MyIntegrationTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/testcontainers/serviceconnections/MyIntegrationTests.java deleted file mode 100644 index 5d19fe11325f..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/testcontainers/serviceconnections/MyIntegrationTests.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.testcontainers.serviceconnections; - -import org.junit.jupiter.api.Test; -import org.testcontainers.containers.Neo4jContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; - -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; - -@Testcontainers -@SpringBootTest -class MyIntegrationTests { - - @Container - @ServiceConnection - static Neo4jContainer neo4j = new Neo4jContainer<>("neo4j:5"); - - @Test - void myTest() { - // ... - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/testcontainers/vanilla/MyIntegrationTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/testcontainers/vanilla/MyIntegrationTests.java deleted file mode 100644 index 516575570ad4..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/testcontainers/vanilla/MyIntegrationTests.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.testcontainers.vanilla; - -import org.junit.jupiter.api.Test; -import org.testcontainers.containers.Neo4jContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; - -import org.springframework.boot.test.context.SpringBootTest; - -@Testcontainers -@SpringBootTest -class MyIntegrationTests { - - @Container - static Neo4jContainer neo4j = new Neo4jContainer<>("neo4j:5"); - - @Test - void myTest() { - // ... - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MyTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MyTests.java deleted file mode 100644 index c0c31d430723..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MyTests.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.utilities.testresttemplate; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.http.ResponseEntity; - -import static org.assertj.core.api.Assertions.assertThat; - -class MyTests { - - private final TestRestTemplate template = new TestRestTemplate(); - - @Test - void testRequest() { - ResponseEntity headers = this.template.getForEntity("https://myhost.example.com/example", String.class); - assertThat(headers.getHeaders().getLocation()).hasHost("other.example.com"); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/filterscannedentitydefinitions/MyEntityScanConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/filterscannedentitydefinitions/MyEntityScanConfiguration.java new file mode 100644 index 000000000000..444920405275 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/filterscannedentitydefinitions/MyEntityScanConfiguration.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.howto.dataaccess.filterscannedentitydefinitions; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.orm.jpa.persistenceunit.ManagedClassNameFilter; + +@Configuration(proxyBeanMethods = false) +public class MyEntityScanConfiguration { + + @Bean + public ManagedClassNameFilter entityScanFilter() { + return (className) -> className.startsWith("com.example.app.customer"); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/deployment/cloud/cloudfoundry/bindingtoservices/MyBean.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/deployment/cloud/cloudfoundry/bindingtoservices/MyBean.java new file mode 100644 index 000000000000..2b0b09b8fae1 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/deployment/cloud/cloudfoundry/bindingtoservices/MyBean.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.howto.deployment.cloud.cloudfoundry.bindingtoservices; + +import org.springframework.context.EnvironmentAware; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +@Component +public class MyBean implements EnvironmentAware { + + @SuppressWarnings("unused") + private String instanceId; + + @Override + public void setEnvironment(Environment environment) { + this.instanceId = environment.getProperty("vcap.application.instance_id"); + } + + // ... + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/httpclients/webclientreactornettycustomization/MyReactorNettyClientConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/httpclients/webclientreactornettycustomization/MyReactorNettyClientConfiguration.java index 8b67fb450e23..b47d7dd48408 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/httpclients/webclientreactornettycustomization/MyReactorNettyClientConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/httpclients/webclientreactornettycustomization/MyReactorNettyClientConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,9 +22,9 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ReactorResourceFactory; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.http.client.reactive.ReactorResourceFactory; @Configuration(proxyBeanMethods = false) public class MyReactorNettyClientConfiguration { diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/messaging/disabletransactedjmssession/MyJmsConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/messaging/disabletransactedjmssession/MyJmsConfiguration.java index 4772f76c537a..cedf98bea604 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/messaging/disabletransactedjmssession/MyJmsConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/messaging/disabletransactedjmssession/MyJmsConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import jakarta.jms.ConnectionFactory; import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer; +import org.springframework.boot.jms.ConnectionFactoryUnwrapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jms.config.DefaultJmsListenerContainerFactory; @@ -30,7 +31,7 @@ public class MyJmsConfiguration { public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(ConnectionFactory connectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer) { DefaultJmsListenerContainerFactory listenerFactory = new DefaultJmsListenerContainerFactory(); - configurer.configure(listenerFactory, connectionFactory); + configurer.configure(listenerFactory, ConnectionFactoryUnwrapper.unwrap(connectionFactory)); listenerFactory.setTransactionManager(null); listenerFactory.setSessionTransacted(false); return listenerFactory; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/nativeimage/developingyourfirstapplication/sampleapplication/MyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/nativeimage/developingyourfirstapplication/sampleapplication/MyApplication.java new file mode 100644 index 000000000000..b94c0b53ac39 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/nativeimage/developingyourfirstapplication/sampleapplication/MyApplication.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.howto.nativeimage.developingyourfirstapplication.sampleapplication; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@SpringBootApplication +public class MyApplication { + + @RequestMapping("/") + String home() { + return "Hello World!"; + } + + public static void main(String[] args) { + SpringApplication.run(MyApplication.class, args); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/testing/withspringsecurity/MySecurityTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/testing/withspringsecurity/MySecurityTests.java index b932490b8af4..312a29e5e671 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/testing/withspringsecurity/MySecurityTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/testing/withspringsecurity/MySecurityTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,20 +21,20 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.assertj.core.api.Assertions.assertThat; @WebMvcTest(UserController.class) class MySecurityTests { @Autowired - private MockMvc mvc; + private MockMvcTester mvc; @Test @WithMockUser(roles = "ADMIN") - void requestProtectedUrlWithUser() throws Exception { - this.mvc.perform(get("/")); + void requestProtectedUrlWithUser() { + assertThat(this.mvc.get().uri("/")).doesNotHaveFailed(); } } diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/Details.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/Details.java new file mode 100644 index 000000000000..28c038969b28 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/Details.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.io.restclient.restclient; + +public class Details { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/MyService.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/MyService.java new file mode 100644 index 000000000000..98bd2049406c --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/MyService.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.io.restclient.restclient; + +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; + +@Service +public class MyService { + + private final RestClient restClient; + + public MyService(RestClient.Builder restClientBuilder) { + this.restClient = restClientBuilder.baseUrl("https://example.org").build(); + } + + public Details someRestCall(String name) { + return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/Details.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/Details.java new file mode 100644 index 000000000000..eb853cba5e36 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/Details.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.io.restclient.restclient.ssl; + +public class Details { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/MyService.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/MyService.java new file mode 100644 index 000000000000..0fa7fa50cbba --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/MyService.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.io.restclient.restclient.ssl; + +import org.springframework.boot.autoconfigure.web.client.RestClientSsl; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; + +@Service +public class MyService { + + private final RestClient restClient; + + public MyService(RestClient.Builder restClientBuilder, RestClientSsl ssl) { + this.restClient = restClientBuilder.baseUrl("https://example.org").apply(ssl.fromBundle("mybundle")).build(); + } + + public Details someRestCall(String name) { + return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/Details.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/Details.java new file mode 100644 index 000000000000..1b0bbdb7533b --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/Details.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.io.restclient.restclient.ssl.settings; + +public class Details { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/MyService.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/MyService.java new file mode 100644 index 000000000000..8fef86df53e3 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/MyService.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.io.restclient.restclient.ssl.settings; + +import java.time.Duration; + +import org.springframework.boot.ssl.SslBundles; +import org.springframework.boot.web.client.ClientHttpRequestFactories; +import org.springframework.boot.web.client.ClientHttpRequestFactorySettings; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; + +@Service +public class MyService { + + private final RestClient restClient; + + public MyService(RestClient.Builder restClientBuilder, SslBundles sslBundles) { + ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS + .withReadTimeout(Duration.ofMinutes(2)) + .withSslBundle(sslBundles.getBundle("mybundle")); + ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(settings); + this.restClient = restClientBuilder.baseUrl("https://example.org").requestFactory(requestFactory).build(); + } + + public Details someRestCall(String name) { + return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/jms/receiving/custom/MyJmsConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/jms/receiving/custom/MyJmsConfiguration.java index fbfbe0633f26..ac9b38cde542 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/jms/receiving/custom/MyJmsConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/jms/receiving/custom/MyJmsConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import jakarta.jms.ConnectionFactory; import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer; +import org.springframework.boot.jms.ConnectionFactoryUnwrapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jms.config.DefaultJmsListenerContainerFactory; @@ -27,16 +28,12 @@ public class MyJmsConfiguration { @Bean - public DefaultJmsListenerContainerFactory myFactory(DefaultJmsListenerContainerFactoryConfigurer configurer) { + public DefaultJmsListenerContainerFactory myFactory(DefaultJmsListenerContainerFactoryConfigurer configurer, + ConnectionFactory connectionFactory) { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); - ConnectionFactory connectionFactory = getCustomConnectionFactory(); - configurer.configure(factory, connectionFactory); + configurer.configure(factory, ConnectionFactoryUnwrapper.unwrap(connectionFactory)); factory.setMessageConverter(new MyMessageConverter()); return factory; } - private ConnectionFactory getCustomConnectionFactory() { - return /**/ null; - } - } diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/reading/MyBean.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/reading/MyBean.java new file mode 100644 index 000000000000..f13cf6ec5451 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/reading/MyBean.java @@ -0,0 +1,30 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.messaging.pulsar.reading; + +import org.springframework.pulsar.annotation.PulsarReader; +import org.springframework.stereotype.Component; + +@Component +public class MyBean { + + @PulsarReader(topics = "someTopic", startMessageId = "earliest") + public void processMessage(String content) { + // ... + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/readingreactive/MyBean.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/readingreactive/MyBean.java new file mode 100644 index 000000000000..c42145288d55 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/readingreactive/MyBean.java @@ -0,0 +1,50 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.messaging.pulsar.readingreactive; + +import java.time.Instant; +import java.util.List; + +import org.apache.pulsar.client.api.Message; +import org.apache.pulsar.client.api.Schema; +import org.apache.pulsar.reactive.client.api.StartAtSpec; +import reactor.core.publisher.Mono; + +import org.springframework.pulsar.reactive.core.ReactiveMessageReaderBuilderCustomizer; +import org.springframework.pulsar.reactive.core.ReactivePulsarReaderFactory; +import org.springframework.stereotype.Component; + +@Component +public class MyBean { + + private final ReactivePulsarReaderFactory pulsarReaderFactory; + + public MyBean(ReactivePulsarReaderFactory pulsarReaderFactory) { + this.pulsarReaderFactory = pulsarReaderFactory; + } + + public void someMethod() { + ReactiveMessageReaderBuilderCustomizer readerBuilderCustomizer = (readerBuilder) -> readerBuilder + .topic("someTopic") + .startAtSpec(StartAtSpec.ofInstant(Instant.now().minusSeconds(5))); + Mono> message = this.pulsarReaderFactory + .createReader(Schema.STRING, List.of(readerBuilderCustomizer)) + .readOne(); + // ... + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/receiving/MyBean.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/receiving/MyBean.java new file mode 100644 index 000000000000..103e4ac8d65a --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/receiving/MyBean.java @@ -0,0 +1,30 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.messaging.pulsar.receiving; + +import org.springframework.pulsar.annotation.PulsarListener; +import org.springframework.stereotype.Component; + +@Component +public class MyBean { + + @PulsarListener(topics = "someTopic") + public void processMessage(String content) { + // ... + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/receivingreactive/MyBean.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/receivingreactive/MyBean.java new file mode 100644 index 000000000000..3dd9e8ffba98 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/receivingreactive/MyBean.java @@ -0,0 +1,33 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.messaging.pulsar.receivingreactive; + +import reactor.core.publisher.Mono; + +import org.springframework.pulsar.reactive.config.annotation.ReactivePulsarListener; +import org.springframework.stereotype.Component; + +@Component +public class MyBean { + + @ReactivePulsarListener(topics = "someTopic") + public Mono processMessage(String content) { + // ... + return Mono.empty(); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/sending/MyBean.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/sending/MyBean.java new file mode 100644 index 000000000000..7b6610b03e92 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/sending/MyBean.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.messaging.pulsar.sending; + +import org.apache.pulsar.client.api.PulsarClientException; + +import org.springframework.pulsar.core.PulsarTemplate; +import org.springframework.stereotype.Component; + +@Component +public class MyBean { + + private final PulsarTemplate pulsarTemplate; + + public MyBean(PulsarTemplate pulsarTemplate) { + this.pulsarTemplate = pulsarTemplate; + } + + public void someMethod() throws PulsarClientException { + this.pulsarTemplate.send("someTopic", "Hello"); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/sendingreactive/MyBean.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/sendingreactive/MyBean.java new file mode 100644 index 000000000000..1784f4ea8059 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/sendingreactive/MyBean.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.messaging.pulsar.sendingreactive; + +import org.springframework.pulsar.reactive.core.ReactivePulsarTemplate; +import org.springframework.stereotype.Component; + +@Component +public class MyBean { + + private final ReactivePulsarTemplate pulsarTemplate; + + public MyBean(ReactivePulsarTemplate pulsarTemplate) { + this.pulsarTemplate = pulsarTemplate; + } + + public void someMethod() { + this.pulsarTemplate.send("someTopic", "Hello").subscribe(); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/developingyourfirstapplication/sampleapplication/MyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/developingyourfirstapplication/sampleapplication/MyApplication.java deleted file mode 100644 index 4e7d1a520f62..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/developingyourfirstapplication/sampleapplication/MyApplication.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.nativeimage.developingyourfirstapplication.sampleapplication; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@SpringBootApplication -public class MyApplication { - - @RequestMapping("/") - String home() { - return "Hello World!"; - } - - public static void main(String[] args) { - SpringApplication.run(MyApplication.class, args); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/introducinggraalvmnativeimages/understandingaotprocessing/sourcecodegeneration/MyBean.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/introducinggraalvmnativeimages/understandingaotprocessing/sourcecodegeneration/MyBean.java deleted file mode 100644 index 08de0b3b398a..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/introducinggraalvmnativeimages/understandingaotprocessing/sourcecodegeneration/MyBean.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.nativeimage.introducinggraalvmnativeimages.understandingaotprocessing.sourcecodegeneration; - -public class MyBean { - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/customhints/MyClass.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/customhints/MyClass.java similarity index 82% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/customhints/MyClass.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/customhints/MyClass.java index 23c2d5f6efc8..25ba35760667 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/customhints/MyClass.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/customhints/MyClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.nativeimage.advanced.customhints; +package org.springframework.boot.docs.packaging.nativeimage.advanced.customhints; class MyClass { diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/customhints/MyInterface.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/customhints/MyInterface.java similarity index 81% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/customhints/MyInterface.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/customhints/MyInterface.java index af07cbcd4fd4..8356b1cd1c87 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/customhints/MyInterface.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/customhints/MyInterface.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.nativeimage.advanced.customhints; +package org.springframework.boot.docs.packaging.nativeimage.advanced.customhints; interface MyInterface { diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/customhints/MyRuntimeHints.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/customhints/MyRuntimeHints.java similarity index 91% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/customhints/MyRuntimeHints.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/customhints/MyRuntimeHints.java index 73f8f21fa3cc..ba3111784569 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/customhints/MyRuntimeHints.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/customhints/MyRuntimeHints.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.nativeimage.advanced.customhints; +package org.springframework.boot.docs.packaging.nativeimage.advanced.customhints; import java.lang.reflect.Method; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/customhints/MySerializableClass.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/customhints/MySerializableClass.java similarity index 82% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/customhints/MySerializableClass.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/customhints/MySerializableClass.java index a1bd0d0215e3..231e5e81e3e1 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/customhints/MySerializableClass.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/customhints/MySerializableClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.nativeimage.advanced.customhints; +package org.springframework.boot.docs.packaging.nativeimage.advanced.customhints; import java.io.Serializable; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/customhints/testing/MyRuntimeHintsTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/customhints/testing/MyRuntimeHintsTests.java similarity index 81% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/customhints/testing/MyRuntimeHintsTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/customhints/testing/MyRuntimeHintsTests.java index 8f92f44d90ab..2c8e0c06c479 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/customhints/testing/MyRuntimeHintsTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/customhints/testing/MyRuntimeHintsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,13 @@ * limitations under the License. */ -package org.springframework.boot.docs.nativeimage.advanced.customhints.testing; +package org.springframework.boot.docs.packaging.nativeimage.advanced.customhints.testing; import org.junit.jupiter.api.Test; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; -import org.springframework.boot.docs.nativeimage.advanced.customhints.MyRuntimeHints; +import org.springframework.boot.docs.packaging.nativeimage.advanced.customhints.MyRuntimeHints; import static org.assertj.core.api.Assertions.assertThat; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/nestedconfigurationproperties/MyProperties.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/nestedconfigurationproperties/MyProperties.java similarity index 87% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/nestedconfigurationproperties/MyProperties.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/nestedconfigurationproperties/MyProperties.java index 69e3bae254c4..e0b25257b01f 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/nestedconfigurationproperties/MyProperties.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/nestedconfigurationproperties/MyProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.nativeimage.advanced.nestedconfigurationproperties; +package org.springframework.boot.docs.packaging.nativeimage.advanced.nestedconfigurationproperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/nestedconfigurationproperties/MyPropertiesCtor.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/nestedconfigurationproperties/MyPropertiesCtor.java similarity index 88% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/nestedconfigurationproperties/MyPropertiesCtor.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/nestedconfigurationproperties/MyPropertiesCtor.java index 779bf7708be2..861947e1f697 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/nestedconfigurationproperties/MyPropertiesCtor.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/nestedconfigurationproperties/MyPropertiesCtor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.nativeimage.advanced.nestedconfigurationproperties; +package org.springframework.boot.docs.packaging.nativeimage.advanced.nestedconfigurationproperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/nestedconfigurationproperties/MyPropertiesRecord.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/nestedconfigurationproperties/MyPropertiesRecord.java similarity index 84% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/nestedconfigurationproperties/MyPropertiesRecord.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/nestedconfigurationproperties/MyPropertiesRecord.java index 3065a932ffa1..8322d7d83af0 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/nestedconfigurationproperties/MyPropertiesRecord.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/nestedconfigurationproperties/MyPropertiesRecord.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.nativeimage.advanced.nestedconfigurationproperties; +package org.springframework.boot.docs.packaging.nativeimage.advanced.nestedconfigurationproperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/nestedconfigurationproperties/Nested.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/nestedconfigurationproperties/Nested.java similarity index 83% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/nestedconfigurationproperties/Nested.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/nestedconfigurationproperties/Nested.java index 67e2c05b2ac8..6d15708a89a4 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/advanced/nestedconfigurationproperties/Nested.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/advanced/nestedconfigurationproperties/Nested.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.nativeimage.advanced.nestedconfigurationproperties; +package org.springframework.boot.docs.packaging.nativeimage.advanced.nestedconfigurationproperties; public class Nested { diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/introducinggraalvmnativeimages/understandingaotprocessing/sourcecodegeneration/MyBean.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/introducinggraalvmnativeimages/understandingaotprocessing/sourcecodegeneration/MyBean.java new file mode 100644 index 000000000000..11504c26c5f5 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/introducinggraalvmnativeimages/understandingaotprocessing/sourcecodegeneration/MyBean.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.packaging.nativeimage.introducinggraalvmnativeimages.understandingaotprocessing.sourcecodegeneration; + +public class MyBean { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/introducinggraalvmnativeimages/understandingaotprocessing/sourcecodegeneration/MyConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/introducinggraalvmnativeimages/understandingaotprocessing/sourcecodegeneration/MyConfiguration.java similarity index 80% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/introducinggraalvmnativeimages/understandingaotprocessing/sourcecodegeneration/MyConfiguration.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/introducinggraalvmnativeimages/understandingaotprocessing/sourcecodegeneration/MyConfiguration.java index 8955e2e141c7..3ff1e8051129 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/introducinggraalvmnativeimages/understandingaotprocessing/sourcecodegeneration/MyConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/introducinggraalvmnativeimages/understandingaotprocessing/sourcecodegeneration/MyConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.nativeimage.introducinggraalvmnativeimages.understandingaotprocessing.sourcecodegeneration; +package org.springframework.boot.docs.packaging.nativeimage.introducinggraalvmnativeimages.understandingaotprocessing.sourcecodegeneration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/introducinggraalvmnativeimages/understandingaotprocessing/sourcecodegeneration/MyConfiguration__BeanDefinitions.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/introducinggraalvmnativeimages/understandingaotprocessing/sourcecodegeneration/MyConfiguration__BeanDefinitions.java similarity index 90% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/introducinggraalvmnativeimages/understandingaotprocessing/sourcecodegeneration/MyConfiguration__BeanDefinitions.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/introducinggraalvmnativeimages/understandingaotprocessing/sourcecodegeneration/MyConfiguration__BeanDefinitions.java index 3bea7555e5e0..e48a2e72a2d1 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/nativeimage/introducinggraalvmnativeimages/understandingaotprocessing/sourcecodegeneration/MyConfiguration__BeanDefinitions.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/packaging/nativeimage/introducinggraalvmnativeimages/understandingaotprocessing/sourcecodegeneration/MyConfiguration__BeanDefinitions.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.nativeimage.introducinggraalvmnativeimages.understandingaotprocessing.sourcecodegeneration; +package org.springframework.boot.docs.packaging.nativeimage.introducinggraalvmnativeimages.understandingaotprocessing.sourcecodegeneration; import org.springframework.beans.factory.aot.BeanInstanceSupplier; import org.springframework.beans.factory.config.BeanDefinition; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/additionalautoconfigurationandslicing/MyJdbcTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/additionalautoconfigurationandslicing/MyJdbcTests.java similarity index 83% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/additionalautoconfigurationandslicing/MyJdbcTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/additionalautoconfigurationandslicing/MyJdbcTests.java index b2f9aadc368e..8de55f68ff68 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/additionalautoconfigurationandslicing/MyJdbcTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/additionalautoconfigurationandslicing/MyJdbcTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.additionalautoconfigurationandslicing; +package org.springframework.boot.docs.testing.springbootapplications.additionalautoconfigurationandslicing; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredjdbc/MyTransactionalTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredjdbc/MyTransactionalTests.java similarity index 84% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredjdbc/MyTransactionalTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredjdbc/MyTransactionalTests.java index 944e1ca34c60..5abc234b4e5c 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredjdbc/MyTransactionalTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredjdbc/MyTransactionalTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredjdbc; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredjdbc; import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; import org.springframework.transaction.annotation.Propagation; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredjooq/MyJooqTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredjooq/MyJooqTests.java similarity index 84% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredjooq/MyJooqTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredjooq/MyJooqTests.java index 16e631e17a66..2039407647ec 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredjooq/MyJooqTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredjooq/MyJooqTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredjooq; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredjooq; import org.jooq.DSLContext; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.java new file mode 100644 index 000000000000..26f57b79663b --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredrestclient; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.client.RestClientTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.MockRestServiceServer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +@RestClientTest(RemoteVehicleDetailsService.class) +class MyRestClientServiceTests { + + @Autowired + private RemoteVehicleDetailsService service; + + @Autowired + private MockRestServiceServer server; + + @Test + void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() { + this.server.expect(requestTo("https://example.com/greet/details")) + .andRespond(withSuccess("hello", MediaType.TEXT_PLAIN)); + String greeting = this.service.callRestService(); + assertThat(greeting).isEqualTo("hello"); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.java new file mode 100644 index 000000000000..9f910024ec2f --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredrestclient; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.client.RestClientTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.MockRestServiceServer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +@RestClientTest(org.springframework.boot.docs.testing.springbootapplications.autoconfiguredrestclient.RemoteVehicleDetailsService.class) +class MyRestTemplateServiceTests { + + @Autowired + private RemoteVehicleDetailsService service; + + @Autowired + private MockRestServiceServer server; + + @Test + void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() { + this.server.expect(requestTo("/greet/details")).andRespond(withSuccess("hello", MediaType.TEXT_PLAIN)); + String greeting = this.service.callRestService(); + assertThat(greeting).isEqualTo("hello"); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/RemoteVehicleDetailsService.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/RemoteVehicleDetailsService.java similarity index 80% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/RemoteVehicleDetailsService.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/RemoteVehicleDetailsService.java index 6bfd672ac480..96ce772ac53f 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/RemoteVehicleDetailsService.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/RemoteVehicleDetailsService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredrestclient; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredrestclient; class RemoteVehicleDetailsService { diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacassandra/MyDataCassandraTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacassandra/MyDataCassandraTests.java similarity index 83% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacassandra/MyDataCassandraTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacassandra/MyDataCassandraTests.java index 639686461cf9..6b9ced354397 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacassandra/MyDataCassandraTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacassandra/MyDataCassandraTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdatacassandra; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdatacassandra; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacassandra/SomeRepository.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacassandra/SomeRepository.java new file mode 100644 index 000000000000..33ab6cc034a5 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacassandra/SomeRepository.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdatacassandra; + +interface SomeRepository { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacouchbase/MyDataCouchbaseTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacouchbase/MyDataCouchbaseTests.java similarity index 83% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacouchbase/MyDataCouchbaseTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacouchbase/MyDataCouchbaseTests.java index 34e102245160..f11c1aee383d 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacouchbase/MyDataCouchbaseTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacouchbase/MyDataCouchbaseTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdatacouchbase; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdatacouchbase; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.couchbase.DataCouchbaseTest; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacouchbase/SomeRepository.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacouchbase/SomeRepository.java new file mode 100644 index 000000000000..1065110d1f72 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacouchbase/SomeRepository.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdatacouchbase; + +interface SomeRepository { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataelasticsearch/MyDataElasticsearchTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataelasticsearch/MyDataElasticsearchTests.java similarity index 83% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataelasticsearch/MyDataElasticsearchTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataelasticsearch/MyDataElasticsearchTests.java index d2c0190773b3..4781b41d8f4e 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataelasticsearch/MyDataElasticsearchTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataelasticsearch/MyDataElasticsearchTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdataelasticsearch; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdataelasticsearch; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.elasticsearch.DataElasticsearchTest; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataelasticsearch/SomeRepository.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataelasticsearch/SomeRepository.java new file mode 100644 index 000000000000..b53e6f640c50 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataelasticsearch/SomeRepository.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdataelasticsearch; + +public interface SomeRepository { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/MyNonTransactionalTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/MyNonTransactionalTests.java similarity index 84% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/MyNonTransactionalTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/MyNonTransactionalTests.java index 90ba61f25529..7ffc7cd0e0e1 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/MyNonTransactionalTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/MyNonTransactionalTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdatajpa; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdatajpa; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.transaction.annotation.Propagation; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withdb/MyRepositoryTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withdb/MyRepositoryTests.java new file mode 100644 index 000000000000..1346c8516020 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withdb/MyRepositoryTests.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdatajpa.withdb; + +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +@DataJpaTest +@AutoConfigureTestDatabase(replace = Replace.NONE) +class MyRepositoryTests { + + // ... + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/MyRepositoryTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/MyRepositoryTests.java new file mode 100644 index 000000000000..4f103e622bef --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/MyRepositoryTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdatajpa.withoutdb; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +class MyRepositoryTests { + + @Autowired + private TestEntityManager entityManager; + + @Autowired + private UserRepository repository; + + @Test + void testExample() { + this.entityManager.persist(new User("sboot", "1234")); + User user = this.repository.findByUsername("sboot"); + assertThat(user.getUsername()).isEqualTo("sboot"); + assertThat(user.getEmployeeNumber()).isEqualTo("1234"); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/User.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/User.java similarity index 80% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/User.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/User.java index c3656b8129cf..6a467a7b35fd 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/User.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/User.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdatajpa.withoutdb; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdatajpa.withoutdb; class User { diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/UserRepository.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/UserRepository.java similarity index 78% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/UserRepository.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/UserRepository.java index 5b2e6963be6c..26089658907b 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/UserRepository.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/UserRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdatajpa.withoutdb; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdatajpa.withoutdb; interface UserRepository { diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataldap/inmemory/MyDataLdapTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataldap/inmemory/MyDataLdapTests.java new file mode 100644 index 000000000000..b28c6da9ed75 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataldap/inmemory/MyDataLdapTests.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdataldap.inmemory; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest; +import org.springframework.ldap.core.LdapTemplate; + +@DataLdapTest +class MyDataLdapTests { + + @Autowired + @SuppressWarnings("unused") + private LdapTemplate ldapTemplate; + + // ... + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataldap/server/MyDataLdapTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataldap/server/MyDataLdapTests.java new file mode 100644 index 000000000000..45821a0cf1fa --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataldap/server/MyDataLdapTests.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdataldap.server; + +import org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration; +import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest; + +@DataLdapTest(excludeAutoConfiguration = EmbeddedLdapAutoConfiguration.class) +class MyDataLdapTests { + + // ... + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatamongodb/MyDataMongoDbTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatamongodb/MyDataMongoDbTests.java similarity index 84% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatamongodb/MyDataMongoDbTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatamongodb/MyDataMongoDbTests.java index cd3f5739cd25..3cff47d5389e 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatamongodb/MyDataMongoDbTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatamongodb/MyDataMongoDbTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdatamongodb; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdatamongodb; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataneo4j/nopropagation/MyDataNeo4jTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataneo4j/nopropagation/MyDataNeo4jTests.java new file mode 100644 index 000000000000..1cc5220432b2 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataneo4j/nopropagation/MyDataNeo4jTests.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdataneo4j.nopropagation; + +import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@DataNeo4jTest +@Transactional(propagation = Propagation.NOT_SUPPORTED) +class MyDataNeo4jTests { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/MyDataNeo4jTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/MyDataNeo4jTests.java new file mode 100644 index 000000000000..b80a53050c76 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/MyDataNeo4jTests.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdataneo4j.propagation; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest; + +@DataNeo4jTest +class MyDataNeo4jTests { + + @Autowired + @SuppressWarnings("unused") + private SomeRepository repository; + + // ... + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/SomeRepository.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/SomeRepository.java new file mode 100644 index 000000000000..b5d5f9c9ad3c --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/SomeRepository.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdataneo4j.propagation; + +interface SomeRepository { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataredis/MyDataRedisTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataredis/MyDataRedisTests.java similarity index 83% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataredis/MyDataRedisTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataredis/MyDataRedisTests.java index d5a58d5a1789..55cd050447db 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataredis/MyDataRedisTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataredis/MyDataRedisTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringdataredis; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdataredis; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataredis/SomeRepository.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataredis/SomeRepository.java new file mode 100644 index 000000000000..aa329cf1a04a --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataredis/SomeRepository.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringdataredis; + +interface SomeRepository { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyRestDocsConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyRestDocsConfiguration.java new file mode 100644 index 000000000000..af3077bf021a --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyRestDocsConfiguration.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc; + +import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer; +import org.springframework.restdocs.templates.TemplateFormats; + +@TestConfiguration(proxyBeanMethods = false) +public class MyRestDocsConfiguration implements RestDocsMockMvcConfigurationCustomizer { + + @Override + public void customize(MockMvcRestDocumentationConfigurer configurer) { + configurer.snippets().withTemplateFormat(TemplateFormats.markdown()); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyResultHandlerConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyResultHandlerConfiguration.java similarity index 85% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyResultHandlerConfiguration.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyResultHandlerConfiguration.java index 51a9d31f0716..18104aeb5c97 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyResultHandlerConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyResultHandlerConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/assertj/MyUserDocumentationTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/assertj/MyUserDocumentationTests.java new file mode 100644 index 000000000000..ee492fcb17ca --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/assertj/MyUserDocumentationTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc.assertj; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.assertj.MockMvcTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; + +@WebMvcTest(UserController.class) +@AutoConfigureRestDocs +class MyUserDocumentationTests { + + @Autowired + private MockMvcTester mvc; + + @Test + void listUsers() { + assertThat(this.mvc.get().uri("/users").accept(MediaType.TEXT_PLAIN)).hasStatusOk() + .apply(document("list-users")); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/assertj/UserController.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/assertj/UserController.java new file mode 100644 index 000000000000..c385c282cee0 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/assertj/UserController.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc.assertj; + +class UserController { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/hamcrest/MyUserDocumentationTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/hamcrest/MyUserDocumentationTests.java new file mode 100644 index 000000000000..3865123bf233 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/hamcrest/MyUserDocumentationTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc.hamcrest; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.assertj.MockMvcTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; + +@WebMvcTest(UserController.class) +@AutoConfigureRestDocs +class MyUserDocumentationTests { + + @Autowired + private MockMvcTester mvc; + + @Test + void listUsers() { + assertThat(this.mvc.get().uri("/users").accept(MediaType.TEXT_PLAIN)).hasStatusOk() + .apply(document("list-users")); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/hamcrest/UserController.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/hamcrest/UserController.java new file mode 100644 index 000000000000..c08fd607634c --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/hamcrest/UserController.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc.hamcrest; + +class UserController { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyRestDocsConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyRestDocsConfiguration.java new file mode 100644 index 000000000000..b93243434670 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyRestDocsConfiguration.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withrestassured; + +import org.springframework.boot.test.autoconfigure.restdocs.RestDocsRestAssuredConfigurationCustomizer; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.restdocs.restassured.RestAssuredRestDocumentationConfigurer; +import org.springframework.restdocs.templates.TemplateFormats; + +@TestConfiguration(proxyBeanMethods = false) +public class MyRestDocsConfiguration implements RestDocsRestAssuredConfigurationCustomizer { + + @Override + public void customize(RestAssuredRestDocumentationConfigurer configurer) { + configurer.snippets().withTemplateFormat(TemplateFormats.markdown()); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyUserDocumentationTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyUserDocumentationTests.java new file mode 100644 index 000000000000..3ff16a1f177b --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyUserDocumentationTests.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withrestassured; + +import io.restassured.specification.RequestSpecification; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.is; +import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@AutoConfigureRestDocs +class MyUserDocumentationTests { + + @Test + void listUsers(@Autowired RequestSpecification documentationSpec, @LocalServerPort int port) { + // @formatter:off + given(documentationSpec) + .filter(document("list-users")) + .when() + .port(port) + .get("/") + .then().assertThat() + .statusCode(is(200)); + // @formatter:on + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyRestDocsConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyRestDocsConfiguration.java new file mode 100644 index 000000000000..5d7d27d5959b --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyRestDocsConfiguration.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withwebtestclient; + +import org.springframework.boot.test.autoconfigure.restdocs.RestDocsWebTestClientConfigurationCustomizer; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentationConfigurer; + +@TestConfiguration(proxyBeanMethods = false) +public class MyRestDocsConfiguration implements RestDocsWebTestClientConfigurationCustomizer { + + @Override + public void customize(WebTestClientRestDocumentationConfigurer configurer) { + configurer.snippets().withEncoding("UTF-8"); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyUsersDocumentationTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyUsersDocumentationTests.java similarity index 88% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyUsersDocumentationTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyUsersDocumentationTests.java index a83b763b9403..e872bcfe3407 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyUsersDocumentationTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyUsersDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringrestdocs.withwebtestclient; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withwebtestclient; import org.junit.jupiter.api.Test; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyWebTestClientBuilderCustomizerConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyWebTestClientBuilderCustomizerConfiguration.java similarity index 86% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyWebTestClientBuilderCustomizerConfiguration.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyWebTestClientBuilderCustomizerConfiguration.java index 8713104daacd..2cb31138c1cb 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyWebTestClientBuilderCustomizerConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyWebTestClientBuilderCustomizerConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringrestdocs.withwebtestclient; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withwebtestclient; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.web.reactive.server.WebTestClientBuilderCustomizer; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/MyWebServiceClientTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/MyWebServiceClientTests.java similarity index 90% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/MyWebServiceClientTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/MyWebServiceClientTests.java index 41d204e11b9c..2844a20433fe 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/MyWebServiceClientTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/MyWebServiceClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredwebservices.client; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredwebservices.client; import org.junit.jupiter.api.Test; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/Request.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/Request.java similarity index 80% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/Request.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/Request.java index 42fbbeeaf886..f346077e3732 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/Request.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/Request.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredwebservices.client; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredwebservices.client; import jakarta.xml.bind.annotation.XmlRootElement; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/Response.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/Response.java similarity index 84% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/Response.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/Response.java index 31a93c5328f0..8c2dbaad7ae5 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/Response.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/Response.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredwebservices.client; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredwebservices.client; import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/SomeWebService.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/SomeWebService.java similarity index 86% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/SomeWebService.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/SomeWebService.java index 8418964d8c11..77d02d6733fc 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/SomeWebService.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/SomeWebService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredwebservices.client; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredwebservices.client; import org.springframework.boot.webservices.client.WebServiceTemplateBuilder; import org.springframework.stereotype.Service; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/server/ExampleEndpoint.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/server/ExampleEndpoint.java similarity index 86% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/server/ExampleEndpoint.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/server/ExampleEndpoint.java index 56c7adfdcbe9..132040819cd6 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/server/ExampleEndpoint.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/server/ExampleEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredwebservices.server; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredwebservices.server; import javax.xml.transform.Source; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/server/MyWebServiceServerTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/server/MyWebServiceServerTests.java similarity index 89% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/server/MyWebServiceServerTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/server/MyWebServiceServerTests.java index cb2bd460ca86..537e3ad9babf 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/server/MyWebServiceServerTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/server/MyWebServiceServerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredwebservices.server; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredwebservices.server; import org.junit.jupiter.api.Test; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/detectingwebapptype/MyWebFluxTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/detectingwebapptype/MyWebFluxTests.java similarity index 82% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/detectingwebapptype/MyWebFluxTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/detectingwebapptype/MyWebFluxTests.java index 6aebffbd26fe..9f9522c8858a 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/detectingwebapptype/MyWebFluxTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/detectingwebapptype/MyWebFluxTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.detectingwebapptype; +package org.springframework.boot.docs.testing.springbootapplications.detectingwebapptype; import org.springframework.boot.test.context.SpringBootTest; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/excludingconfiguration/MyTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/excludingconfiguration/MyTests.java new file mode 100644 index 000000000000..d62ddb0a33e4 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/excludingconfiguration/MyTests.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.excludingconfiguration; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; + +@SpringBootTest +@Import(MyTestsConfiguration.class) +class MyTests { + + @Test + void exampleTest() { + // ... + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/excludingconfiguration/MyTestsConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/excludingconfiguration/MyTestsConfiguration.java similarity index 79% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/excludingconfiguration/MyTestsConfiguration.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/excludingconfiguration/MyTestsConfiguration.java index be779949850c..9eb98ff3fa3d 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/excludingconfiguration/MyTestsConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/excludingconfiguration/MyTestsConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.excludingconfiguration; +package org.springframework.boot.docs.testing.springbootapplications.excludingconfiguration; class MyTestsConfiguration { diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/jmx/MyJmxTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/jmx/MyJmxTests.java similarity index 89% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/jmx/MyJmxTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/jmx/MyJmxTests.java index 606b179760ba..5e879d63d457 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/jmx/MyJmxTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/jmx/MyJmxTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.jmx; +package org.springframework.boot.docs.testing.springbootapplications.jmx; import javax.management.MBeanServer; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/jmx/SampleApp.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/jmx/SampleApp.java similarity index 86% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/jmx/SampleApp.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/jmx/SampleApp.java index e6d101f0c226..599c05516c3e 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/jmx/SampleApp.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/jmx/SampleApp.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.jmx; +package org.springframework.boot.docs.testing.springbootapplications.jmx; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/MyJsonAssertJTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/jsontests/MyJsonAssertJTests.java similarity index 90% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/MyJsonAssertJTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/jsontests/MyJsonAssertJTests.java index 354009771c2b..5c453111c967 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/MyJsonAssertJTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/jsontests/MyJsonAssertJTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.jsontests; +package org.springframework.boot.docs.testing.springbootapplications.jsontests; import org.junit.jupiter.api.Test; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/MyJsonTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/jsontests/MyJsonTests.java similarity index 92% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/MyJsonTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/jsontests/MyJsonTests.java index 027bb69a97a1..a3772d59642d 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/MyJsonTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/jsontests/MyJsonTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.jsontests; +package org.springframework.boot.docs.testing.springbootapplications.jsontests; import org.junit.jupiter.api.Test; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/SomeObject.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/jsontests/SomeObject.java similarity index 81% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/SomeObject.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/jsontests/SomeObject.java index b24cd2a98dd6..9026f0b01be3 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/SomeObject.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/jsontests/SomeObject.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.jsontests; +package org.springframework.boot.docs.testing.springbootapplications.jsontests; class SomeObject { diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/jsontests/VehicleDetails.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/jsontests/VehicleDetails.java new file mode 100644 index 000000000000..57edbcbc5b54 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/jsontests/VehicleDetails.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.jsontests; + +class VehicleDetails { + + private final String make; + + private final String model; + + VehicleDetails(String make, String model) { + this.make = make; + this.model = model; + } + + String getMake() { + return this.make; + } + + String getModel() { + return this.model; + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.java similarity index 90% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.java index 5e07dd2c85af..c1a44cd3f704 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.springgraphqltests; +package org.springframework.boot.docs.testing.springbootapplications.springgraphqltests; import org.junit.jupiter.api.Test; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GreetingControllerTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springgraphqltests/GreetingControllerTests.java similarity index 90% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GreetingControllerTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springgraphqltests/GreetingControllerTests.java index a1de2f6e0562..5582fee6ab2f 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GreetingControllerTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springgraphqltests/GreetingControllerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.springgraphqltests; +package org.springframework.boot.docs.testing.springbootapplications.springgraphqltests; import org.junit.jupiter.api.Test; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/MyControllerTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/MyControllerTests.java new file mode 100644 index 000000000000..0b0e8e82ab8b --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/MyControllerTests.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.springmvctests; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.assertj.MockMvcTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + +@WebMvcTest(UserVehicleController.class) +class MyControllerTests { + + @Autowired + private MockMvcTester mvc; + + @MockitoBean + private UserVehicleService userVehicleService; + + @Test + void testExample() { + // @formatter:off + given(this.userVehicleService.getVehicleDetails("sboot")) + .willReturn(new VehicleDetails("Honda", "Civic")); + assertThat(this.mvc.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN)) + .hasStatusOk() + .hasBodyTextEqualTo("Honda Civic"); + // @formatter:on + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/MyHtmlUnitTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/MyHtmlUnitTests.java similarity index 80% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/MyHtmlUnitTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/MyHtmlUnitTests.java index 5d0b4b5bc976..53ebdbcdcf4d 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/MyHtmlUnitTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/MyHtmlUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,15 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.springmvctests; +package org.springframework.boot.docs.testing.springbootapplications.springmvctests; -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.html.HtmlPage; +import org.htmlunit.WebClient; +import org.htmlunit.html.HtmlPage; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @@ -33,7 +33,7 @@ class MyHtmlUnitTests { @Autowired private WebClient webClient; - @MockBean + @MockitoBean private UserVehicleService userVehicleService; @Test diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/UserVehicleController.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/UserVehicleController.java new file mode 100644 index 000000000000..5eaf98909c82 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/UserVehicleController.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.springmvctests; + +class UserVehicleController { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/UserVehicleService.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/UserVehicleService.java new file mode 100644 index 000000000000..55aaaa4ba548 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/UserVehicleService.java @@ -0,0 +1,25 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.springmvctests; + +class UserVehicleService { + + VehicleDetails getVehicleDetails(String name) { + return null; + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/VehicleDetails.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/VehicleDetails.java new file mode 100644 index 000000000000..341d9c212024 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/VehicleDetails.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.springmvctests; + +class VehicleDetails { + + VehicleDetails(String make, String model) { + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/MyControllerTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/MyControllerTests.java new file mode 100644 index 000000000000..275f2bc89def --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/MyControllerTests.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.springwebfluxtests; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.reactive.server.WebTestClient; + +import static org.mockito.BDDMockito.given; + +@WebFluxTest(UserVehicleController.class) +class MyControllerTests { + + @Autowired + private WebTestClient webClient; + + @MockitoBean + private UserVehicleService userVehicleService; + + @Test + void testExample() { + // @formatter:off + given(this.userVehicleService.getVehicleDetails("sboot")) + .willReturn(new VehicleDetails("Honda", "Civic")); + this.webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN).exchange() + .expectStatus().isOk() + .expectBody(String.class).isEqualTo("Honda Civic"); + // @formatter:on + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/UserVehicleController.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/UserVehicleController.java new file mode 100644 index 000000000000..b5501185810b --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/UserVehicleController.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.springwebfluxtests; + +class UserVehicleController { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/UserVehicleService.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/UserVehicleService.java new file mode 100644 index 000000000000..d95bb51ab55a --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/UserVehicleService.java @@ -0,0 +1,25 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.springwebfluxtests; + +class UserVehicleService { + + VehicleDetails getVehicleDetails(String name) { + return null; + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/VehicleDetails.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/VehicleDetails.java new file mode 100644 index 000000000000..3353f1a26502 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/VehicleDetails.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.springwebfluxtests; + +class VehicleDetails { + + VehicleDetails(String make, String model) { + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyApplication.java new file mode 100644 index 000000000000..a20855ebf727 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyApplication.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.userconfigurationandslicing; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.mongodb.config.EnableMongoAuditing; + +@SpringBootApplication +@EnableMongoAuditing +public class MyApplication { + + // ... + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyMongoConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyMongoConfiguration.java similarity index 83% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyMongoConfiguration.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyMongoConfiguration.java index fb324003eaeb..dc3d583151c5 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyMongoConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyMongoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.userconfigurationandslicing; +package org.springframework.boot.docs.testing.springbootapplications.userconfigurationandslicing; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.config.EnableMongoAuditing; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyWebConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyWebConfiguration.java similarity index 85% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyWebConfiguration.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyWebConfiguration.java index a4f7aba1a5f1..ff714209b5d4 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyWebConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyWebConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.userconfigurationandslicing; +package org.springframework.boot.docs.testing.springbootapplications.userconfigurationandslicing; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyWebMvcConfigurer.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyWebMvcConfigurer.java similarity index 82% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyWebMvcConfigurer.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyWebMvcConfigurer.java index 30e4c013232a..a6f8e31912ef 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyWebMvcConfigurer.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyWebMvcConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.userconfigurationandslicing; +package org.springframework.boot.docs.testing.springbootapplications.userconfigurationandslicing; import org.springframework.stereotype.Component; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/scan/MyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/scan/MyApplication.java new file mode 100644 index 000000000000..add512bfb089 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/scan/MyApplication.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.userconfigurationandslicing.scan; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan({ "com.example.app", "com.example.another" }) +public class MyApplication { + + // ... + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/usingapplicationarguments/MyApplicationArgumentTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/usingapplicationarguments/MyApplicationArgumentTests.java similarity index 87% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/usingapplicationarguments/MyApplicationArgumentTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/usingapplicationarguments/MyApplicationArgumentTests.java index c362c7ebf5a0..d6f294db14d9 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/usingapplicationarguments/MyApplicationArgumentTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/usingapplicationarguments/MyApplicationArgumentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.usingapplicationarguments; +package org.springframework.boot.docs.testing.springbootapplications.usingapplicationarguments; import org.junit.jupiter.api.Test; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/usingmain/always/MyApplicationTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/usingmain/always/MyApplicationTests.java similarity index 85% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/usingmain/always/MyApplicationTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/usingmain/always/MyApplicationTests.java index 05a4be6e26fa..25affe804cb9 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/usingmain/always/MyApplicationTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/usingmain/always/MyApplicationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.usingmain.always; +package org.springframework.boot.docs.testing.springbootapplications.usingmain.always; import org.junit.jupiter.api.Test; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/usingmain/custom/MyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/usingmain/custom/MyApplication.java new file mode 100644 index 000000000000..314ef90377cf --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/usingmain/custom/MyApplication.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.usingmain.custom; + +import org.springframework.boot.Banner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MyApplication { + + public static void main(String[] args) { + SpringApplication application = new SpringApplication(MyApplication.class); + application.setBannerMode(Banner.Mode.OFF); + application.setAdditionalProfiles("myprofile"); + application.run(args); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/usingmain/typical/MyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/usingmain/typical/MyApplication.java new file mode 100644 index 000000000000..6165ab5879c2 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/usingmain/typical/MyApplication.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.springbootapplications.usingmain.typical; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MyApplication { + + public static void main(String[] args) { + SpringApplication.run(MyApplication.class, args); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/withmockenvironment/MyMockMvcTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/withmockenvironment/MyMockMvcTests.java similarity index 78% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/withmockenvironment/MyMockMvcTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/withmockenvironment/MyMockMvcTests.java index b869d880f6ed..15c68728dc88 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/withmockenvironment/MyMockMvcTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/withmockenvironment/MyMockMvcTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.withmockenvironment; +package org.springframework.boot.docs.testing.springbootapplications.withmockenvironment; import org.junit.jupiter.api.Test; @@ -23,7 +23,9 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -37,6 +39,12 @@ void testWithMockMvc(@Autowired MockMvc mvc) throws Exception { mvc.perform(get("/")).andExpect(status().isOk()).andExpect(content().string("Hello World")); } + // If AssertJ is on the classpath, you can use MockMvcTester + @Test + void testWithMockMvcTester(@Autowired MockMvcTester mvc) { + assertThat(mvc.get().uri("/")).hasStatusOk().hasBodyTextEqualTo("Hello World"); + } + // If Spring WebFlux is on the classpath, you can drive MVC tests with a WebTestClient @Test void testWithWebTestClient(@Autowired WebTestClient webClient) { diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/withmockenvironment/MyMockWebTestClientTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/withmockenvironment/MyMockWebTestClientTests.java similarity index 88% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/withmockenvironment/MyMockWebTestClientTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/withmockenvironment/MyMockWebTestClientTests.java index 4fcd698b73dc..d197a7e01a60 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/withmockenvironment/MyMockWebTestClientTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/withmockenvironment/MyMockWebTestClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.withmockenvironment; +package org.springframework.boot.docs.testing.springbootapplications.withmockenvironment; import org.junit.jupiter.api.Test; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/withrunningserver/MyRandomPortTestRestTemplateTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/withrunningserver/MyRandomPortTestRestTemplateTests.java similarity index 88% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/withrunningserver/MyRandomPortTestRestTemplateTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/withrunningserver/MyRandomPortTestRestTemplateTests.java index 8ed9f3f416b3..8b3c34b298aa 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/withrunningserver/MyRandomPortTestRestTemplateTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/withrunningserver/MyRandomPortTestRestTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.withrunningserver; +package org.springframework.boot.docs.testing.springbootapplications.withrunningserver; import org.junit.jupiter.api.Test; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/withrunningserver/MyRandomPortWebTestClientTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/withrunningserver/MyRandomPortWebTestClientTests.java similarity index 88% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/withrunningserver/MyRandomPortWebTestClientTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/withrunningserver/MyRandomPortWebTestClientTests.java index c7f660b67d54..4198e9108c5e 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/withrunningserver/MyRandomPortWebTestClientTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/withrunningserver/MyRandomPortWebTestClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.withrunningserver; +package org.springframework.boot.docs.testing.springbootapplications.withrunningserver; import org.junit.jupiter.api.Test; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/testcontainers/dynamicproperties/MyIntegrationTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/testcontainers/dynamicproperties/MyIntegrationTests.java new file mode 100644 index 000000000000..7032586acf50 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/testcontainers/dynamicproperties/MyIntegrationTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.testcontainers.dynamicproperties; + +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.Neo4jContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; + +@Testcontainers +@SpringBootTest +class MyIntegrationTests { + + @Container + static Neo4jContainer neo4j = new Neo4jContainer<>("neo4j:5"); + + @Test + void myTest() { + // ... + } + + @DynamicPropertySource + static void neo4jProperties(DynamicPropertyRegistry registry) { + registry.add("spring.neo4j.uri", neo4j::getBoltUrl); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/testcontainers/serviceconnections/MyIntegrationTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/testcontainers/serviceconnections/MyIntegrationTests.java new file mode 100644 index 000000000000..8827213caa45 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/testcontainers/serviceconnections/MyIntegrationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.testcontainers.serviceconnections; + +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.Neo4jContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +@Testcontainers +@SpringBootTest +class MyIntegrationTests { + + @Container + @ServiceConnection + static Neo4jContainer neo4j = new Neo4jContainer<>("neo4j:5"); + + @Test + void myTest() { + // ... + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/testcontainers/serviceconnections/MyRedisConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/testcontainers/serviceconnections/MyRedisConfiguration.java similarity index 87% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/testcontainers/serviceconnections/MyRedisConfiguration.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/testcontainers/serviceconnections/MyRedisConfiguration.java index 38b2640557f6..e463aba10c7c 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/testcontainers/serviceconnections/MyRedisConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/testcontainers/serviceconnections/MyRedisConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.testcontainers.serviceconnections; +package org.springframework.boot.docs.testing.testcontainers.serviceconnections; import org.testcontainers.containers.GenericContainer; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/testcontainers/vanilla/MyIntegrationTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/testcontainers/vanilla/MyIntegrationTests.java new file mode 100644 index 000000000000..1bfe8857cc0f --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/testcontainers/vanilla/MyIntegrationTests.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.testcontainers.vanilla; + +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.Neo4jContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.boot.test.context.SpringBootTest; + +@Testcontainers +@SpringBootTest +class MyIntegrationTests { + + @Container + static Neo4jContainer neo4j = new Neo4jContainer<>("neo4j:5"); + + @Test + void myTest() { + // ... + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/configdataapplicationcontextinitializer/Config.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/configdataapplicationcontextinitializer/Config.java similarity index 78% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/configdataapplicationcontextinitializer/Config.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/configdataapplicationcontextinitializer/Config.java index 499411882218..a75412db2204 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/configdataapplicationcontextinitializer/Config.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/configdataapplicationcontextinitializer/Config.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.utilities.configdataapplicationcontextinitializer; +package org.springframework.boot.docs.testing.utilities.configdataapplicationcontextinitializer; class Config { diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/configdataapplicationcontextinitializer/MyConfigFileTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/configdataapplicationcontextinitializer/MyConfigFileTests.java similarity index 84% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/configdataapplicationcontextinitializer/MyConfigFileTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/configdataapplicationcontextinitializer/MyConfigFileTests.java index 3799f6ed52df..b281a94a5ba9 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/configdataapplicationcontextinitializer/MyConfigFileTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/configdataapplicationcontextinitializer/MyConfigFileTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.utilities.configdataapplicationcontextinitializer; +package org.springframework.boot.docs.testing.utilities.configdataapplicationcontextinitializer; import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer; import org.springframework.test.context.ContextConfiguration; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/outputcapture/MyOutputCaptureTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/outputcapture/MyOutputCaptureTests.java similarity index 88% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/outputcapture/MyOutputCaptureTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/outputcapture/MyOutputCaptureTests.java index 589eef16a658..4777af30ce36 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/outputcapture/MyOutputCaptureTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/outputcapture/MyOutputCaptureTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.utilities.outputcapture; +package org.springframework.boot.docs.testing.utilities.outputcapture; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/testpropertyvalues/MyEnvironmentTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/testpropertyvalues/MyEnvironmentTests.java similarity index 88% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/testpropertyvalues/MyEnvironmentTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/testpropertyvalues/MyEnvironmentTests.java index c40bd3addbbe..e772c7ea0c77 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/testpropertyvalues/MyEnvironmentTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/testpropertyvalues/MyEnvironmentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.utilities.testpropertyvalues; +package org.springframework.boot.docs.testing.utilities.testpropertyvalues; import org.junit.jupiter.api.Test; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MySpringBootTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/testresttemplate/MySpringBootTests.java similarity index 92% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MySpringBootTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/testresttemplate/MySpringBootTests.java index 77847fdf4091..6a3403385da2 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MySpringBootTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/testresttemplate/MySpringBootTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.utilities.testresttemplate; +package org.springframework.boot.docs.testing.utilities.testresttemplate; import java.time.Duration; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MySpringBootTestsConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/testresttemplate/MySpringBootTestsConfiguration.java similarity index 95% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MySpringBootTestsConfiguration.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/testresttemplate/MySpringBootTestsConfiguration.java index b9de7e86c5b7..6e88f20eeb4f 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MySpringBootTestsConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/testresttemplate/MySpringBootTestsConfiguration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.utilities.testresttemplate; +package org.springframework.boot.docs.testing.utilities.testresttemplate; import java.net.URI; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/testresttemplate/MyTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/testresttemplate/MyTests.java new file mode 100644 index 000000000000..d35829002e27 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/utilities/testresttemplate/MyTests.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.utilities.testresttemplate; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.ResponseEntity; + +import static org.assertj.core.api.Assertions.assertThat; + +class MyTests { + + private final TestRestTemplate template = new TestRestTemplate(); + + @Test + void testRequest() { + ResponseEntity headers = this.template.getForEntity("https://myhost.example.com/example", String.class); + assertThat(headers.getHeaders().getLocation()).hasHost("other.example.com"); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/web/reactive/webflux/errorhandling/MyExceptionHandlingController.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/web/reactive/webflux/errorhandling/MyExceptionHandlingController.java deleted file mode 100644 index b1913940a84e..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/web/reactive/webflux/errorhandling/MyExceptionHandlingController.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.web.reactive.webflux.errorhandling; - -import org.springframework.boot.web.reactive.error.ErrorAttributes; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.reactive.result.view.Rendering; -import org.springframework.web.server.ServerWebExchange; - -@Controller -public class MyExceptionHandlingController { - - @GetMapping("/profile") - public Rendering userProfile() { - // ... - throw new IllegalStateException(); - } - - @ExceptionHandler(IllegalStateException.class) - public Rendering handleIllegalState(ServerWebExchange exchange, IllegalStateException exc) { - exchange.getAttributes().putIfAbsent(ErrorAttributes.ERROR_ATTRIBUTE, exc); - return Rendering.view("errorView").modelAttribute("message", exc.getMessage()).build(); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/web/servlet/springmvc/errorhandling/MyController.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/web/servlet/springmvc/errorhandling/MyController.java deleted file mode 100644 index e93f0ba92b43..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/web/servlet/springmvc/errorhandling/MyController.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.web.servlet.springmvc.errorhandling; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.boot.web.servlet.error.ErrorAttributes; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.ExceptionHandler; - -@Controller -public class MyController { - - @ExceptionHandler(CustomException.class) - String handleCustomException(HttpServletRequest request, CustomException ex) { - request.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, ex); - return "errorView"; - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/data/nosql/elasticsearch/connectingusingspringdata/MyBean.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/data/nosql/elasticsearch/connectingusingspringdata/MyBean.kt index 95442da426a2..001ee654ce00 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/data/nosql/elasticsearch/connectingusingspringdata/MyBean.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/data/nosql/elasticsearch/connectingusingspringdata/MyBean.kt @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,7 @@ package org.springframework.boot.docs.data.nosql.elasticsearch.connectingusingsp import org.springframework.stereotype.Component @Component -@Suppress("DEPRECATION") -class MyBean(private val template: org.springframework.data.elasticsearch.client.erhlc.ElasticsearchRestTemplate ) { +class MyBean(private val template: org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate ) { // @fold:on // ... fun someMethod(id: String): Boolean { diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/data/sql/jdbcclient/MyBean.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/data/sql/jdbcclient/MyBean.kt new file mode 100644 index 000000000000..02cd7243bd7d --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/data/sql/jdbcclient/MyBean.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.data.sql.jdbcclient + +import org.springframework.jdbc.core.simple.JdbcClient +import org.springframework.stereotype.Component + +@Component +class MyBean(private val jdbcClient: JdbcClient) { + + fun doSomething() { + jdbcClient.sql("delete from customer").update() + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/deployment/cloud/cloudfoundry/bindingtoservices/MyBean.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/deployment/cloud/cloudfoundry/bindingtoservices/MyBean.kt deleted file mode 100644 index 0399da6b794e..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/deployment/cloud/cloudfoundry/bindingtoservices/MyBean.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.deployment.cloud.cloudfoundry.bindingtoservices - -import org.springframework.context.EnvironmentAware -import org.springframework.core.env.Environment -import org.springframework.stereotype.Component - -@Component -class MyBean : EnvironmentAware { - - private var instanceId: String? = null - - override fun setEnvironment(environment: Environment) { - instanceId = environment.getProperty("vcap.application.instance_id") - } - - // ... - -} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/devtools/MyContainersConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/devtools/MyContainersConfiguration.kt index 8837947959f0..44a21e69418d 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/devtools/MyContainersConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/devtools/MyContainersConfiguration.kt @@ -27,7 +27,7 @@ class MyContainersConfiguration { @Bean @RestartScope @ServiceConnection - fun monogDbContainer(): MongoDBContainer { + fun mongoDbContainer(): MongoDBContainer { return MongoDBContainer("mongo:5.0") } diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/dynamicproperties/MyContainersConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/dynamicproperties/MyContainersConfiguration.kt index 5ee593e46c24..04959303fcad 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/dynamicproperties/MyContainersConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/dynamicproperties/MyContainersConfiguration.kt @@ -25,7 +25,7 @@ import org.testcontainers.containers.MongoDBContainer class MyContainersConfiguration { @Bean - fun monogDbContainer(properties: DynamicPropertyRegistry): MongoDBContainer { + fun mongoDbContainer(properties: DynamicPropertyRegistry): MongoDBContainer { var container = MongoDBContainer("mongo:5.0") properties.add("spring.data.mongodb.host", container::getHost) properties.add("spring.data.mongodb.port", container::getFirstMappedPort) diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/importingcontainerdeclarations/MyContainersConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/importingcontainerdeclarations/MyContainersConfiguration.kt index 4d4ae76e8fcc..9c1776c0a4d1 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/importingcontainerdeclarations/MyContainersConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/importingcontainerdeclarations/MyContainersConfiguration.kt @@ -16,6 +16,7 @@ package org.springframework.boot.docs.features.testcontainers.atdevelopmenttime.importingcontainerdeclarations +import org.springframework.boot.docs.features.devservices.testcontainers.atdevelopmenttime.importingcontainerdeclarations.MyContainers import org.springframework.boot.test.context.TestConfiguration import org.springframework.boot.testcontainers.context.ImportTestcontainers diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.kt new file mode 100644 index 000000000000..9d6d561da02a --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredrestclient + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.client.RestClientTest +import org.springframework.http.MediaType +import org.springframework.test.web.client.MockRestServiceServer +import org.springframework.test.web.client.match.MockRestRequestMatchers +import org.springframework.test.web.client.response.MockRestResponseCreators + +@RestClientTest(RemoteVehicleDetailsService::class) +class MyRestClientServiceTests( + @Autowired val service: RemoteVehicleDetailsService, + @Autowired val server: MockRestServiceServer) { + + @Test + fun getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() { + server.expect(MockRestRequestMatchers.requestTo("https://example.com/greet/details")) + .andRespond(MockRestResponseCreators.withSuccess("hello", MediaType.TEXT_PLAIN)) + val greeting = service.callRestService() + assertThat(greeting).isEqualTo("hello") + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientTests.kt deleted file mode 100644 index d0372b89ed22..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientTests.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredrestclient - -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.client.RestClientTest -import org.springframework.http.MediaType -import org.springframework.test.web.client.MockRestServiceServer -import org.springframework.test.web.client.match.MockRestRequestMatchers -import org.springframework.test.web.client.response.MockRestResponseCreators - -@RestClientTest(RemoteVehicleDetailsService::class) -class MyRestClientTests( - @Autowired val service: RemoteVehicleDetailsService, - @Autowired val server: MockRestServiceServer) { - - @Test - fun getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() { - server.expect(MockRestRequestMatchers.requestTo("/greet/details")) - .andRespond(MockRestResponseCreators.withSuccess("hello", MediaType.TEXT_PLAIN)) - val greeting = service.callRestService() - assertThat(greeting).isEqualTo("hello") - } - -} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.kt new file mode 100644 index 000000000000..5b51eae5c68d --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredrestclient + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.client.RestClientTest +import org.springframework.http.MediaType +import org.springframework.test.web.client.MockRestServiceServer +import org.springframework.test.web.client.match.MockRestRequestMatchers +import org.springframework.test.web.client.response.MockRestResponseCreators + +@RestClientTest(RemoteVehicleDetailsService::class) +class MyRestTemplateServiceTests( + @Autowired val service: RemoteVehicleDetailsService, + @Autowired val server: MockRestServiceServer) { + + @Test + fun getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() { + server.expect(MockRestRequestMatchers.requestTo("/greet/details")) + .andRespond(MockRestResponseCreators.withSuccess("hello", MediaType.TEXT_PLAIN)) + val greeting = service.callRestService() + assertThat(greeting).isEqualTo("hello") + } + +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyUserDocumentationTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyUserDocumentationTests.kt index 3bdd188515ec..ec5916b9d5de 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyUserDocumentationTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyUserDocumentationTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,25 +16,23 @@ package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.http.MediaType import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders -import org.springframework.test.web.servlet.result.MockMvcResultMatchers +import org.springframework.test.web.servlet.assertj.MockMvcTester @WebMvcTest(UserController::class) @AutoConfigureRestDocs -class MyUserDocumentationTests(@Autowired val mvc: MockMvc) { +class MyUserDocumentationTests(@Autowired val mvc: MockMvcTester) { @Test fun listUsers() { - mvc.perform(MockMvcRequestBuilders.get("/users").accept(MediaType.TEXT_PLAIN)) - .andExpect(MockMvcResultMatchers.status().isOk) - .andDo(MockMvcRestDocumentation.document("list-users")) + assertThat(mvc.get().uri("/users").accept(MediaType.TEXT_PLAIN)) + .hasStatusOk().apply(MockMvcRestDocumentation.document("list-users")) } } \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/bean/MyTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/bean/MyTests.kt deleted file mode 100644 index 14833f413683..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/bean/MyTests.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.mockingbeans.bean - -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import org.mockito.BDDMockito.given -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.mock.mockito.MockBean - -@SpringBootTest -class MyTests(@Autowired val reverser: Reverser, @MockBean val remoteService: RemoteService) { - - @Test - fun exampleTest() { - given(remoteService.value).willReturn("spring") - val reverse = reverser.reverseValue // Calls injected RemoteService - assertThat(reverse).isEqualTo("gnirps") - } - -} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/bean/RemoteService.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/bean/RemoteService.kt deleted file mode 100644 index f79f32d26d76..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/bean/RemoteService.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.mockingbeans.bean - -class RemoteService { - - val value: Any? - get() = null - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/bean/Reverser.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/bean/Reverser.kt deleted file mode 100644 index 982aa813cac3..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/bean/Reverser.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.mockingbeans.bean - -class Reverser { - - val reverseValue: String? - get() = null - -} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/listener/MyConfig.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/listener/MyConfig.kt deleted file mode 100644 index fb681d574ff0..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/listener/MyConfig.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.mockingbeans.listener - -class MyConfig diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/listener/MyTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/listener/MyTests.kt deleted file mode 100644 index 66c4795b7116..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/mockingbeans/listener/MyTests.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.features.testing.springbootapplications.mockingbeans.listener - -import org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener -import org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener -import org.springframework.test.context.ContextConfiguration -import org.springframework.test.context.TestExecutionListeners - -@ContextConfiguration(classes = [MyConfig::class]) -@TestExecutionListeners( - MockitoTestExecutionListener::class, - ResetMocksTestExecutionListener::class -) -class MyTests { - - // ... - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/MyControllerTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/MyControllerTests.kt index 9879efc62a02..77c98c15f436 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/MyControllerTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/MyControllerTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,29 +16,27 @@ package org.springframework.boot.docs.features.testing.springbootapplications.springmvctests +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.mockito.BDDMockito.given import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest -import org.springframework.boot.test.mock.mockito.MockBean import org.springframework.http.MediaType -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders -import org.springframework.test.web.servlet.result.MockMvcResultMatchers +import org.springframework.test.context.bean.override.mockito.MockitoBean +import org.springframework.test.web.servlet.assertj.MockMvcTester @WebMvcTest(UserVehicleController::class) -class MyControllerTests(@Autowired val mvc: MockMvc) { +class MyControllerTests(@Autowired val mvc: MockMvcTester) { - @MockBean + @MockitoBean lateinit var userVehicleService: UserVehicleService @Test fun testExample() { given(userVehicleService.getVehicleDetails("sboot")) - .willReturn(VehicleDetails("Honda", "Civic")) - mvc.perform(MockMvcRequestBuilders.get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN)) - .andExpect(MockMvcResultMatchers.status().isOk) - .andExpect(MockMvcResultMatchers.content().string("Honda Civic")) + .willReturn(VehicleDetails("Honda", "Civic")) + assertThat(mvc.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN)) + .hasStatusOk().hasBodyTextEqualTo("Honda Civic") } } diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/MyHtmlUnitTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/MyHtmlUnitTests.kt index 27119f4052f8..34e059158caf 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/MyHtmlUnitTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/MyHtmlUnitTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +16,19 @@ package org.springframework.boot.docs.features.testing.springbootapplications.springmvctests -import com.gargoylesoftware.htmlunit.WebClient -import com.gargoylesoftware.htmlunit.html.HtmlPage import org.assertj.core.api.Assertions.assertThat +import org.htmlunit.WebClient +import org.htmlunit.html.HtmlPage import org.junit.jupiter.api.Test import org.mockito.BDDMockito.given import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest -import org.springframework.boot.test.mock.mockito.MockBean +import org.springframework.test.context.bean.override.mockito.MockitoBean @WebMvcTest(UserVehicleController::class) class MyHtmlUnitTests(@Autowired val webClient: WebClient) { - @MockBean + @MockitoBean lateinit var userVehicleService: UserVehicleService @Test diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/MyControllerTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/MyControllerTests.kt index ab2d64e11e7a..acf895c7e4a2 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/MyControllerTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/MyControllerTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,15 +20,15 @@ import org.junit.jupiter.api.Test import org.mockito.BDDMockito.given import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest -import org.springframework.boot.test.mock.mockito.MockBean import org.springframework.http.MediaType +import org.springframework.test.context.bean.override.mockito.MockitoBean import org.springframework.test.web.reactive.server.WebTestClient import org.springframework.test.web.reactive.server.expectBody @WebFluxTest(UserVehicleController::class) class MyControllerTests(@Autowired val webClient: WebTestClient) { - @MockBean + @MockitoBean lateinit var userVehicleService: UserVehicleService @Test diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/withmockenvironment/MyMockMvcTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/withmockenvironment/MyMockMvcTests.kt index 10e10bae2f5b..4a64a4b04615 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/withmockenvironment/MyMockMvcTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/withmockenvironment/MyMockMvcTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,24 +16,23 @@ package org.springframework.boot.docs.features.testing.springbootapplications.withmockenvironment +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.web.reactive.server.WebTestClient import org.springframework.test.web.reactive.server.expectBody -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders -import org.springframework.test.web.servlet.result.MockMvcResultMatchers +import org.springframework.test.web.servlet.assertj.MockMvcTester @SpringBootTest @AutoConfigureMockMvc class MyMockMvcTests { @Test - fun testWithMockMvc(@Autowired mvc: MockMvc) { - mvc.perform(MockMvcRequestBuilders.get("/")).andExpect(MockMvcResultMatchers.status().isOk) - .andExpect(MockMvcResultMatchers.content().string("Hello World")) + fun testWithMockMvc(@Autowired mvc: MockMvcTester) { + assertThat(mvc.get().uri("/")).hasStatusOk() + .hasBodyTextEqualTo("Hello World") } // If Spring WebFlux is on the classpath, you can drive MVC tests with a WebTestClient @@ -41,10 +40,10 @@ class MyMockMvcTests { @Test fun testWithWebTestClient(@Autowired webClient: WebTestClient) { webClient - .get().uri("/") - .exchange() - .expectStatus().isOk - .expectBody().isEqualTo("Hello World") + .get().uri("/") + .exchange() + .expectStatus().isOk + .expectBody().isEqualTo("Hello World") } } diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/vanilla/MyIntegrationTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/vanilla/MyIntegrationTests.kt index d15a1bb6c638..e62e5804d7d6 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/vanilla/MyIntegrationTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/vanilla/MyIntegrationTests.kt @@ -32,8 +32,10 @@ class MyIntegrationTests { } companion object { + @Container val neo4j = Neo4jContainer("neo4j:5") + } } diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/actuator/maphealthindicatorstometrics/MyHealthMetricsExportConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/actuator/maphealthindicatorstometrics/MyHealthMetricsExportConfiguration.kt index 950e30242ce4..38e8f94f4c07 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/actuator/maphealthindicatorstometrics/MyHealthMetricsExportConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/actuator/maphealthindicatorstometrics/MyHealthMetricsExportConfiguration.kt @@ -32,13 +32,11 @@ class MyHealthMetricsExportConfiguration(registry: MeterRegistry, healthEndpoint }.strongReference(true).register(registry) } - private fun getStatusCode(health: HealthEndpoint): Int { - return when (health.health().status) { - Status.UP -> 3 - Status.OUT_OF_SERVICE -> 2 - Status.DOWN -> 1 - else -> 0 - } + private fun getStatusCode(health: HealthEndpoint) = when (health.health().status) { + Status.UP -> 3 + Status.OUT_OF_SERVICE -> 2 + Status.DOWN -> 1 + else -> 0 } } diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/filterscannedentitydefinitions/MyEntityScanConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/filterscannedentitydefinitions/MyEntityScanConfiguration.kt new file mode 100644 index 000000000000..4152c9e7c7da --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/filterscannedentitydefinitions/MyEntityScanConfiguration.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.howto.dataaccess.filterscannedentitydefinitions + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.orm.jpa.persistenceunit.ManagedClassNameFilter + +@Configuration(proxyBeanMethods = false) +class MyEntityScanConfiguration { + + @Bean + fun entityScanFilter() : ManagedClassNameFilter { + return ManagedClassNameFilter { className -> + className.startsWith("com.example.app.customer") + } + } +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/deployment/cloud/cloudfoundry/bindingtoservices/MyBean.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/deployment/cloud/cloudfoundry/bindingtoservices/MyBean.kt new file mode 100644 index 000000000000..b411fb883d60 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/deployment/cloud/cloudfoundry/bindingtoservices/MyBean.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.howto.deployment.cloud.cloudfoundry.bindingtoservices + +import org.springframework.context.EnvironmentAware +import org.springframework.core.env.Environment +import org.springframework.stereotype.Component + +@Component +class MyBean : EnvironmentAware { + + private var instanceId: String? = null + + override fun setEnvironment(environment: Environment) { + instanceId = environment.getProperty("vcap.application.instance_id") + } + + // ... + +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/httpclients/webclientreactornettycustomization/MyReactorNettyClientConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/httpclients/webclientreactornettycustomization/MyReactorNettyClientConfiguration.kt index 85c140e3f078..1dac3ab5de0e 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/httpclients/webclientreactornettycustomization/MyReactorNettyClientConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/httpclients/webclientreactornettycustomization/MyReactorNettyClientConfiguration.kt @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.http.client.reactive.ClientHttpConnector import org.springframework.http.client.reactive.ReactorClientHttpConnector -import org.springframework.http.client.reactive.ReactorResourceFactory +import org.springframework.http.client.ReactorResourceFactory import reactor.netty.http.client.HttpClient @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/messaging/disabletransactedjmssession/MyJmsConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/messaging/disabletransactedjmssession/MyJmsConfiguration.kt index 83bfb50271bf..39b2f194448a 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/messaging/disabletransactedjmssession/MyJmsConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/messaging/disabletransactedjmssession/MyJmsConfiguration.kt @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.boot.docs.howto.messaging.disabletransactedjmssession import jakarta.jms.ConnectionFactory +import org.springframework.boot.jms.ConnectionFactoryUnwrapper import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -30,7 +31,7 @@ class MyJmsConfiguration { fun jmsListenerContainerFactory(connectionFactory: ConnectionFactory?, configurer: DefaultJmsListenerContainerFactoryConfigurer): DefaultJmsListenerContainerFactory { val listenerFactory = DefaultJmsListenerContainerFactory() - configurer.configure(listenerFactory, connectionFactory) + configurer.configure(listenerFactory, ConnectionFactoryUnwrapper.unwrap(connectionFactory)) listenerFactory.setTransactionManager(null) listenerFactory.setSessionTransacted(false) return listenerFactory diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/testing/withspringsecurity/MySecurityTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/testing/withspringsecurity/MySecurityTests.kt index efbb12d1205c..43bfd36fcc1d 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/testing/withspringsecurity/MySecurityTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/testing/withspringsecurity/MySecurityTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,20 +16,21 @@ package org.springframework.boot.docs.howto.testing.withspringsecurity +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.security.test.context.support.WithMockUser -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders +import org.springframework.test.web.servlet.assertj.MockMvcTester @WebMvcTest(UserController::class) -class MySecurityTests(@Autowired val mvc: MockMvc) { +class MySecurityTests(@Autowired val mvc: MockMvcTester) { @Test @WithMockUser(roles = ["ADMIN"]) fun requestProtectedUrlWithUser() { - mvc.perform(MockMvcRequestBuilders.get("/")) + assertThat(mvc.get().uri("/")) + .doesNotHaveFailed() } } diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/Details.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/Details.kt new file mode 100644 index 000000000000..219b0a9ffe29 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/Details.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.io.restclient.restclient + +class Details diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/MyService.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/MyService.kt new file mode 100644 index 000000000000..cb1854c03c5e --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/MyService.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.io.restclient.restclient + +import org.springframework.boot.docs.io.restclient.restclient.ssl.Details +import org.springframework.stereotype.Service +import org.springframework.web.client.RestClient + +@Service +class MyService(restClientBuilder: RestClient.Builder) { + + private val restClient: RestClient + + init { + restClient = restClientBuilder.baseUrl("https://example.org").build() + } + + fun someRestCall(name: String?): Details { + return restClient.get().uri("/{name}/details", name) + .retrieve().body(Details::class.java)!! + } + +} + diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/Details.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/Details.kt new file mode 100644 index 000000000000..613bbadb3fd7 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/Details.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.io.restclient.restclient.ssl + +class Details diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/MyService.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/MyService.kt new file mode 100644 index 000000000000..220a44252e7f --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/MyService.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.io.restclient.restclient.ssl + +import org.springframework.boot.autoconfigure.web.client.RestClientSsl +import org.springframework.boot.docs.io.restclient.restclient.ssl.settings.Details +import org.springframework.stereotype.Service +import org.springframework.web.client.RestClient + +@Service +class MyService(restClientBuilder: RestClient.Builder, ssl: RestClientSsl) { + + private val restClient: RestClient + + init { + restClient = restClientBuilder.baseUrl("https://example.org") + .apply(ssl.fromBundle("mybundle")).build() + } + + fun someRestCall(name: String?): Details { + return restClient.get().uri("/{name}/details", name) + .retrieve().body(Details::class.java)!! + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/Details.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/Details.kt new file mode 100644 index 000000000000..3a73e355e1c1 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/Details.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.io.restclient.restclient.ssl.settings + +class Details diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/MyService.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/MyService.kt new file mode 100644 index 000000000000..e153262f8248 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/MyService.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.io.restclient.restclient.ssl.settings + +import org.springframework.boot.ssl.SslBundles +import org.springframework.boot.web.client.ClientHttpRequestFactories +import org.springframework.boot.web.client.ClientHttpRequestFactorySettings +import org.springframework.stereotype.Service +import org.springframework.web.client.RestClient +import java.time.Duration + +@Service +class MyService(restClientBuilder: RestClient.Builder, sslBundles: SslBundles) { + + private val restClient: RestClient + + init { + val settings = ClientHttpRequestFactorySettings.DEFAULTS + .withReadTimeout(Duration.ofMinutes(2)) + .withSslBundle(sslBundles.getBundle("mybundle")) + val requestFactory = ClientHttpRequestFactories.get(settings) + restClient = restClientBuilder + .baseUrl("https://example.org") + .requestFactory(requestFactory).build() + } + + fun someRestCall(name: String?): Details { + return restClient.get().uri("/{name}/details", name).retrieve().body(Details::class.java)!! + } + +} + diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/jms/receiving/custom/MyJmsConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/jms/receiving/custom/MyJmsConfiguration.kt index d535cf7be763..d788b97d998a 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/jms/receiving/custom/MyJmsConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/jms/receiving/custom/MyJmsConfiguration.kt @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.boot.docs.messaging.jms.receiving.custom import jakarta.jms.ConnectionFactory import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer +import org.springframework.boot.jms.ConnectionFactoryUnwrapper import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.jms.config.DefaultJmsListenerContainerFactory @@ -26,16 +27,12 @@ import org.springframework.jms.config.DefaultJmsListenerContainerFactory class MyJmsConfiguration { @Bean - fun myFactory(configurer: DefaultJmsListenerContainerFactoryConfigurer): DefaultJmsListenerContainerFactory { + fun myFactory(configurer: DefaultJmsListenerContainerFactoryConfigurer, + connectionFactory: ConnectionFactory): DefaultJmsListenerContainerFactory { val factory = DefaultJmsListenerContainerFactory() - val connectionFactory = getCustomConnectionFactory() - configurer.configure(factory, connectionFactory) + configurer.configure(factory, ConnectionFactoryUnwrapper.unwrap(connectionFactory)) factory.setMessageConverter(MyMessageConverter()) return factory } - fun getCustomConnectionFactory() : ConnectionFactory? { - return /**/ null - } - } diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/pulsar/reading/MyBean.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/pulsar/reading/MyBean.kt new file mode 100644 index 000000000000..bb2936cc07d5 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/pulsar/reading/MyBean.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.messaging.pulsar.reading + +import org.springframework.pulsar.annotation.PulsarReader +import org.springframework.stereotype.Component + +@Suppress("UNUSED_PARAMETER") +@Component +class MyBean { + + @PulsarReader(topics = ["someTopic"], startMessageId = "earliest") + fun processMessage(content: String?) { + // ... + } + +} + diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/pulsar/readingreactive/MyBean.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/pulsar/readingreactive/MyBean.kt new file mode 100644 index 000000000000..7651be558113 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/pulsar/readingreactive/MyBean.kt @@ -0,0 +1,44 @@ +/* +* Copyright 2023-2023 the original author or authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package org.springframework.boot.docs.messaging.pulsar.readingreactive + +import org.apache.pulsar.client.api.Schema +import org.apache.pulsar.reactive.client.api.ReactiveMessageReaderBuilder +import org.apache.pulsar.reactive.client.api.StartAtSpec +import org.springframework.pulsar.reactive.core.ReactiveMessageReaderBuilderCustomizer +import org.springframework.pulsar.reactive.core.ReactivePulsarReaderFactory +import org.springframework.stereotype.Component +import java.time.Instant + +@Suppress("UNUSED_PARAMETER", "UNUSED_VARIABLE") +@Component +class MyBean(private val pulsarReaderFactory: ReactivePulsarReaderFactory) { + + fun someMethod() { + val readerBuilderCustomizer = ReactiveMessageReaderBuilderCustomizer { + readerBuilder: ReactiveMessageReaderBuilder -> + readerBuilder + .topic("someTopic") + .startAtSpec(StartAtSpec.ofInstant(Instant.now().minusSeconds(5))) + } + val message = pulsarReaderFactory + .createReader(Schema.STRING, listOf(readerBuilderCustomizer)) + .readOne() + // ... + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/pulsar/receiving/MyBean.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/pulsar/receiving/MyBean.kt new file mode 100644 index 000000000000..80ee6160ab43 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/pulsar/receiving/MyBean.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.messaging.pulsar.receiving + +import org.springframework.pulsar.annotation.PulsarListener +import org.springframework.stereotype.Component + +@Suppress("UNUSED_PARAMETER") +@Component +class MyBean { + + @PulsarListener(topics = ["someTopic"]) + fun processMessage(content: String?) { + // ... + } + +} + diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/pulsar/receivingreactive/MyBean.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/pulsar/receivingreactive/MyBean.kt new file mode 100644 index 000000000000..6434ff849225 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/pulsar/receivingreactive/MyBean.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.boot.docs.messaging.pulsar.receivingreactive + +import org.springframework.pulsar.reactive.config.annotation.ReactivePulsarListener +import org.springframework.stereotype.Component +import reactor.core.publisher.Mono + +@Component +@Suppress("UNUSED_PARAMETER") +class MyBean { + + @ReactivePulsarListener(topics = ["someTopic"]) + fun processMessage(content: String?): Mono { + // ... + return Mono.empty() + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/pulsar/sending/MyBean.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/pulsar/sending/MyBean.kt new file mode 100644 index 000000000000..8bff88ff696d --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/pulsar/sending/MyBean.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.messaging.pulsar.sending + +import org.apache.pulsar.client.api.PulsarClientException +import org.springframework.pulsar.core.PulsarTemplate +import org.springframework.stereotype.Component + +@Component +class MyBean(private val pulsarTemplate: PulsarTemplate) { + + @Throws(PulsarClientException::class) + fun someMethod() { + pulsarTemplate.send("someTopic", "Hello") + } + +} + diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/pulsar/sendingreactive/MyBean.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/pulsar/sendingreactive/MyBean.kt new file mode 100644 index 000000000000..3205912919ec --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/pulsar/sendingreactive/MyBean.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.boot.docs.messaging.pulsar.sendingreactive + +import org.springframework.pulsar.reactive.core.ReactivePulsarTemplate +import org.springframework.stereotype.Component + +@Component +class MyBean(private val pulsarTemplate: ReactivePulsarTemplate) { + + fun someMethod() { + pulsarTemplate.send("someTopic", "Hello").subscribe() + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/nativeimage/advanced/nestedconfigurationproperties/MyPropertiesKotlin.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/packaging/nativeimage/advanced/nestedconfigurationproperties/MyPropertiesKotlin.kt similarity index 90% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/nativeimage/advanced/nestedconfigurationproperties/MyPropertiesKotlin.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/packaging/nativeimage/advanced/nestedconfigurationproperties/MyPropertiesKotlin.kt index d00a5bd34269..f094e0bf742a 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/nativeimage/advanced/nestedconfigurationproperties/MyPropertiesKotlin.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/packaging/nativeimage/advanced/nestedconfigurationproperties/MyPropertiesKotlin.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.nativeimage.advanced.nestedconfigurationproperties +package org.springframework.boot.docs.packaging.nativeimage.advanced.nestedconfigurationproperties import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.NestedConfigurationProperty diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/testcontainers/dynamicproperties/MyIntegrationTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/testcontainers/dynamicproperties/MyIntegrationTests.kt new file mode 100644 index 000000000000..c2aa497a5702 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/testcontainers/dynamicproperties/MyIntegrationTests.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.testcontainers.dynamicproperties; + +import org.junit.jupiter.api.Test +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.DynamicPropertyRegistry +import org.springframework.test.context.DynamicPropertySource +import org.testcontainers.containers.Neo4jContainer +import org.testcontainers.junit.jupiter.Container +import org.testcontainers.junit.jupiter.Testcontainers + +@Testcontainers +@SpringBootTest +class MyIntegrationTests { + + @Test + fun myTest() { + // ... + } + + companion object { + @Container + @JvmStatic + val neo4j = Neo4jContainer("neo4j:5"); + + @DynamicPropertySource + @JvmStatic + fun neo4jProperties(registry: DynamicPropertyRegistry) { + registry.add("spring.neo4j.uri") { neo4j.boltUrl } + } + } +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/testcontainers/serviceconnections/MyIntegrationTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/testcontainers/serviceconnections/MyIntegrationTests.kt new file mode 100644 index 000000000000..a5e1071d544e --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/testcontainers/serviceconnections/MyIntegrationTests.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.testcontainers.serviceconnections; + +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.Neo4jContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +@Testcontainers +@SpringBootTest +class MyIntegrationTests { + + @Test + fun myTest() { + // ... + } + + companion object { + + @Container + @ServiceConnection + @JvmStatic + val neo4j = Neo4jContainer("neo4j:5"); + + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/testcontainers/serviceconnections/MyRedisConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/testcontainers/serviceconnections/MyRedisConfiguration.kt new file mode 100644 index 000000000000..fb5ac0340673 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/testcontainers/serviceconnections/MyRedisConfiguration.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.testcontainers.serviceconnections; + +import org.springframework.boot.test.context.TestConfiguration +import org.springframework.boot.testcontainers.service.connection.ServiceConnection +import org.springframework.context.annotation.Bean +import org.testcontainers.containers.GenericContainer + +@TestConfiguration(proxyBeanMethods = false) +class MyRedisConfiguration { + @Bean + @ServiceConnection(name = "redis") + fun redisContainer(): GenericContainer<*> { + return GenericContainer("redis:7") + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/testcontainers/vanilla/MyIntegrationTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/testcontainers/vanilla/MyIntegrationTests.kt new file mode 100644 index 000000000000..587ed84cce62 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/testcontainers/vanilla/MyIntegrationTests.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.testing.testcontainers.vanilla; + +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.Neo4jContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection + +@Testcontainers +@SpringBootTest +class MyIntegrationTests { + + @Test + fun myTest() { + // ... + } + + companion object { + @Container + @JvmStatic + val neo4j = Neo4jContainer("neo4j:5"); + } +} + diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/web/reactive/webflux/errorhandling/MyExceptionHandlingController.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/web/reactive/webflux/errorhandling/MyExceptionHandlingController.kt deleted file mode 100644 index 26dfa251d465..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/web/reactive/webflux/errorhandling/MyExceptionHandlingController.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.web.reactive.webflux.errorhandling - -import org.springframework.boot.web.reactive.error.ErrorAttributes -import org.springframework.stereotype.Controller -import org.springframework.web.bind.annotation.ExceptionHandler -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.reactive.result.view.Rendering -import org.springframework.web.server.ServerWebExchange - -@Suppress("UNUSED_PARAMETER") -@Controller -class MyExceptionHandlingController { - - @GetMapping("/profile") - fun userProfile(): Rendering { - // ... - throw IllegalStateException() - } - - @ExceptionHandler(IllegalStateException::class) - fun handleIllegalState(exchange: ServerWebExchange, exc: IllegalStateException): Rendering { - exchange.attributes.putIfAbsent(ErrorAttributes.ERROR_ATTRIBUTE, exc) - return Rendering.view("errorView").modelAttribute("message", exc.message ?: "").build() - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/web/servlet/springmvc/errorhandling/MyController.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/web/servlet/springmvc/errorhandling/MyController.kt deleted file mode 100644 index deead1e60da0..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/web/servlet/springmvc/errorhandling/MyController.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docs.web.servlet.springmvc.errorhandling - -import jakarta.servlet.http.HttpServletRequest -import org.springframework.boot.web.servlet.error.ErrorAttributes -import org.springframework.stereotype.Controller -import org.springframework.web.bind.annotation.ExceptionHandler - -@Controller -class MyController { - - @ExceptionHandler(CustomException::class) - fun handleCustomException(request: HttpServletRequest, ex: CustomException?): String { - request.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, ex) - return "errorView" - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/features/testing/springbootapplications/jmx/MyJmxTestsTests.java b/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/testing/springbootapplications/jmx/MyJmxTestsTests.java similarity index 83% rename from spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/features/testing/springbootapplications/jmx/MyJmxTestsTests.java rename to spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/testing/springbootapplications/jmx/MyJmxTestsTests.java index fb9e56d21734..f779324ffdfe 100644 --- a/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/features/testing/springbootapplications/jmx/MyJmxTestsTests.java +++ b/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/testing/springbootapplications/jmx/MyJmxTestsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.jmx; +package org.springframework.boot.docs.testing.springbootapplications.jmx; /** * Tests for SampleJmxTests diff --git a/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/features/testing/utilities/outputcapture/MyOutputCaptureTestsTests.java b/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/testing/utilities/outputcapture/MyOutputCaptureTestsTests.java similarity index 84% rename from spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/features/testing/utilities/outputcapture/MyOutputCaptureTestsTests.java rename to spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/testing/utilities/outputcapture/MyOutputCaptureTestsTests.java index d7f587b283b3..1d004714a233 100644 --- a/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/features/testing/utilities/outputcapture/MyOutputCaptureTestsTests.java +++ b/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/testing/utilities/outputcapture/MyOutputCaptureTestsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.utilities.outputcapture; +package org.springframework.boot.docs.testing.utilities.outputcapture; /** * Tests for {@link MyOutputCaptureTests}. diff --git a/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MySpringBootTestsTests.java b/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/testing/utilities/testresttemplate/MySpringBootTestsTests.java similarity index 83% rename from spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MySpringBootTestsTests.java rename to spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/testing/utilities/testresttemplate/MySpringBootTestsTests.java index d91bc5fb7e9a..e463e5aa6401 100644 --- a/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MySpringBootTestsTests.java +++ b/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/testing/utilities/testresttemplate/MySpringBootTestsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.utilities.testresttemplate; +package org.springframework.boot.docs.testing.utilities.testresttemplate; /** * Tests for {@link MySpringBootTests}. diff --git a/spring-boot-project/spring-boot-parent/build.gradle b/spring-boot-project/spring-boot-parent/build.gradle index 48b8d88b3b6f..72c981910180 100644 --- a/spring-boot-project/spring-boot-parent/build.gradle +++ b/spring-boot-project/spring-boot-parent/build.gradle @@ -30,11 +30,11 @@ bom { library("C3P0", "0.9.5.5") { group("com.mchange") { modules = [ - "c3p0" + "c3p0" ] } } - library("Commons Compress", "1.21") { + library("Commons Compress", "1.25.0") { group("org.apache.commons") { modules = [ "commons-compress" @@ -48,10 +48,10 @@ bom { ] } } - library("Jakarta Inject", "2.0.1") { - group("jakarta.inject") { + library("CycloneDX Gradle Plugin", "1.8.2") { + group("org.cyclonedx") { modules = [ - "jakarta.inject-api" + "cyclonedx-gradle-plugin" ] } } @@ -73,7 +73,7 @@ bom { ] } } - library("JNA", "5.7.0") { + library("JNA", "5.13.0") { group("net.java.dev.jna") { modules = [ "jna-platform" @@ -97,7 +97,7 @@ bom { ] } } - library("Maven Common Artifact Filters", "3.2.0") { + library("Maven Common Artifact Filters", "3.3.2") { group("org.apache.maven.shared") { modules = [ "maven-common-artifact-filters" @@ -111,7 +111,7 @@ bom { ] } } - library("Maven Plugin Tools", "3.6.0") { + library("Maven Plugin Tools", "3.9.0") { group("org.apache.maven.plugin-tools") { modules = [ "maven-plugin-annotations" @@ -131,13 +131,20 @@ bom { ] } } - library("Maven Shade Plugin", "3.2.4") { + library("Maven Shade Plugin", "3.5.0") { group("org.apache.maven.plugins") { modules = [ "maven-shade-plugin" ] } } + library("Micrometer Context Propagation", "1.0.5") { + group("io.micrometer") { + modules = [ + "context-propagation" + ] + } + } library("MockK", "1.13.5") { group("io.mockk") { modules = [ @@ -194,14 +201,6 @@ bom { ] } } - library("Spring Asciidoctor Extensions", "0.6.3") { - group("io.spring.asciidoctor") { - modules = [ - "spring-asciidoctor-extensions-spring-boot", - "spring-asciidoctor-extensions-section-ids" - ] - } - } } dependencies { diff --git a/spring-boot-project/spring-boot-starters/README.adoc b/spring-boot-project/spring-boot-starters/README.adoc index cabb28016056..f685eb1ce66b 100644 --- a/spring-boot-project/spring-boot-starters/README.adoc +++ b/spring-boot-project/spring-boot-starters/README.adoc @@ -11,6 +11,7 @@ For complete details see the https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using-boot-starter[reference documentation] == Community Contributions + If you create a starter for a technology that is not already in the standard list we can list it here. To ask us to do so, please open a pull request that updates this page. @@ -113,7 +114,7 @@ do as they were designed before this was clarified. | https://github.com/dabla/grizzly-spring-boot-starter | https://www.grpc.io/[gRPC] -| https://github.com/LogNet/grpc-spring-boot-starter & https://github.com/yidongnan/grpc-spring-boot-starter +| https://github.com/LogNet/grpc-spring-boot-starter & https://github.com/yidongnan/grpc-spring-boot-starter & https://github.com/DanielLiu1123/grpc-starter | https://ha-jdbc.github.io/[HA JDBC] | https://github.com/lievendoclo/hajdbc-spring-boot @@ -154,6 +155,9 @@ do as they were designed before this was clarified. | https://kogito.kie.org/[Kogito] | https://github.com/kiegroup/kogito-runtimes/tree/main/springboot/starters +| https://github.com/langchain4j/langchain4j[LangChain for Java] +| https://github.com/langchain4j/langchain4j/tree/main/langchain4j-spring-boot-starter + | https://www.liquigraph.org/[Liquigraph] | https://github.com/liquigraph/liquigraph @@ -172,6 +176,9 @@ do as they were designed before this was clarified. | https://developer.nexmo.com/[Nexmo] | https://github.com/nexmo/nexmo-spring-boot-starter +| https://github.com/nostr-protocol/nostr[Nostr] +| https://github.com/theborakompanioni/nostr-spring-boot-starter + | https://github.com/nutzam/nutz[Nutz] | https://github.com/nutzam/nutzmore @@ -184,6 +191,9 @@ do as they were designed before this was clarified. | https://developer.okta.com/[Okta] | https://github.com/okta/okta-spring-boot +| https://opentelemetry.io/docs/languages/java/automatic/spring-boot/#opentelemetry-spring-boot-starter[OpenTelemetry] +| https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/spring/starters/spring-boot-starter + | https://www.optaplanner.org/[OptaPlanner] | https://github.com/kiegroup/optaplanner/tree/master/optaplanner-spring-integration/optaplanner-spring-boot-starter @@ -226,6 +236,9 @@ do as they were designed before this was clarified. | https://projects.spring.io/spring-batch/[Spring Batch] (Advanced usage) | https://github.com/codecentric/spring-boot-starter-batch-web +| https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#rest-http-interface[Spring Http Interface] +| https://github.com/DanielLiu1123/httpexchange-spring-boot-starter + | https://projects.spring.io/spring-shell/[Spring Shell] | https://github.com/fonimus/ssh-shell-spring-boot diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-activemq/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-activemq/build.gradle index 622bef10b470..72f13cd35dfe 100644 --- a/spring-boot-project/spring-boot-starters/spring-boot-starter-activemq/build.gradle +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-activemq/build.gradle @@ -7,5 +7,5 @@ description = "Starter for JMS messaging using Apache ActiveMQ" dependencies { api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter")) api("org.springframework:spring-jms") - api("org.apache.activemq:activemq-client-jakarta") + api("org.apache.activemq:activemq-client") } diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-actuator/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-actuator/build.gradle index 3e2a471593a3..0ff356b4e0d9 100644 --- a/spring-boot-project/spring-boot-starters/spring-boot-starter-actuator/build.gradle +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-actuator/build.gradle @@ -8,5 +8,5 @@ dependencies { api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter")) api(project(":spring-boot-project:spring-boot-actuator-autoconfigure")) api("io.micrometer:micrometer-observation") - api("io.micrometer:micrometer-core") + api("io.micrometer:micrometer-jakarta9") } diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-data-redis-reactive/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-data-redis-reactive/build.gradle index d66f98dcfc40..f0c2d2bb4b22 100644 --- a/spring-boot-project/spring-boot-starters/spring-boot-starter-data-redis-reactive/build.gradle +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-data-redis-reactive/build.gradle @@ -5,5 +5,8 @@ plugins { description = "Starter for using Redis key-value data store with Spring Data Redis reactive and the Lettuce client" dependencies { - api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-data-redis")) + api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter")) + api("io.lettuce:lettuce-core") + api("io.projectreactor:reactor-core") + api("org.springframework.data:spring-data-redis") } diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-data-redis/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-data-redis/build.gradle index 11f150cd1eec..b76ff7e34fe1 100644 --- a/spring-boot-project/spring-boot-starters/spring-boot-starter-data-redis/build.gradle +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-data-redis/build.gradle @@ -6,6 +6,6 @@ description = "Starter for using Redis key-value data store with Spring Data Red dependencies { api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter")) - api("org.springframework.data:spring-data-redis") api("io.lettuce:lettuce-core") + api("org.springframework.data:spring-data-redis") } diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-jetty/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-jetty/build.gradle index 5d677c3fe598..6be8cf0f9cd6 100644 --- a/spring-boot-project/spring-boot-starters/spring-boot-starter-jetty/build.gradle +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-jetty/build.gradle @@ -9,15 +9,14 @@ dependencies { api("jakarta.websocket:jakarta.websocket-api") api("jakarta.websocket:jakarta.websocket-client-api") api("org.apache.tomcat.embed:tomcat-embed-el") - api("org.eclipse.jetty:jetty-servlets") - api("org.eclipse.jetty:jetty-webapp") { - exclude(group: "org.eclipse.jetty.toolchain", module: "jetty-jakarta-servlet-api") + api("org.eclipse.jetty.ee10:jetty-ee10-servlets") + api("org.eclipse.jetty.ee10:jetty-ee10-webapp") + api("org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-server") { + exclude group: "jakarta.el", module: "jakarta.el-api" + exclude group: "org.eclipse.jetty", module: "jetty-jndi" } - api("org.eclipse.jetty.websocket:websocket-jakarta-server") { - exclude(group: "org.eclipse.jetty.toolchain", module: "jetty-jakarta-servlet-api") - exclude(group: "org.eclipse.jetty.toolchain", module: "jetty-jakarta-websocket-api") - } - api("org.eclipse.jetty.websocket:websocket-jetty-server") { - exclude(group: "org.eclipse.jetty.toolchain", module: "jetty-jakarta-servlet-api") + api("org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-server") { + exclude group: "jakarta.el", module: "jakarta.el-api" + exclude group: "org.eclipse.jetty", module: "jetty-jndi" } } diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-parent/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-parent/build.gradle index a57809d9535d..a9a020db4ebb 100644 --- a/spring-boot-project/spring-boot-starters/spring-boot-starter-parent/build.gradle +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-parent/build.gradle @@ -24,6 +24,7 @@ publishing.publications.withType(MavenPublication) { delegate."maven.compiler.release"('${java.version}') delegate."project.build.sourceEncoding"('UTF-8') delegate."project.reporting.outputEncoding"('UTF-8') + delegate."spring-boot.run.main-class"('${start-class}') } } root.scm.plus { @@ -148,11 +149,28 @@ publishing.publications.withType(MavenPublication) { } configuration { delegate.verbose('true') - delegate.dateFormat("yyyy-MM-dd'T'HH:mm:ssZ") delegate.generateGitPropertiesFile('true') delegate.generateGitPropertiesFilename('${project.build.outputDirectory}/git.properties') } } + plugin { + delegate.groupId('org.cyclonedx') + delegate.artifactId('cyclonedx-maven-plugin') + executions { + execution { + delegate.phase('generate-resources') + goals { + delegate.goal('makeAggregateBom') + } + configuration { + delegate.projectType('application') + delegate.outputDirectory('${project.build.outputDirectory}/META-INF/sbom') + delegate.outputFormat('json') + delegate.outputName('application.cdx') + } + } + } + } plugin { delegate.groupId('org.springframework.boot') delegate.artifactId('spring-boot-maven-plugin') @@ -165,7 +183,7 @@ publishing.publications.withType(MavenPublication) { } } configuration { - delegate.mainClass('${start-class}') + delegate.mainClass('${spring-boot.run.main-class}') } } plugin { @@ -249,7 +267,6 @@ publishing.publications.withType(MavenPublication) { delegate.artifactId('spring-boot-maven-plugin') configuration { image { - delegate.builder("paketobuildpacks/builder-jammy-tiny:latest") env { delegate.BP_NATIVE_IMAGE("true") } @@ -269,9 +286,6 @@ publishing.publications.withType(MavenPublication) { delegate.artifactId('native-maven-plugin') configuration { delegate.classesDirectory('${project.build.outputDirectory}') - metadataRepository { - delegate.enabled('true') - } delegate.requiredVersion('22.3') } executions { @@ -315,9 +329,6 @@ publishing.publications.withType(MavenPublication) { delegate.artifactId('native-maven-plugin') configuration { delegate.classesDirectory('${project.build.outputDirectory}') - metadataRepository { - delegate.enabled('true') - } delegate.requiredVersion('22.3') } executions { diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-pulsar-reactive/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-pulsar-reactive/build.gradle new file mode 100644 index 000000000000..af2c3ce6462a --- /dev/null +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-pulsar-reactive/build.gradle @@ -0,0 +1,16 @@ +plugins { + id "org.springframework.boot.starter" +} + +description = "Starter for using Spring for Apache Pulsar Reactive" + +dependencies { + api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter")) + api("org.springframework.pulsar:spring-pulsar-reactive") +} + +checkRuntimeClasspathForConflicts { + ignore { name -> name.startsWith("org/bouncycastle/") || + name.matches("^org/apache/pulsar/.*/package-info.class\$") || + name.equals("findbugsExclude.xml") } +} diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-pulsar/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-pulsar/build.gradle new file mode 100644 index 000000000000..65f40e5b09a3 --- /dev/null +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-pulsar/build.gradle @@ -0,0 +1,16 @@ +plugins { + id "org.springframework.boot.starter" +} + +description = "Starter for using Spring for Apache Pulsar" + +dependencies { + api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter")) + api("org.springframework.pulsar:spring-pulsar") +} + +checkRuntimeClasspathForConflicts { + ignore { name -> name.startsWith("org/bouncycastle/") || + name.matches("^org/apache/pulsar/.*/package-info.class\$") || + name.equals("findbugsExclude.xml") } +} diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-test/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-test/build.gradle index f5a2cc091382..e98d71281766 100644 --- a/spring-boot-project/spring-boot-starters/spring-boot-starter-test/build.gradle +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-test/build.gradle @@ -12,6 +12,7 @@ dependencies { api("jakarta.xml.bind:jakarta.xml.bind-api") api("net.minidev:json-smart") api("org.assertj:assertj-core") + api("org.awaitility:awaitility") api("org.hamcrest:hamcrest") api("org.junit.jupiter:junit-jupiter") api("org.mockito:mockito-core") diff --git a/spring-boot-project/spring-boot-test-autoconfigure/build.gradle b/spring-boot-project/spring-boot-test-autoconfigure/build.gradle index 623831519c1c..fe40c29b7bc0 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-test-autoconfigure/build.gradle @@ -2,6 +2,7 @@ plugins { id "java-library" id "org.springframework.boot.conventions" id "org.springframework.boot.deployed" + id "org.springframework.boot.docker-test" id "org.springframework.boot.optional-dependencies" } @@ -12,6 +13,22 @@ dependencies { api(project(":spring-boot-project:spring-boot-test")) api(project(":spring-boot-project:spring-boot-autoconfigure")) + dockerTestImplementation(project(":spring-boot-project:spring-boot-testcontainers")) + dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker")) + dockerTestImplementation("io.projectreactor:reactor-test") + dockerTestImplementation("org.assertj:assertj-core") + dockerTestImplementation("org.junit.jupiter:junit-jupiter") + dockerTestImplementation("org.testcontainers:cassandra") + dockerTestImplementation("org.testcontainers:couchbase") + dockerTestImplementation("org.testcontainers:elasticsearch") + dockerTestImplementation("org.testcontainers:junit-jupiter") + dockerTestImplementation("org.testcontainers:mongodb") + dockerTestImplementation("org.testcontainers:neo4j") + dockerTestImplementation("org.testcontainers:testcontainers") + + dockerTestRuntimeOnly("io.lettuce:lettuce-core") + dockerTestRuntimeOnly("org.springframework.data:spring-data-redis") + optional("jakarta.json.bind:jakarta.json.bind-api") optional("jakarta.persistence:jakarta.persistence-api") optional("jakarta.servlet:jakarta.servlet-api") @@ -20,12 +37,12 @@ dependencies { optional("com.google.code.gson:gson") optional("com.jayway.jsonpath:json-path") optional("com.sun.xml.messaging.saaj:saaj-impl") - optional("net.sourceforge.htmlunit:htmlunit") { + optional("org.hibernate.orm:hibernate-core") + optional("org.htmlunit:htmlunit") { exclude group: "commons-logging", module: "commons-logging" } - optional("org.hibernate.orm:hibernate-core") optional("org.junit.jupiter:junit-jupiter-api") - optional("org.seleniumhq.selenium:htmlunit-driver") { + optional("org.seleniumhq.selenium:htmlunit3-driver") { exclude(group: "commons-logging", module: "commons-logging") exclude(group: "com.sun.activation", module: "jakarta.activation") } @@ -62,14 +79,14 @@ dependencies { testImplementation(project(":spring-boot-project:spring-boot-actuator")) testImplementation(project(":spring-boot-project:spring-boot-actuator-autoconfigure")) - testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation(project(":spring-boot-project:spring-boot-testcontainers")) + testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation("ch.qos.logback:logback-classic") testImplementation("com.fasterxml.jackson.module:jackson-module-parameter-names") testImplementation("com.h2database:h2") testImplementation("com.unboundid:unboundid-ldapsdk") testImplementation("io.lettuce:lettuce-core") - testImplementation("io.micrometer:micrometer-registry-prometheus") + testImplementation("io.micrometer:micrometer-registry-prometheus-simpleclient") testImplementation("io.projectreactor.netty:reactor-netty-http") testImplementation("io.projectreactor:reactor-core") testImplementation("io.projectreactor:reactor-test") @@ -96,13 +113,6 @@ dependencies { testImplementation("org.springframework:spring-core-test") testImplementation("org.springframework.hateoas:spring-hateoas") testImplementation("org.springframework.plugin:spring-plugin-core") - testImplementation("org.testcontainers:cassandra") - testImplementation("org.testcontainers:couchbase") - testImplementation("org.testcontainers:elasticsearch") - testImplementation("org.testcontainers:junit-jupiter") - testImplementation("org.testcontainers:mongodb") - testImplementation("org.testcontainers:neo4j") - testImplementation("org.testcontainers:testcontainers") testImplementation("org.thymeleaf:thymeleaf") } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestIntegrationTests.java similarity index 91% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestIntegrationTests.java index f505a6fa2353..5fd24430d24e 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.CqlSessionBuilder; import org.junit.jupiter.api.Test; +import org.testcontainers.containers.CassandraContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -30,7 +31,7 @@ import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testcontainers.service.connection.ServiceConnectionAutoConfiguration; -import org.springframework.boot.testsupport.testcontainers.CassandraContainer; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.data.cassandra.core.CassandraTemplate; @@ -55,7 +56,7 @@ class DataCassandraTestIntegrationTests { @Container @ServiceConnection - static final CassandraContainer cassandra = new CassandraContainer(); + static final CassandraContainer cassandra = TestImage.container(CassandraContainer.class); @Autowired private CassandraTemplate cassandraTemplate; @@ -98,7 +99,7 @@ static class KeyspaceTestConfiguration { CqlSession cqlSession(CqlSessionBuilder cqlSessionBuilder) { try (CqlSession session = cqlSessionBuilder.build()) { session.execute("CREATE KEYSPACE IF NOT EXISTS boot_test" - + " WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"); + + " WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"); } return cqlSessionBuilder.withKeyspace("boot_test").build(); } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestWithIncludeFilterIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestWithIncludeFilterIntegrationTests.java similarity index 88% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestWithIncludeFilterIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestWithIncludeFilterIntegrationTests.java index 05741e8a91de..1056a9ac24a2 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestWithIncludeFilterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestWithIncludeFilterIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,13 +21,14 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.CqlSessionBuilder; import org.junit.jupiter.api.Test; +import org.testcontainers.containers.CassandraContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.CassandraContainer; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.stereotype.Service; @@ -52,7 +53,7 @@ class DataCassandraTestWithIncludeFilterIntegrationTests { @Container @ServiceConnection - static final CassandraContainer cassandra = new CassandraContainer(); + static final CassandraContainer cassandra = TestImage.container(CassandraContainer.class); @Autowired private ExampleRepository exampleRepository; @@ -77,7 +78,7 @@ static class KeyspaceTestConfiguration { CqlSession cqlSession(CqlSessionBuilder cqlSessionBuilder) { try (CqlSession session = cqlSessionBuilder.build()) { session.execute("CREATE KEYSPACE IF NOT EXISTS boot_test" - + " WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"); + + " WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"); } return cqlSessionBuilder.withKeyspace("boot_test").build(); } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/ExampleCassandraApplication.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/cassandra/ExampleCassandraApplication.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/ExampleCassandraApplication.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/cassandra/ExampleCassandraApplication.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/ExampleEntity.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/cassandra/ExampleEntity.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/ExampleEntity.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/cassandra/ExampleEntity.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/ExampleRepository.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/cassandra/ExampleRepository.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/ExampleRepository.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/cassandra/ExampleRepository.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/ExampleService.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/cassandra/ExampleService.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/ExampleService.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/cassandra/ExampleService.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestIntegrationTests.java similarity index 90% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestIntegrationTests.java index 2ebef2224b6f..0609288b90b7 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.boot.test.autoconfigure.data.couchbase; -import java.time.Duration; - import org.junit.jupiter.api.Test; import org.testcontainers.couchbase.BucketDefinition; import org.testcontainers.couchbase.CouchbaseContainer; @@ -29,7 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testcontainers.service.connection.ServiceConnectionAutoConfiguration; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.ApplicationContext; import org.springframework.data.couchbase.core.CouchbaseTemplate; @@ -54,10 +52,8 @@ class DataCouchbaseTestIntegrationTests { @Container @ServiceConnection - static final CouchbaseContainer couchbase = new CouchbaseContainer(DockerImageNames.couchbase()) + static final CouchbaseContainer couchbase = TestImage.container(CouchbaseContainer.class) .withEnabledServices(CouchbaseService.KV, CouchbaseService.INDEX, CouchbaseService.QUERY) - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)) .withBucket(new BucketDefinition(BUCKET_NAME)); @Autowired diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestReactiveIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestReactiveIntegrationTests.java similarity index 89% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestReactiveIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestReactiveIntegrationTests.java index 7730acd9426d..341561d8dc89 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestReactiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestReactiveIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate; import static org.assertj.core.api.Assertions.assertThat; @@ -50,10 +50,8 @@ class DataCouchbaseTestReactiveIntegrationTests { @Container @ServiceConnection - static final CouchbaseContainer couchbase = new CouchbaseContainer(DockerImageNames.couchbase()) + static final CouchbaseContainer couchbase = TestImage.container(CouchbaseContainer.class) .withEnabledServices(CouchbaseService.KV, CouchbaseService.INDEX, CouchbaseService.QUERY) - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)) .withBucket(new BucketDefinition(BUCKET_NAME)); @Autowired diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestWithIncludeFilterIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestWithIncludeFilterIntegrationTests.java similarity index 84% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestWithIncludeFilterIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestWithIncludeFilterIntegrationTests.java index 7c6c89f1cfaa..83bc681803c9 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestWithIncludeFilterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestWithIncludeFilterIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.boot.test.autoconfigure.data.couchbase; -import java.time.Duration; - import org.junit.jupiter.api.Test; import org.testcontainers.couchbase.BucketDefinition; import org.testcontainers.couchbase.CouchbaseContainer; @@ -27,7 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.stereotype.Service; @@ -48,15 +46,11 @@ @Testcontainers(disabledWithoutDocker = true) class DataCouchbaseTestWithIncludeFilterIntegrationTests { - private static final String BUCKET_NAME = "cbbucket"; - @Container @ServiceConnection - static final CouchbaseContainer couchbase = new CouchbaseContainer(DockerImageNames.couchbase()) + static final CouchbaseContainer couchbase = TestImage.container(CouchbaseContainer.class) .withEnabledServices(CouchbaseService.KV, CouchbaseService.INDEX, CouchbaseService.QUERY) - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)) - .withBucket(new BucketDefinition(BUCKET_NAME)); + .withBucket(new BucketDefinition("cbbucket")); @Autowired private ExampleRepository exampleRepository; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleCouchbaseApplication.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleCouchbaseApplication.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleCouchbaseApplication.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleCouchbaseApplication.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleDocument.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleDocument.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleDocument.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleDocument.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleReactiveRepository.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleReactiveRepository.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleReactiveRepository.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleReactiveRepository.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleRepository.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleRepository.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleRepository.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleRepository.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleService.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleService.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleService.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/couchbase/ExampleService.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestIntegrationTests.java similarity index 88% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestIntegrationTests.java index 5cdf5a56624a..71afd239f63f 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.springframework.boot.test.autoconfigure.data.elasticsearch; -import java.time.Duration; import java.util.UUID; import org.junit.jupiter.api.Test; @@ -28,7 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testcontainers.service.connection.ServiceConnectionAutoConfiguration; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.ApplicationContext; import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate; @@ -37,7 +36,7 @@ import static org.springframework.boot.test.autoconfigure.AutoConfigurationImportedCondition.importedAutoConfiguration; /** - * Sample test for {@link DataElasticsearchTest @DataElasticsearchTest} + * Sample test for {@link DataElasticsearchTest @DataElasticsearchTest}. * * @author Eddú Meléndez * @author Moritz Halbritter @@ -50,10 +49,7 @@ class DataElasticsearchTestIntegrationTests { @Container @ServiceConnection - static final ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) - .withEnv("ES_JAVA_OPTS", "-Xms32m -Xmx512m") - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + static final ElasticsearchContainer elasticsearch = TestImage.container(ElasticsearchContainer.class); @Autowired private ElasticsearchTemplate elasticsearchTemplate; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestPropertiesIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestPropertiesIntegrationTests.java similarity index 84% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestPropertiesIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestPropertiesIntegrationTests.java index de0a64d18308..37542392f550 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestPropertiesIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestPropertiesIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.boot.test.autoconfigure.data.elasticsearch; -import java.time.Duration; - import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.testcontainers.elasticsearch.ElasticsearchContainer; @@ -26,7 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.core.env.Environment; import static org.assertj.core.api.Assertions.assertThat; @@ -46,10 +44,7 @@ class DataElasticsearchTestPropertiesIntegrationTests { @Container @ServiceConnection - static final ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) - .withEnv("ES_JAVA_OPTS", "-Xms32m -Xmx512m") - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + static final ElasticsearchContainer elasticsearch = TestImage.container(ElasticsearchContainer.class); @Autowired private Environment environment; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestReactiveIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestReactiveIntegrationTests.java similarity index 85% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestReactiveIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestReactiveIntegrationTests.java index 45d6e6c74efb..7d386967951a 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestReactiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestReactiveIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchTemplate; import static org.assertj.core.api.Assertions.assertThat; @@ -45,10 +45,7 @@ class DataElasticsearchTestReactiveIntegrationTests { @Container @ServiceConnection - static final ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) - .withEnv("ES_JAVA_OPTS", "-Xms32m -Xmx512m") - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + static final ElasticsearchContainer elasticsearch = TestImage.container(ElasticsearchContainer.class); @Autowired private ReactiveElasticsearchTemplate elasticsearchTemplate; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestWithIncludeFilterIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestWithIncludeFilterIntegrationTests.java similarity index 84% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestWithIncludeFilterIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestWithIncludeFilterIntegrationTests.java index 60aebb21d8e7..edb04d9df2cf 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestWithIncludeFilterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestWithIncludeFilterIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.springframework.boot.test.autoconfigure.data.elasticsearch; -import java.time.Duration; import java.util.UUID; import org.junit.jupiter.api.Test; @@ -26,7 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.stereotype.Service; @@ -47,10 +46,7 @@ class DataElasticsearchTestWithIncludeFilterIntegrationTests { @Container @ServiceConnection - static final ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) - .withEnv("ES_JAVA_OPTS", "-Xms32m -Xmx512m") - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + static final ElasticsearchContainer elasticsearch = TestImage.container(ElasticsearchContainer.class); @Autowired private ExampleRepository exampleRepository; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleDocument.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleDocument.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleDocument.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleDocument.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleElasticsearchApplication.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleElasticsearchApplication.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleElasticsearchApplication.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleElasticsearchApplication.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleReactiveRepository.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleReactiveRepository.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleReactiveRepository.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleReactiveRepository.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleRepository.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleRepository.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleRepository.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleRepository.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleService.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleService.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleService.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/ExampleService.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/ldap/DataLdapTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/ldap/DataLdapTestIntegrationTests.java new file mode 100644 index 000000000000..bda7b4c7deb3 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/ldap/DataLdapTestIntegrationTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.data.ldap; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnectionAutoConfiguration; +import org.springframework.boot.testsupport.container.OpenLdapContainer; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.ApplicationContext; +import org.springframework.ldap.core.AttributesMapper; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.query.LdapQueryBuilder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.autoconfigure.AutoConfigurationImportedCondition.importedAutoConfiguration; + +/** + * Sample test for {@link DataLdapTest @DataLdapTest}. + * + * @author Eddú Meléndez + */ +@DataLdapTest +@Testcontainers(disabledWithoutDocker = true) +public class DataLdapTestIntegrationTests { + + @Container + @ServiceConnection + static final OpenLdapContainer openLdap = TestImage.container(OpenLdapContainer.class).withEnv("LDAP_TLS", "false"); + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private LdapTemplate ldapTemplate; + + @Test + void connectionCanBeMadeToLdapContainer() { + List cn = this.ldapTemplate.search(LdapQueryBuilder.query().where("objectclass").is("dcObject"), + (AttributesMapper) (attributes) -> attributes.get("dc").get().toString()); + assertThat(cn).hasSize(1); + assertThat(cn.get(0)).isEqualTo("example"); + } + + @Test + void serviceConnectionAutoConfigurationWasImported() { + assertThat(this.applicationContext).has(importedAutoConfiguration(ServiceConnectionAutoConfiguration.class)); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestIntegrationTests.java similarity index 87% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestIntegrationTests.java index aaa55491c716..3c1da4bc5f42 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.boot.test.autoconfigure.data.mongo; -import java.time.Duration; - import org.junit.jupiter.api.Test; import org.testcontainers.containers.MongoDBContainer; import org.testcontainers.junit.jupiter.Container; @@ -27,7 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testcontainers.service.connection.ServiceConnectionAutoConfiguration; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.ApplicationContext; import org.springframework.data.mongodb.core.MongoTemplate; @@ -36,7 +34,7 @@ import static org.springframework.boot.test.autoconfigure.AutoConfigurationImportedCondition.importedAutoConfiguration; /** - * Sample test for {@link DataMongoTest @DataMongoTest} + * Sample test for {@link DataMongoTest @DataMongoTest}. * * @author Michael Simons * @author Moritz Halbritter @@ -49,8 +47,7 @@ class DataMongoTestIntegrationTests { @Container @ServiceConnection - static final MongoDBContainer mongoDB = new MongoDBContainer(DockerImageNames.mongo()).withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(5)); + static final MongoDBContainer mongoDb = TestImage.container(MongoDBContainer.class); @Autowired private MongoTemplate mongoTemplate; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestReactiveIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestReactiveIntegrationTests.java similarity index 87% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestReactiveIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestReactiveIntegrationTests.java index 8adb740b4597..2b2b06aceaa5 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestReactiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestReactiveIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import static org.assertj.core.api.Assertions.assertThat; @@ -44,8 +44,7 @@ class DataMongoTestReactiveIntegrationTests { @Container @ServiceConnection - static final MongoDBContainer mongoDB = new MongoDBContainer(DockerImageNames.mongo()).withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(5)); + static final MongoDBContainer mongoDb = TestImage.container(MongoDBContainer.class); @Autowired private ReactiveMongoTemplate mongoTemplate; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestWithIncludeFilterIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestWithIncludeFilterIntegrationTests.java similarity index 84% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestWithIncludeFilterIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestWithIncludeFilterIntegrationTests.java index 24e023919654..97f6e84876c4 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestWithIncludeFilterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestWithIncludeFilterIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.boot.test.autoconfigure.data.mongo; -import java.time.Duration; - import org.junit.jupiter.api.Test; import org.testcontainers.containers.MongoDBContainer; import org.testcontainers.junit.jupiter.Container; @@ -25,7 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.stereotype.Service; @@ -45,8 +43,7 @@ class DataMongoTestWithIncludeFilterIntegrationTests { @Container @ServiceConnection - static final MongoDBContainer mongoDB = new MongoDBContainer(DockerImageNames.mongo()).withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(5)); + static final MongoDBContainer mongoDb = TestImage.container(MongoDBContainer.class); @Autowired private ExampleService service; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleDocument.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleDocument.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleDocument.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleDocument.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleMongoApplication.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleMongoApplication.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleMongoApplication.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleMongoApplication.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleReactiveRepository.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleReactiveRepository.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleReactiveRepository.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleReactiveRepository.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleRepository.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleRepository.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleRepository.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleRepository.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleService.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleService.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleService.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/ExampleService.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/TransactionalDataMongoTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/TransactionalDataMongoTestIntegrationTests.java similarity index 90% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/TransactionalDataMongoTestIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/TransactionalDataMongoTestIntegrationTests.java index c76dad56e368..655650ddf38f 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/TransactionalDataMongoTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/mongo/TransactionalDataMongoTestIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.boot.test.autoconfigure.data.mongo; -import java.time.Duration; - import org.junit.jupiter.api.Test; import org.testcontainers.containers.MongoDBContainer; import org.testcontainers.junit.jupiter.Container; @@ -27,7 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Bean; import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.MongoTransactionManager; @@ -50,8 +48,7 @@ class TransactionalDataMongoTestIntegrationTests { @Container @ServiceConnection - static final MongoDBContainer mongoDB = new MongoDBContainer(DockerImageNames.mongo()).withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(5)); + static final MongoDBContainer mongoDb = TestImage.container(MongoDBContainer.class); @Autowired private ExampleRepository exampleRepository; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestIntegrationTests.java similarity index 89% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestIntegrationTests.java index 0d1f95995c7b..9554ccd65d70 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.boot.test.autoconfigure.data.neo4j; -import java.time.Duration; - import org.junit.jupiter.api.Test; import org.testcontainers.containers.Neo4jContainer; import org.testcontainers.junit.jupiter.Container; @@ -27,7 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testcontainers.service.connection.ServiceConnectionAutoConfiguration; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.ApplicationContext; import org.springframework.data.neo4j.core.Neo4jTemplate; @@ -51,8 +49,7 @@ class DataNeo4jTestIntegrationTests { @Container @ServiceConnection - static final Neo4jContainer neo4j = new Neo4jContainer<>(DockerImageNames.neo4j()).withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + static final Neo4jContainer neo4j = TestImage.container(Neo4jContainer.class); @Autowired private Neo4jTemplate neo4jTemplate; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestPropertiesIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestPropertiesIntegrationTests.java similarity index 85% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestPropertiesIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestPropertiesIntegrationTests.java index 0f97ff5f4de3..8e7d1b0163ec 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestPropertiesIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestPropertiesIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.boot.test.autoconfigure.data.neo4j; -import java.time.Duration; - import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.testcontainers.containers.Neo4jContainer; @@ -26,7 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.core.env.Environment; import static org.assertj.core.api.Assertions.assertThat; @@ -46,9 +44,7 @@ class DataNeo4jTestPropertiesIntegrationTests { @Container @ServiceConnection - static final Neo4jContainer neo4j = new Neo4jContainer<>(DockerImageNames.neo4j()).withoutAuthentication() - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + static final Neo4jContainer neo4j = TestImage.container(Neo4jContainer.class).withoutAuthentication(); @Autowired private Environment environment; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestReactiveIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestReactiveIntegrationTests.java similarity index 91% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestReactiveIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestReactiveIntegrationTests.java index 749af312beef..2ffcb33c329c 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestReactiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestReactiveIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.data.neo4j.core.ReactiveDatabaseSelectionProvider; @@ -57,9 +57,7 @@ class DataNeo4jTestReactiveIntegrationTests { @Container @ServiceConnection - static final Neo4jContainer neo4j = new Neo4jContainer<>(DockerImageNames.neo4j()).withoutAuthentication() - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + static final Neo4jContainer neo4j = TestImage.container(Neo4jContainer.class).withoutAuthentication(); @Autowired private ReactiveNeo4jTemplate neo4jTemplate; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestWithIncludeFilterIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestWithIncludeFilterIntegrationTests.java similarity index 83% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestWithIncludeFilterIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestWithIncludeFilterIntegrationTests.java index f2a4a822addf..718da518653a 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestWithIncludeFilterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestWithIncludeFilterIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.boot.test.autoconfigure.data.neo4j; -import java.time.Duration; - import org.junit.jupiter.api.Test; import org.testcontainers.containers.Neo4jContainer; import org.testcontainers.junit.jupiter.Container; @@ -25,7 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.stereotype.Service; @@ -46,9 +44,7 @@ class DataNeo4jTestWithIncludeFilterIntegrationTests { @Container @ServiceConnection - static final Neo4jContainer neo4j = new Neo4jContainer<>(DockerImageNames.neo4j()).withoutAuthentication() - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + static final Neo4jContainer neo4j = TestImage.container(Neo4jContainer.class).withoutAuthentication(); @Autowired private ExampleService service; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleGraph.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleGraph.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleGraph.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleGraph.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleNeo4jApplication.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleNeo4jApplication.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleNeo4jApplication.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleNeo4jApplication.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleReactiveRepository.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleReactiveRepository.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleReactiveRepository.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleReactiveRepository.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleRepository.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleRepository.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleRepository.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleRepository.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleService.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleService.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleService.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/neo4j/ExampleService.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestIntegrationTests.java similarity index 91% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestIntegrationTests.java index 0018b31976fa..50ab4defe997 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testcontainers.service.connection.ServiceConnectionAutoConfiguration; -import org.springframework.boot.testsupport.testcontainers.RedisContainer; +import org.springframework.boot.testsupport.container.RedisContainer; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.ApplicationContext; import org.springframework.data.redis.core.RedisOperations; @@ -51,7 +52,7 @@ class DataRedisTestIntegrationTests { @Container @ServiceConnection - static RedisContainer redis = new RedisContainer(); + static RedisContainer redis = TestImage.container(RedisContainer.class); @Autowired private RedisOperations operations; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestPropertiesIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestPropertiesIntegrationTests.java similarity index 88% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestPropertiesIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestPropertiesIntegrationTests.java index d4897ebaaf95..49c6f1a67a02 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestPropertiesIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestPropertiesIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.RedisContainer; +import org.springframework.boot.testsupport.container.RedisContainer; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.core.env.Environment; import static org.assertj.core.api.Assertions.assertThat; @@ -43,7 +44,7 @@ class DataRedisTestPropertiesIntegrationTests { @Container @ServiceConnection - static final RedisContainer redis = new RedisContainer(); + static final RedisContainer redis = TestImage.container(RedisContainer.class); @Autowired private Environment environment; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestReactiveIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestReactiveIntegrationTests.java similarity index 90% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestReactiveIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestReactiveIntegrationTests.java index 4d91aae06f89..be773935ba6a 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestReactiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestReactiveIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,8 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.RedisContainer; +import org.springframework.boot.testsupport.container.RedisContainer; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.ApplicationContext; import org.springframework.data.redis.core.ReactiveRedisOperations; @@ -47,7 +48,7 @@ class DataRedisTestReactiveIntegrationTests { @Container @ServiceConnection - static RedisContainer redis = new RedisContainer(); + static RedisContainer redis = TestImage.container(RedisContainer.class); @Autowired private ReactiveRedisOperations operations; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestWithIncludeFilterIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestWithIncludeFilterIntegrationTests.java similarity index 87% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestWithIncludeFilterIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestWithIncludeFilterIntegrationTests.java index c46659b9dbb2..233511a441dc 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestWithIncludeFilterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestWithIncludeFilterIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.RedisContainer; +import org.springframework.boot.testsupport.container.RedisContainer; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.stereotype.Service; @@ -42,7 +43,7 @@ class DataRedisTestWithIncludeFilterIntegrationTests { @Container @ServiceConnection - static final RedisContainer redis = new RedisContainer(); + static final RedisContainer redis = TestImage.container(RedisContainer.class); @Autowired private ExampleRepository exampleRepository; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/ExampleRedisApplication.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/ExampleRedisApplication.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/ExampleRedisApplication.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/ExampleRedisApplication.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/ExampleRepository.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/ExampleRepository.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/ExampleRepository.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/ExampleRepository.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/ExampleService.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/ExampleService.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/ExampleService.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/ExampleService.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/PersonHash.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/PersonHash.java similarity index 100% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/PersonHash.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/redis/PersonHash.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/OverrideAutoConfigurationContextCustomizerFactory.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/OverrideAutoConfigurationContextCustomizerFactory.java index 664e1e035d95..5b7ab2976b28 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/OverrideAutoConfigurationContextCustomizerFactory.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/OverrideAutoConfigurationContextCustomizerFactory.java @@ -44,7 +44,7 @@ public ContextCustomizer createContextCustomizer(Class testClass, } OverrideAutoConfiguration overrideAutoConfiguration = TestContextAnnotationUtils.findMergedAnnotation(testClass, OverrideAutoConfiguration.class); - boolean enabled = (overrideAutoConfiguration != null) ? overrideAutoConfiguration.enabled() : true; + boolean enabled = (overrideAutoConfiguration == null) || overrideAutoConfiguration.enabled(); return !enabled ? new DisableAutoConfigurationContextCustomizer() : null; } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/SpringBootDependencyInjectionTestExecutionListener.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/SpringBootDependencyInjectionTestExecutionListener.java deleted file mode 100644 index e3fbb4d61b96..000000000000 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/SpringBootDependencyInjectionTestExecutionListener.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.test.autoconfigure; - -import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; -import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportMessage; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.test.context.ApplicationContextFailureProcessor; -import org.springframework.test.context.TestContext; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; - -/** - * Since 3.0.0 this class has been replaced by - * {@link ConditionReportApplicationContextFailureProcessor} and is not used internally. - * - * @author Phillip Webb - * @since 1.4.1 - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of - * {@link ApplicationContextFailureProcessor} - */ -@Deprecated(since = "3.0.0", forRemoval = true) -public class SpringBootDependencyInjectionTestExecutionListener extends DependencyInjectionTestExecutionListener { - - @Override - public void prepareTestInstance(TestContext testContext) throws Exception { - try { - super.prepareTestInstance(testContext); - } - catch (Exception ex) { - outputConditionEvaluationReport(testContext); - throw ex; - } - } - - private void outputConditionEvaluationReport(TestContext testContext) { - try { - ApplicationContext context = testContext.getApplicationContext(); - if (context instanceof ConfigurableApplicationContext configurableContext) { - ConditionEvaluationReport report = ConditionEvaluationReport.get(configurableContext.getBeanFactory()); - System.err.println(new ConditionEvaluationReportMessage(report)); - } - } - catch (Exception ex) { - // Allow original failure to be reported - } - } - -} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetrics.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetrics.java deleted file mode 100644 index 44ec896236d4..000000000000 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetrics.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.test.autoconfigure.actuate.metrics; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.boot.test.autoconfigure.actuate.observability.AutoConfigureObservability; - -/** - * Annotation that can be applied to a test class to enable auto-configuration for metrics - * exporters. - * - * @author Chris Bono - * @since 2.4.0 - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of - * {@link AutoConfigureObservability @AutoConfigureObservability} - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -@Deprecated(since = "3.0.0", forRemoval = true) -@AutoConfigureObservability(tracing = false) -public @interface AutoConfigureMetrics { - -} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/metrics/package-info.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/metrics/package-info.java deleted file mode 100644 index 6dfcc180e669..000000000000 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/metrics/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2012-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Auto-configuration for handling metrics in tests. - */ -package org.springframework.boot.test.autoconfigure.actuate.metrics; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/observability/AutoConfigureObservability.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/observability/AutoConfigureObservability.java index fb16e8d1032e..6a70adafd31d 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/observability/AutoConfigureObservability.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/observability/AutoConfigureObservability.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,9 +23,15 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; + /** * Annotation that can be applied to a test class to enable auto-configuration for * observability. + *

+ * If this annotation is applied to a sliced test, an in-memory {@code MeterRegistry}, a + * no-op {@code Tracer} and an {@code ObservationRegistry} are added to the application + * context. * * @author Moritz Halbritter * @since 3.0.0 @@ -34,17 +40,18 @@ @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited +@ImportAutoConfiguration public @interface AutoConfigureObservability { /** - * Whether metrics should be enabled in the test. - * @return whether metrics should be enabled in the test + * Whether metrics should be reported to external systems in the test. + * @return whether metrics should be reported to external systems in the test */ boolean metrics() default true; /** - * Whether tracing should be enabled in the test. - * @return whether tracing should be enabled in the test + * Whether traces should be reported to external systems in the test. + * @return whether traces should be reported to external systems in the test */ boolean tracing() default true; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/observability/ObservabilityContextCustomizerFactory.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/observability/ObservabilityContextCustomizerFactory.java index 918777baf1e7..86a003b6b6e0 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/observability/ObservabilityContextCustomizerFactory.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/observability/ObservabilityContextCustomizerFactory.java @@ -19,39 +19,19 @@ import java.util.List; import java.util.Objects; -import io.micrometer.tracing.Tracer; - -import org.springframework.aot.AotDetector; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.BeanFactoryUtils; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.ConfigurationClassPostProcessor; -import org.springframework.core.Ordered; import org.springframework.core.env.Environment; import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.ContextCustomizerFactory; import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.TestContextAnnotationUtils; -import org.springframework.util.ClassUtils; /** * {@link ContextCustomizerFactory} that globally disables metrics export and tracing in * tests. The behaviour can be controlled with {@link AutoConfigureObservability} on the * test class or via the {@value #AUTO_CONFIGURE_PROPERTY} property. - *

- * Registers {@link Tracer#NOOP} if tracing is disabled, micrometer-tracing is on the - * classpath, and the user hasn't supplied their own {@link Tracer}. * * @author Chris Bono * @author Moritz Halbritter @@ -87,7 +67,6 @@ public void customizeContext(ConfigurableApplicationContext context, } if (isTracingDisabled(context.getEnvironment())) { TestPropertyValues.of("management.tracing.enabled=false").applyTo(context); - registerNoopTracer(context); } } @@ -105,25 +84,6 @@ private boolean isTracingDisabled(Environment environment) { return !environment.getProperty(AUTO_CONFIGURE_PROPERTY, Boolean.class, false); } - private void registerNoopTracer(ConfigurableApplicationContext context) { - if (AotDetector.useGeneratedArtifacts()) { - return; - } - if (!ClassUtils.isPresent("io.micrometer.tracing.Tracer", context.getClassLoader())) { - return; - } - ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); - if (beanFactory instanceof BeanDefinitionRegistry registry) { - registerNoopTracer(registry); - } - } - - private void registerNoopTracer(BeanDefinitionRegistry registry) { - RootBeanDefinition definition = new RootBeanDefinition(NoopTracerRegistrar.class); - definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - registry.registerBeanDefinition(NoopTracerRegistrar.class.getName(), definition); - } - @Override public boolean equals(Object o) { if (this == o) { @@ -143,54 +103,4 @@ public int hashCode() { } - /** - * {@link BeanDefinitionRegistryPostProcessor} that runs after the - * {@link ConfigurationClassPostProcessor} and adds a {@link Tracer} bean definition - * when a {@link Tracer} hasn't already been registered. - */ - static class NoopTracerRegistrar implements BeanDefinitionRegistryPostProcessor, Ordered, BeanFactoryAware { - - private BeanFactory beanFactory; - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - @Override - public int getOrder() { - return Ordered.LOWEST_PRECEDENCE; - } - - @Override - public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { - if (AotDetector.useGeneratedArtifacts()) { - return; - } - if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory) this.beanFactory, - Tracer.class, false, false).length == 0) { - registry.registerBeanDefinition("noopTracer", new RootBeanDefinition(NoopTracerFactoryBean.class)); - } - } - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { - } - - } - - static class NoopTracerFactoryBean implements FactoryBean { - - @Override - public Tracer getObject() { - return Tracer.NOOP; - } - - @Override - public Class getObjectType() { - return Tracer.class; - } - - } - } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/filter/AnnotationCustomizableTypeExcludeFilter.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/filter/AnnotationCustomizableTypeExcludeFilter.java index af92b8a1541c..807c4a7d9cf8 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/filter/AnnotationCustomizableTypeExcludeFilter.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/filter/AnnotationCustomizableTypeExcludeFilter.java @@ -18,6 +18,8 @@ import java.io.IOException; import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Objects; import java.util.Set; import org.springframework.beans.factory.BeanClassLoaderAware; @@ -135,11 +137,11 @@ public int hashCode() { int result = 0; result = prime * result + Boolean.hashCode(hasAnnotation()); for (FilterType filterType : FilterType.values()) { - result = prime * result + ObjectUtils.nullSafeHashCode(getFilters(filterType)); + result = prime * result + Arrays.hashCode(getFilters(filterType)); } result = prime * result + Boolean.hashCode(isUseDefaultFilters()); - result = prime * result + ObjectUtils.nullSafeHashCode(getDefaultIncludes()); - result = prime * result + ObjectUtils.nullSafeHashCode(getComponentIncludes()); + result = prime * result + Objects.hashCode(getDefaultIncludes()); + result = prime * result + Objects.hashCode(getComponentIncludes()); return result; } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/AutoConfigureJsonTesters.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/AutoConfigureJsonTesters.java index 04e939e8c724..b7fb92f8e1ad 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/AutoConfigureJsonTesters.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/AutoConfigureJsonTesters.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited +@AutoConfigureJson @ImportAutoConfiguration @PropertyMapping("spring.test.jsontesters") public @interface AutoConfigureJsonTesters { diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTest.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTest.java index 4a64e30c7e93..8aedf126c5d8 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTest.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,7 +70,6 @@ @OverrideAutoConfiguration(enabled = false) @TypeExcludeFilters(JsonTypeExcludeFilter.class) @AutoConfigureCache -@AutoConfigureJson @AutoConfigureJsonTesters @ImportAutoConfiguration public @interface JsonTest { diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServer.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServer.java index 6e9e8def9672..43e1aac70e07 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServer.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,20 +25,26 @@ import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.autoconfigure.properties.PropertyMapping; +import org.springframework.boot.test.web.client.MockServerRestClientCustomizer; import org.springframework.boot.test.web.client.MockServerRestTemplateCustomizer; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; /** * Annotation that can be applied to a test class to enable and configure * auto-configuration of a single {@link MockRestServiceServer}. Only useful when a single - * call is made to {@link RestTemplateBuilder}. If multiple - * {@link org.springframework.web.client.RestTemplate RestTemplates} are in use, inject + * call is made to {@link RestTemplateBuilder} or {@link RestClient.Builder}. If multiple + * {@link org.springframework.web.client.RestTemplate RestTemplates} or + * {@link org.springframework.web.client.RestClient RestClients} are in use, inject a * {@link MockServerRestTemplateCustomizer} and use * {@link MockServerRestTemplateCustomizer#getServer(org.springframework.web.client.RestTemplate) - * getServer(RestTemplate)} or bind a {@link MockRestServiceServer} directly. + * getServer(RestTemplate)}, or inject a {@link MockServerRestClientCustomizer} and use + * {@link MockServerRestClientCustomizer#getServer(org.springframework.web.client.RestClient.Builder) + * * getServer(RestClient.Builder)}, or bind a {@link MockRestServiceServer} directly. * * @author Phillip Webb + * @author Scott Frederick * @since 1.4.0 * @see MockServerRestTemplateCustomizer */ @@ -51,7 +57,8 @@ public @interface AutoConfigureMockRestServiceServer { /** - * If {@link MockServerRestTemplateCustomizer} should be enabled and + * If {@link MockServerRestTemplateCustomizer} and + * {@link MockServerRestClientCustomizer} should be enabled and * {@link MockRestServiceServer} beans should be registered. Defaults to {@code true} * @return if mock support is enabled */ diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureWebClient.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureWebClient.java index cc071e0b7049..83d8c7789be0 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureWebClient.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureWebClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import java.lang.annotation.Target; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.autoconfigure.json.AutoConfigureJson; import org.springframework.boot.test.autoconfigure.properties.PropertyMapping; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.web.client.RestTemplate; @@ -40,6 +41,7 @@ @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited +@AutoConfigureJson @ImportAutoConfiguration @PropertyMapping("spring.test.webclient") public @interface AutoConfigureWebClient { diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/MockRestServiceServerAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/MockRestServiceServerAutoConfiguration.java index aa76319d00a6..a0ada9a512c4 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/MockRestServiceServerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/MockRestServiceServerAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,12 @@ import java.io.IOException; import java.lang.reflect.Constructor; import java.time.Duration; +import java.util.Collection; import java.util.Map; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.test.web.client.MockServerRestClientCustomizer; import org.springframework.boot.test.web.client.MockServerRestTemplateCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.http.client.ClientHttpRequest; @@ -33,12 +35,14 @@ import org.springframework.test.web.client.RequestMatcher; import org.springframework.test.web.client.ResponseActions; import org.springframework.util.Assert; +import org.springframework.web.client.RestClient; import org.springframework.web.client.RestTemplate; /** * Auto-configuration for {@link MockRestServiceServer} support. * * @author Phillip Webb + * @author Scott Frederick * @since 1.4.0 * @see AutoConfigureMockRestServiceServer */ @@ -52,21 +56,29 @@ public MockServerRestTemplateCustomizer mockServerRestTemplateCustomizer() { } @Bean - public MockRestServiceServer mockRestServiceServer(MockServerRestTemplateCustomizer customizer) { + public MockServerRestClientCustomizer mockServerRestClientCustomizer() { + return new MockServerRestClientCustomizer(); + } + + @Bean + public MockRestServiceServer mockRestServiceServer(MockServerRestTemplateCustomizer restTemplateCustomizer, + MockServerRestClientCustomizer restClientCustomizer) { try { - return createDeferredMockRestServiceServer(customizer); + return createDeferredMockRestServiceServer(restTemplateCustomizer, restClientCustomizer); } catch (Exception ex) { throw new IllegalStateException(ex); } } - private MockRestServiceServer createDeferredMockRestServiceServer(MockServerRestTemplateCustomizer customizer) - throws Exception { + private MockRestServiceServer createDeferredMockRestServiceServer( + MockServerRestTemplateCustomizer restTemplateCustomizer, + MockServerRestClientCustomizer restClientCustomizer) throws Exception { Constructor constructor = MockRestServiceServer.class .getDeclaredConstructor(RequestExpectationManager.class); constructor.setAccessible(true); - return constructor.newInstance(new DeferredRequestExpectationManager(customizer)); + return constructor + .newInstance(new DeferredRequestExpectationManager(restTemplateCustomizer, restClientCustomizer)); } /** @@ -77,10 +89,14 @@ private MockRestServiceServer createDeferredMockRestServiceServer(MockServerRest */ private static class DeferredRequestExpectationManager implements RequestExpectationManager { - private final MockServerRestTemplateCustomizer customizer; + private final MockServerRestTemplateCustomizer restTemplateCustomizer; + + private final MockServerRestClientCustomizer restClientCustomizer; - DeferredRequestExpectationManager(MockServerRestTemplateCustomizer customizer) { - this.customizer = customizer; + DeferredRequestExpectationManager(MockServerRestTemplateCustomizer restTemplateCustomizer, + MockServerRestClientCustomizer restClientCustomizer) { + this.restTemplateCustomizer = restTemplateCustomizer; + this.restClientCustomizer = restClientCustomizer; } @Override @@ -105,19 +121,37 @@ public void verify(Duration timeout) { @Override public void reset() { - Map expectationManagers = this.customizer.getExpectationManagers(); + resetExpectations(this.restTemplateCustomizer.getExpectationManagers().values()); + resetExpectations(this.restClientCustomizer.getExpectationManagers().values()); + } + + private void resetExpectations(Collection expectationManagers) { if (expectationManagers.size() == 1) { - getDelegate().reset(); + expectationManagers.iterator().next().reset(); } } private RequestExpectationManager getDelegate() { - Map expectationManagers = this.customizer.getExpectationManagers(); - Assert.state(!expectationManagers.isEmpty(), "Unable to use auto-configured MockRestServiceServer since " - + "MockServerRestTemplateCustomizer has not been bound to a RestTemplate"); - Assert.state(expectationManagers.size() == 1, "Unable to use auto-configured MockRestServiceServer since " - + "MockServerRestTemplateCustomizer has been bound to more than one RestTemplate"); - return expectationManagers.values().iterator().next(); + Map restTemplateExpectationManagers = this.restTemplateCustomizer + .getExpectationManagers(); + Map restClientExpectationManagers = this.restClientCustomizer + .getExpectationManagers(); + boolean neitherBound = restTemplateExpectationManagers.isEmpty() && restClientExpectationManagers.isEmpty(); + boolean bothBound = !restTemplateExpectationManagers.isEmpty() && !restClientExpectationManagers.isEmpty(); + Assert.state(!neitherBound, "Unable to use auto-configured MockRestServiceServer since " + + "a mock server customizer has not been bound to a RestTemplate or RestClient"); + Assert.state(!bothBound, "Unable to use auto-configured MockRestServiceServer since " + + "mock server customizers have been bound to both a RestTemplate and a RestClient"); + if (!restTemplateExpectationManagers.isEmpty()) { + Assert.state(restTemplateExpectationManagers.size() == 1, + "Unable to use auto-configured MockRestServiceServer since " + + "MockServerRestTemplateCustomizer has been bound to more than one RestTemplate"); + return restTemplateExpectationManagers.values().iterator().next(); + } + Assert.state(restClientExpectationManagers.size() == 1, + "Unable to use auto-configured MockRestServiceServer since " + + "MockServerRestClientCustomizer has been bound to more than one RestClient"); + return restClientExpectationManagers.values().iterator().next(); } } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTest.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTest.java index eba5ed69d003..8a93b1b42226 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTest.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,11 +38,12 @@ import org.springframework.test.context.BootstrapWith; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; import org.springframework.web.client.RestTemplate; /** * Annotation for a Spring rest client test that focuses only on beans - * that use {@link RestTemplateBuilder}. + * that use {@link RestTemplateBuilder} or {@link RestClient.Builder}. *

* Using this annotation will disable full auto-configuration and instead apply only * configuration relevant to rest client tests (i.e. Jackson or GSON auto-configuration diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/AutoConfigureMockMvc.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/AutoConfigureMockMvc.java index 6882f158ae90..a0aefc9c149f 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/AutoConfigureMockMvc.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/AutoConfigureMockMvc.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import com.gargoylesoftware.htmlunit.WebClient; +import org.htmlunit.WebClient; import org.openqa.selenium.WebDriver; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; @@ -31,10 +31,12 @@ import org.springframework.boot.test.autoconfigure.properties.SkipPropertyMapping; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.assertj.MockMvcTester; /** * Annotation that can be applied to a test class to enable and configure - * auto-configuration of {@link MockMvc}. + * auto-configuration of {@link MockMvc}. If AssertJ is available a {@link MockMvcTester} + * is auto-configured as well. * * @author Phillip Webb * @since 1.4.0 diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/AutoConfigureWebMvc.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/AutoConfigureWebMvc.java index d4684f8ddf35..60c3832b67bd 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/AutoConfigureWebMvc.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/AutoConfigureWebMvc.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import java.lang.annotation.Target; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.autoconfigure.json.AutoConfigureJson; /** * {@link ImportAutoConfiguration Auto-configuration imports} for typical Spring MVC @@ -39,6 +40,7 @@ @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited +@AutoConfigureJson @ImportAutoConfiguration public @interface AutoConfigureWebMvc { diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java index 6d429eb4fae3..6b96b5bd226f 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,20 +27,15 @@ import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties; -import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration; import org.springframework.boot.test.web.reactive.server.WebTestClientBuilderCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.test.web.servlet.DispatcherServletCustomizer; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MockMvcBuilder; import org.springframework.test.web.servlet.client.MockMvcWebTestClient; -import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.servlet.DispatcherServlet; @@ -57,44 +52,13 @@ @AutoConfiguration(after = { WebMvcAutoConfiguration.class, WebTestClientAutoConfiguration.class }) @ConditionalOnWebApplication(type = Type.SERVLET) @EnableConfigurationProperties({ ServerProperties.class, WebMvcProperties.class }) +@Import({ MockMvcConfiguration.class, MockMvcTesterConfiguration.class }) public class MockMvcAutoConfiguration { - private final WebApplicationContext context; - - private final WebMvcProperties webMvcProperties; - - MockMvcAutoConfiguration(WebApplicationContext context, WebMvcProperties webMvcProperties) { - this.context = context; - this.webMvcProperties = webMvcProperties; - } - @Bean @ConditionalOnMissingBean - public DispatcherServletPath dispatcherServletPath() { - return () -> this.webMvcProperties.getServlet().getPath(); - } - - @Bean - @ConditionalOnMissingBean(MockMvcBuilder.class) - public DefaultMockMvcBuilder mockMvcBuilder(List customizers) { - DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.context); - builder.addDispatcherServletCustomizer(new MockMvcDispatcherServletCustomizer(this.webMvcProperties)); - for (MockMvcBuilderCustomizer customizer : customizers) { - customizer.customize(builder); - } - return builder; - } - - @Bean - @ConfigurationProperties(prefix = "spring.test.mockmvc") - public SpringBootMockMvcBuilderCustomizer springBootMockMvcBuilderCustomizer() { - return new SpringBootMockMvcBuilderCustomizer(this.context); - } - - @Bean - @ConditionalOnMissingBean - public MockMvc mockMvc(MockMvcBuilder builder) { - return builder.build(); + public DispatcherServletPath dispatcherServletPath(WebMvcProperties webMvcProperties) { + return () -> webMvcProperties.getServlet().getPath(); } @Bean @@ -119,22 +83,4 @@ WebTestClient webTestClient(MockMvc mockMvc, List customizers) { + DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.context); + builder.addDispatcherServletCustomizer(new MockMvcDispatcherServletCustomizer(this.webMvcProperties)); + for (MockMvcBuilderCustomizer customizer : customizers) { + customizer.customize(builder); + } + return builder; + } + + @Bean + @ConfigurationProperties(prefix = "spring.test.mockmvc") + SpringBootMockMvcBuilderCustomizer springBootMockMvcBuilderCustomizer() { + return new SpringBootMockMvcBuilderCustomizer(this.context); + } + + @Bean + @ConditionalOnMissingBean + MockMvc mockMvc(MockMvcBuilder builder) { + return builder.build(); + } + + private static class MockMvcDispatcherServletCustomizer implements DispatcherServletCustomizer { + + private final WebMvcProperties webMvcProperties; + + MockMvcDispatcherServletCustomizer(WebMvcProperties webMvcProperties) { + this.webMvcProperties = webMvcProperties; + } + + @Override + public void customize(DispatcherServlet dispatcherServlet) { + dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest()); + dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcTesterConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcTesterConfiguration.java new file mode 100644 index 000000000000..657dbb2e403f --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcTesterConfiguration.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.web.servlet; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; + +/** + * Configuration for {@link MockMvcTester}. + * + * @author Stephane Nicoll + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(name = "org.assertj.core.api.Assert") +class MockMvcTesterConfiguration { + + @Bean + @ConditionalOnMissingBean + MockMvcTester mockMvcTester(MockMvc mockMvc, ObjectProvider httpMessageConverters) { + MockMvcTester mockMvcTester = MockMvcTester.create(mockMvc); + HttpMessageConverters converters = httpMessageConverters.getIfAvailable(); + if (converters != null) { + mockMvcTester = mockMvcTester.withHttpMessageConverters(converters); + } + return mockMvcTester; + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcWebClientAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcWebClientAutoConfiguration.java index 2ac41ec3d3b8..9f838bb2c4ca 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcWebClientAutoConfiguration.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcWebClientAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package org.springframework.boot.test.autoconfigure.web.servlet; -import com.gargoylesoftware.htmlunit.WebClient; +import org.htmlunit.WebClient; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcWebDriverAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcWebDriverAutoConfiguration.java index 202cbb0ab57e..6a45c59c7b21 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcWebDriverAutoConfiguration.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcWebDriverAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import java.util.concurrent.Executors; -import com.gargoylesoftware.htmlunit.BrowserVersion; +import org.htmlunit.BrowserVersion; import org.openqa.selenium.WebDriver; import org.openqa.selenium.htmlunit.HtmlUnitDriver; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/SpringBootMockMvcBuilderCustomizer.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/SpringBootMockMvcBuilderCustomizer.java index 2c1a84c8be91..693a5431d03c 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/SpringBootMockMvcBuilderCustomizer.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/SpringBootMockMvcBuilderCustomizer.java @@ -117,12 +117,8 @@ private void addFilters(ConfigurableMockMvcBuilder builder) { private void addFilter(ConfigurableMockMvcBuilder builder, AbstractFilterRegistrationBean registration) { Filter filter = registration.getFilter(); Collection urls = registration.getUrlPatterns(); - if (urls.isEmpty()) { - builder.addFilters(filter); - } - else { - builder.addFilter(filter, StringUtils.toStringArray(urls)); - } + builder.addFilter(filter, registration.getFilterName(), registration.getInitParameters(), + registration.determineDispatcherTypes(), StringUtils.toStringArray(urls)); } public void setAddFilters(boolean addFilters) { diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/WebDriverContextCustomizer.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/WebDriverContextCustomizer.java index 5a7420207dec..e0c178db3e2b 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/WebDriverContextCustomizer.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/WebDriverContextCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,10 +39,7 @@ public boolean equals(Object obj) { if (obj == this) { return true; } - if (obj == null || obj.getClass() != getClass()) { - return false; - } - return true; + return obj != null && obj.getClass() == getClass(); } @Override diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.actuate.observability.AutoConfigureObservability.imports b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.actuate.observability.AutoConfigureObservability.imports new file mode 100644 index 000000000000..af374669f52f --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.actuate.observability.AutoConfigureObservability.imports @@ -0,0 +1,13 @@ +# AutoConfigureObservability auto-configuration imports + +# Observation +org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration + +# Metrics +org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration +org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration +org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration + +# Tracing +org.springframework.boot.actuate.autoconfigure.tracing.NoopTracerAutoConfiguration +org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.data.jdbc.AutoConfigureDataJdbc.imports b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.data.jdbc.AutoConfigureDataJdbc.imports index 2fc2a1e54ad0..eb4b3faada1b 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.data.jdbc.AutoConfigureDataJdbc.imports +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.data.jdbc.AutoConfigureDataJdbc.imports @@ -3,6 +3,7 @@ org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfigurati org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration +org.springframework.boot.autoconfigure.jdbc.JdbcClientAutoConfiguration org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.data.ldap.AutoConfigureDataLdap.imports b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.data.ldap.AutoConfigureDataLdap.imports index 9704c0aacaac..508c529b5101 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.data.ldap.AutoConfigureDataLdap.imports +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.data.ldap.AutoConfigureDataLdap.imports @@ -1,4 +1,5 @@ # AutoConfigureDataLdap auto-configuration imports org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration -org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration \ No newline at end of file +org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration +optional:org.springframework.boot.testcontainers.service.connection.ServiceConnectionAutoConfiguration \ No newline at end of file diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureJdbc.imports b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureJdbc.imports index 5aa3ad940cf4..480dcff0e7c1 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureJdbc.imports +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureJdbc.imports @@ -2,6 +2,7 @@ org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration +org.springframework.boot.autoconfigure.jdbc.JdbcClientAutoConfiguration org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters.imports b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters.imports index c3d0a1017b3d..ed6678423b54 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters.imports +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters.imports @@ -1,5 +1,2 @@ # AutoConfigureJsonTesters auto-configuration imports -org.springframework.boot.test.autoconfigure.json.JsonTestersAutoConfiguration -org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration -org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration -org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration \ No newline at end of file +org.springframework.boot.test.autoconfigure.json.JsonTestersAutoConfiguration \ No newline at end of file diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureDataJpa.imports b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureDataJpa.imports index ba99875857e7..83465fdeba7e 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureDataJpa.imports +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureDataJpa.imports @@ -3,6 +3,7 @@ org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration +org.springframework.boot.autoconfigure.jdbc.JdbcClientAutoConfiguration org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient.imports b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient.imports index c789b0b5c278..c781adda6d14 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient.imports +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient.imports @@ -1,9 +1,7 @@ # AutoConfigureWebClient auto-configuration imports org.springframework.boot.test.autoconfigure.web.client.WebClientRestTemplateAutoConfiguration -org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration -org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration -org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration +org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration \ No newline at end of file diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc.imports b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc.imports index a8facbe6c039..4d4a8ff4a870 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc.imports +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc.imports @@ -3,11 +3,8 @@ org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration -org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration -org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration -org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsMissingIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsMissingIntegrationTests.java deleted file mode 100644 index ff4e924a993a..000000000000 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsMissingIntegrationTests.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.test.autoconfigure.actuate.metrics; - -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import io.micrometer.prometheus.PrometheusMeterRegistry; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.ApplicationContext; -import org.springframework.core.env.Environment; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration test to verify behaviour when - * {@link AutoConfigureMetrics @AutoConfigureMetrics} is not present on the test class. - * - * @author Chris Bono - */ -@SpringBootTest -class AutoConfigureMetricsMissingIntegrationTests { - - @Test - void customizerRunsAndOnlyEnablesSimpleMeterRegistryWhenNoAnnotationPresent( - @Autowired ApplicationContext applicationContext) { - assertThat(applicationContext.getBean(MeterRegistry.class)).isInstanceOf(SimpleMeterRegistry.class); - assertThat(applicationContext.getBeansOfType(PrometheusMeterRegistry.class)).isEmpty(); - } - - @Test - void customizerRunsAndSetsExclusionPropertiesWhenNoAnnotationPresent(@Autowired Environment environment) { - assertThat(environment.getProperty("management.defaults.metrics.export.enabled")).isEqualTo("false"); - assertThat(environment.getProperty("management.simple.metrics.export.enabled")).isEqualTo("true"); - } - -} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsPresentIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsPresentIntegrationTests.java deleted file mode 100644 index dfdef02bdb2c..000000000000 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsPresentIntegrationTests.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.test.autoconfigure.actuate.metrics; - -import io.micrometer.prometheus.PrometheusMeterRegistry; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.ApplicationContext; -import org.springframework.core.env.Environment; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration test to verify behaviour when - * {@link AutoConfigureMetrics @AutoConfigureMetrics} is present on the test class. - * - * @author Chris Bono - */ -@SuppressWarnings("removal") -@SpringBootTest -@AutoConfigureMetrics -@Deprecated(since = "3.0.0", forRemoval = true) -class AutoConfigureMetricsPresentIntegrationTests { - - @Test - void customizerDoesNotDisableAvailableMeterRegistriesWhenAnnotationPresent( - @Autowired ApplicationContext applicationContext) { - assertThat(applicationContext.getBeansOfType(PrometheusMeterRegistry.class)).hasSize(1); - } - - @Test - void customizerDoesNotSetExclusionPropertiesWhenAnnotationPresent(@Autowired Environment environment) { - assertThat(environment.containsProperty("management.defaults.metrics.export.enabled")).isFalse(); - assertThat(environment.containsProperty("management.simple.metrics.export.enabled")).isFalse(); - } - -} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsSpringBootApplication.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsSpringBootApplication.java deleted file mode 100644 index 31c699a44b8d..000000000000 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsSpringBootApplication.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.test.autoconfigure.actuate.metrics; - -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; -import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; -import org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration; - -/** - * Example {@link SpringBootApplication @SpringBootApplication} for use with - * {@link AutoConfigureMetrics @AutoConfigureMetrics} tests. - * - * @author Chris Bono - */ -@SpringBootConfiguration -@EnableAutoConfiguration(exclude = { CassandraAutoConfiguration.class, MongoAutoConfiguration.class, - MongoReactiveAutoConfiguration.class }) -class AutoConfigureMetricsSpringBootApplication { - -} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/AutoConfigureObservabilityMissingIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/AutoConfigureObservabilityMissingIntegrationTests.java index e0aa06009431..8c3fd64eec95 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/AutoConfigureObservabilityMissingIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/AutoConfigureObservabilityMissingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import io.micrometer.prometheus.PrometheusMeterRegistry; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -37,13 +36,14 @@ * @author Moritz Halbritter */ @SpringBootTest +@SuppressWarnings("deprecation") class AutoConfigureObservabilityMissingIntegrationTests { @Test void customizerRunsAndOnlyEnablesSimpleMeterRegistryWhenNoAnnotationPresent( @Autowired ApplicationContext applicationContext) { assertThat(applicationContext.getBean(MeterRegistry.class)).isInstanceOf(SimpleMeterRegistry.class); - assertThat(applicationContext.getBeansOfType(PrometheusMeterRegistry.class)).isEmpty(); + assertThat(applicationContext.getBeansOfType(io.micrometer.prometheus.PrometheusMeterRegistry.class)).isEmpty(); } @Test diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/AutoConfigureObservabilityPresentIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/AutoConfigureObservabilityPresentIntegrationTests.java index d4fbdcef5675..f1c58916c458 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/AutoConfigureObservabilityPresentIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/AutoConfigureObservabilityPresentIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.springframework.boot.test.autoconfigure.actuate.observability; -import io.micrometer.prometheus.PrometheusMeterRegistry; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -36,12 +35,14 @@ */ @SpringBootTest @AutoConfigureObservability +@SuppressWarnings("deprecation") class AutoConfigureObservabilityPresentIntegrationTests { @Test void customizerDoesNotDisableAvailableMeterRegistriesWhenAnnotationPresent( @Autowired ApplicationContext applicationContext) { - assertThat(applicationContext.getBeansOfType(PrometheusMeterRegistry.class)).hasSize(1); + assertThat(applicationContext.getBeansOfType(io.micrometer.prometheus.PrometheusMeterRegistry.class)) + .hasSize(1); } @Test diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/AutoConfigureObservabilitySlicedIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/AutoConfigureObservabilitySlicedIntegrationTests.java new file mode 100644 index 000000000000..4b73857da05a --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/AutoConfigureObservabilitySlicedIntegrationTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.actuate.observability; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.tracing.Tracer; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.ApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AutoConfigureObservability} when used on a sliced test. + * + * @author Moritz Halbritter + */ +@WebMvcTest +@AutoConfigureObservability +class AutoConfigureObservabilitySlicedIntegrationTests { + + @Autowired + private ApplicationContext context; + + @Test + void shouldHaveTracer() { + assertThat(this.context.getBean(Tracer.class)).isEqualTo(Tracer.NOOP); + } + + @Test + void shouldHaveMeterRegistry() { + assertThat(this.context.getBean(MeterRegistry.class)).isInstanceOf(SimpleMeterRegistry.class); + } + + @Test + void shouldHaveObservationRegistry() { + assertThat(this.context.getBean(ObservationRegistry.class)).isNotNull(); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/ObservabilityContextCustomizerFactoryTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/ObservabilityContextCustomizerFactoryTests.java index d8e46424c52e..c8604bf53071 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/ObservabilityContextCustomizerFactoryTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/ObservabilityContextCustomizerFactoryTests.java @@ -18,22 +18,15 @@ import java.util.Collections; -import io.micrometer.tracing.Tracer; import org.junit.jupiter.api.Test; -import org.springframework.boot.context.annotation.UserConfigurations; -import org.springframework.boot.test.context.FilteredClassLoader; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.context.support.GenericApplicationContext; import org.springframework.mock.env.MockEnvironment; import org.springframework.test.context.ContextCustomizer; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; /** * Tests for {@link AutoConfigureObservability} and @@ -82,59 +75,6 @@ void shouldEnableBothWhenAnnotated() { assertThatTracingIsEnabled(context); } - @Test - void shouldRegisterNoopTracerIfTracingIsDisabled() { - ContextCustomizer customizer = createContextCustomizer(NoAnnotation.class); - ConfigurableApplicationContext context = new GenericApplicationContext(); - applyCustomizerToContext(customizer, context); - context.refresh(); - Tracer tracer = context.getBean(Tracer.class); - assertThat(tracer).isNotNull(); - assertThat(tracer.nextSpan().isNoop()).isTrue(); - } - - @Test - void shouldNotRegisterNoopTracerIfTracingIsEnabled() { - ContextCustomizer customizer = createContextCustomizer(WithAnnotation.class); - ConfigurableApplicationContext context = new GenericApplicationContext(); - applyCustomizerToContext(customizer, context); - context.refresh(); - assertThat(context.getBeanProvider(Tracer.class).getIfAvailable()).as("Tracer bean").isNull(); - } - - @Test - void shouldNotRegisterNoopTracerIfMicrometerTracingIsNotPresent() throws Exception { - try (FilteredClassLoader filteredClassLoader = new FilteredClassLoader("io.micrometer.tracing")) { - ContextCustomizer customizer = createContextCustomizer(NoAnnotation.class); - new ApplicationContextRunner().withClassLoader(filteredClassLoader) - .withInitializer(applyCustomizer(customizer)) - .run((context) -> { - assertThat(context).doesNotHaveBean(Tracer.class); - assertThatMetricsAreDisabled(context); - assertThatTracingIsDisabled(context); - }); - } - } - - @Test - void shouldBackOffOnCustomTracer() { - ContextCustomizer customizer = createContextCustomizer(NoAnnotation.class); - new ApplicationContextRunner().withConfiguration(UserConfigurations.of(CustomTracer.class)) - .withInitializer(applyCustomizer(customizer)) - .run((context) -> { - assertThat(context).hasSingleBean(Tracer.class); - assertThat(context).hasBean("customTracer"); - }); - } - - @Test - void shouldNotRunIfAotIsEnabled() { - ContextCustomizer customizer = createContextCustomizer(NoAnnotation.class); - new ApplicationContextRunner().withSystemProperties("spring.aot.enabled:true") - .withInitializer(applyCustomizer(customizer)) - .run((context) -> assertThat(context).doesNotHaveBean(Tracer.class)); - } - @Test void notEquals() { ContextCustomizer customizer1 = createContextCustomizer(OnlyMetrics.class); @@ -256,14 +196,4 @@ static class WithDisabledAnnotation { } - @Configuration(proxyBeanMethods = false) - static class CustomTracer { - - @Bean - Tracer customTracer() { - return mock(Tracer.class); - } - - } - } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/jdbc/ExampleEntityRowMapper.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/jdbc/ExampleEntityRowMapper.java new file mode 100644 index 000000000000..5897f26716bc --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/jdbc/ExampleEntityRowMapper.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.jdbc; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.springframework.jdbc.core.RowMapper; + +/** + * @author Stephane Nicoll + */ +class ExampleEntityRowMapper implements RowMapper { + + @Override + public ExampleEntity mapRow(ResultSet rs, int rowNum) throws SQLException { + int id = rs.getInt("id"); + String name = rs.getString("name"); + return new ExampleEntity(id, name); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/jdbc/ExampleJdbcClientRepository.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/jdbc/ExampleJdbcClientRepository.java new file mode 100644 index 000000000000..a104017292ab --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/jdbc/ExampleJdbcClientRepository.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.jdbc; + +import java.util.Collection; + +import org.springframework.jdbc.core.simple.JdbcClient; + +/** + * Example repository used with {@link JdbcClient JdbcClient} and + * {@link JdbcTest @JdbcTest} tests. + * + * @author Yanming Zhou + */ +class ExampleJdbcClientRepository { + + private static final ExampleEntityRowMapper ROW_MAPPER = new ExampleEntityRowMapper(); + + private final JdbcClient jdbcClient; + + ExampleJdbcClientRepository(JdbcClient jdbcClient) { + this.jdbcClient = jdbcClient; + } + + void save(ExampleEntity entity) { + this.jdbcClient.sql("insert into example (id, name) values (:id, :name)") + .param("id", entity.getId()) + .param("name", entity.getName()) + .update(); + } + + ExampleEntity findById(int id) { + return this.jdbcClient.sql("select id, name from example where id = :id") + .param("id", id) + .query(ROW_MAPPER) + .single(); + } + + Collection findAll() { + return this.jdbcClient.sql("select id, name from example").query(ROW_MAPPER).list(); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/jdbc/ExampleRepository.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/jdbc/ExampleRepository.java index bc238839cb3d..346a524e7de1 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/jdbc/ExampleRepository.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/jdbc/ExampleRepository.java @@ -16,14 +16,11 @@ package org.springframework.boot.test.autoconfigure.jdbc; -import java.sql.ResultSet; -import java.sql.SQLException; import java.util.Collection; import jakarta.transaction.Transactional; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; /** @@ -55,15 +52,4 @@ Collection findAll() { return this.jdbcTemplate.query("select id, name from example", ROW_MAPPER); } - static class ExampleEntityRowMapper implements RowMapper { - - @Override - public ExampleEntity mapRow(ResultSet rs, int rowNum) throws SQLException { - int id = rs.getInt("id"); - String name = rs.getString("name"); - return new ExampleEntity(id, name); - } - - } - } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/jdbc/JdbcTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/jdbc/JdbcTestIntegrationTests.java index 9cf7781d3b99..44d6fc2ed241 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/jdbc/JdbcTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/jdbc/JdbcTestIntegrationTests.java @@ -29,6 +29,7 @@ import org.springframework.boot.testcontainers.service.connection.ServiceConnectionAutoConfiguration; import org.springframework.context.ApplicationContext; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.simple.JdbcClient; import org.springframework.test.context.TestPropertySource; import static org.assertj.core.api.Assertions.assertThat; @@ -39,12 +40,16 @@ * Integration tests for {@link JdbcTest @JdbcTest}. * * @author Stephane Nicoll + * @author Yanming Zhou */ @JdbcTest @TestPropertySource( properties = "spring.sql.init.schemaLocations=classpath:org/springframework/boot/test/autoconfigure/jdbc/schema.sql") class JdbcTestIntegrationTests { + @Autowired + private JdbcClient jdbcClient; + @Autowired private JdbcTemplate jdbcTemplate; @@ -54,13 +59,30 @@ class JdbcTestIntegrationTests { @Autowired private ApplicationContext applicationContext; + @Test + void testJdbcClient() { + ExampleJdbcClientRepository repository = new ExampleJdbcClientRepository(this.jdbcClient); + repository.save(new ExampleEntity(1, "John")); + ExampleEntity entity = repository.findById(1); + assertThat(entity.getId()).isOne(); + assertThat(entity.getName()).isEqualTo("John"); + Collection entities = repository.findAll(); + assertThat(entities).hasSize(1); + entity = entities.iterator().next(); + assertThat(entity.getId()).isOne(); + assertThat(entity.getName()).isEqualTo("John"); + } + @Test void testJdbcTemplate() { ExampleRepository repository = new ExampleRepository(this.jdbcTemplate); repository.save(new ExampleEntity(1, "John")); + ExampleEntity entity = repository.findById(1); + assertThat(entity.getId()).isOne(); + assertThat(entity.getName()).isEqualTo("John"); Collection entities = repository.findAll(); assertThat(entities).hasSize(1); - ExampleEntity entity = entities.iterator().next(); + entity = entities.iterator().next(); assertThat(entity.getId()).isOne(); assertThat(entity.getName()).isEqualTo("John"); } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestIntegrationTests.java index 277677ab0f9a..f1f84cf1fe81 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestIntegrationTests.java @@ -28,6 +28,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.data.repository.config.BootstrapMode; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.simple.JdbcClient; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -39,6 +40,7 @@ * @author Phillip Webb * @author Andy Wilkinson * @author Scott Frederick + * @author Yanming Zhou */ @DataJpaTest class DataJpaTestIntegrationTests { @@ -46,6 +48,9 @@ class DataJpaTestIntegrationTests { @Autowired private TestEntityManager entities; + @Autowired + private JdbcClient jdbcClient; + @Autowired private JdbcTemplate jdbcTemplate; @@ -72,8 +77,10 @@ void testEntityManagerPersistAndGetId() { Long id = this.entities.persistAndGetId(new ExampleEntity("spring", "123"), Long.class); this.entities.flush(); assertThat(id).isNotNull(); - String reference = this.jdbcTemplate.queryForObject("SELECT REFERENCE FROM EXAMPLE_ENTITY WHERE ID = ?", - String.class, id); + String sql = "SELECT REFERENCE FROM EXAMPLE_ENTITY WHERE ID = ?"; + String reference = this.jdbcTemplate.queryForObject(sql, String.class, id); + assertThat(reference).isEqualTo("123"); + reference = this.jdbcClient.sql(sql).param(id).query(String.class).single(); assertThat(reference).isEqualTo("123"); } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/MockMvcRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/MockMvcRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java index c543a3e2ec9e..8a01e8dc7f0f 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/MockMvcRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/MockMvcRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import org.springframework.util.FileSystemUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -39,7 +39,6 @@ import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; /** * Integration tests for advanced configuration of @@ -54,7 +53,7 @@ class MockMvcRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests { @Autowired - private MockMvc mvc; + private MockMvcTester mvc; @Autowired private RestDocumentationResultHandler documentationHandler; @@ -68,10 +67,9 @@ void deleteSnippets() { } @Test - void snippetGeneration() throws Exception { - this.mvc.perform(get("/")) - .andDo(this.documentationHandler - .document(links(linkWithRel("self").description("Canonical location of this resource")))); + void snippetGeneration() { + assertThat(this.mvc.get().uri("/")).apply(this.documentationHandler + .document(links(linkWithRel("self").description("Canonical location of this resource")))); File defaultSnippetsDir = new File(this.generatedSnippets, "snippet-generation"); assertThat(defaultSnippetsDir).exists(); assertThat(contentOf(new File(defaultSnippetsDir, "curl-request.md"))).contains("'http://localhost:8080/'"); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/MockMvcRestDocsAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/MockMvcRestDocsAutoConfigurationIntegrationTests.java index 94523a8f65f5..50c7a6acca99 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/MockMvcRestDocsAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/MockMvcRestDocsAutoConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,13 +24,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.testsupport.BuildOutput; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import org.springframework.util.FileSystemUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; /** * Integration tests for {@link RestDocsAutoConfiguration} with Mock MVC. @@ -42,7 +41,7 @@ class MockMvcRestDocsAutoConfigurationIntegrationTests { @Autowired - private MockMvc mvc; + private MockMvcTester mvc; private File generatedSnippets; @@ -53,8 +52,8 @@ void deleteSnippets() { } @Test - void defaultSnippetsAreWritten() throws Exception { - this.mvc.perform(get("/")).andDo(document("default-snippets")); + void defaultSnippetsAreWritten() { + assertThat(this.mvc.get().uri("/")).apply(document("default-snippets")); File defaultSnippetsDir = new File(this.generatedSnippets, "default-snippets"); assertThat(defaultSnippetsDir).exists(); assertThat(contentOf(new File(defaultSnippetsDir, "curl-request.adoc"))).contains("'https://api.example.com/'"); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/security/MockMvcSecurityIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/security/MockMvcSecurityIntegrationTests.java index cc93138f8257..05a94e89139e 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/security/MockMvcSecurityIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/security/MockMvcSecurityIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,13 +23,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for MockMvc security. @@ -41,25 +41,25 @@ class MockMvcSecurityIntegrationTests { @Autowired - private MockMvc mockMvc; + private MockMvcTester mvc; @Test @WithMockUser(username = "test", password = "test", roles = "USER") - void okResponseWithMockUser() throws Exception { - this.mockMvc.perform(get("/")).andExpect(status().isOk()); + void okResponseWithMockUser() { + assertThat(this.mvc.get().uri("/")).hasStatusOk(); } @Test - void unauthorizedResponseWithNoUser() throws Exception { - this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized()); + void unauthorizedResponseWithNoUser() { + assertThat(this.mvc.get().uri("/").accept(MediaType.APPLICATION_JSON)).hasStatus(HttpStatus.UNAUTHORIZED); } @Test - void okResponseWithBasicAuthCredentialsForKnownUser() throws Exception { - this.mockMvc - .perform(get("/").header(HttpHeaders.AUTHORIZATION, - "Basic " + Base64.getEncoder().encodeToString("user:secret".getBytes()))) - .andExpect(status().isOk()); + void okResponseWithBasicAuthCredentialsForKnownUser() { + assertThat(this.mvc.get() + .uri("/") + .header(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString("user:secret".getBytes()))) + .hasStatusOk(); } } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestClient.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestClient.java deleted file mode 100644 index e7452ca904e4..000000000000 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestClient.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2012-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.test.autoconfigure.web.client; - -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; - -/** - * A second example web client used with {@link RestClientTest @RestClientTest} tests. - * - * @author Phillip Webb - */ -@Service -public class AnotherExampleRestClient { - - private final RestTemplate restTemplate; - - public AnotherExampleRestClient(RestTemplateBuilder builder) { - this.restTemplate = builder.rootUri("https://example.com").build(); - } - - protected RestTemplate getRestTemplate() { - return this.restTemplate; - } - - public String test() { - return this.restTemplate.getForEntity("/test", String.class).getBody(); - } - -} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestClientService.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestClientService.java new file mode 100644 index 000000000000..29d459100f41 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestClientService.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.web.client; + +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClient.Builder; + +/** + * A second example web client used with {@link RestClientTest @RestClientTest} tests. + * + * @author Scott Frederick + */ +@Service +public class AnotherExampleRestClientService { + + private final Builder builder; + + private final RestClient restClient; + + public AnotherExampleRestClientService(RestClient.Builder builder) { + this.builder = builder; + this.restClient = builder.baseUrl("https://example.com").build(); + } + + protected Builder getRestClientBuilder() { + return this.builder; + } + + public String test() { + return this.restClient.get().uri("/test").retrieve().toEntity(String.class).getBody(); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestTemplateService.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestTemplateService.java new file mode 100644 index 000000000000..a781d7cc80cc --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestTemplateService.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.web.client; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +/** + * A second example web client used with {@link RestClientTest @RestClientTest} tests. + * + * @author Phillip Webb + */ +@Service +public class AnotherExampleRestTemplateService { + + private final RestTemplate restTemplate; + + public AnotherExampleRestTemplateService(RestTemplateBuilder builder) { + this.restTemplate = builder.rootUri("https://example.com").build(); + } + + protected RestTemplate getRestTemplate() { + return this.restTemplate; + } + + public String test() { + return this.restTemplate.getForEntity("/test", String.class).getBody(); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerEnabledFalseIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerEnabledFalseIntegrationTests.java index 68c31ee5bd0e..fc55c4ad9974 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerEnabledFalseIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerEnabledFalseIntegrationTests.java @@ -20,6 +20,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.client.MockServerRestClientCustomizer; import org.springframework.boot.test.web.client.MockServerRestTemplateCustomizer; import org.springframework.context.ApplicationContext; @@ -43,6 +44,8 @@ class AutoConfigureMockRestServiceServerEnabledFalseIntegrationTests { void mockServerRestTemplateCustomizerShouldNotBeRegistered() { assertThatExceptionOfType(NoSuchBeanDefinitionException.class) .isThrownBy(() -> this.applicationContext.getBean(MockServerRestTemplateCustomizer.class)); + assertThatExceptionOfType(NoSuchBeanDefinitionException.class) + .isThrownBy(() -> this.applicationContext.getBean(MockServerRestClientCustomizer.class)); } } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerWithRestClientIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerWithRestClientIntegrationTests.java new file mode 100644 index 000000000000..7e7b5adcbd46 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerWithRestClientIntegrationTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.web.client; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClient.Builder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for + * {@link AutoConfigureMockRestServiceServer @AutoConfigureMockRestServiceServer} with a + * {@link RestClient} configured with a base URL. + * + * @author Scott Frederick + */ +@SpringBootTest +@AutoConfigureMockRestServiceServer +class AutoConfigureMockRestServiceServerWithRestClientIntegrationTests { + + @Autowired + private RestClient restClient; + + @Autowired + private MockRestServiceServer server; + + @Test + void mockServerExpectationsAreMatched() { + this.server.expect(requestTo("/rest/test")).andRespond(withSuccess("hello", MediaType.TEXT_HTML)); + ResponseEntity entity = this.restClient.get().uri("/test").retrieve().toEntity(String.class); + assertThat(entity.getBody()).isEqualTo("hello"); + } + + @EnableAutoConfiguration(exclude = CassandraAutoConfiguration.class) + @Configuration(proxyBeanMethods = false) + static class RootUriConfiguration { + + @Bean + RestClient restClient(Builder restClientBuilder) { + return restClientBuilder.baseUrl("/rest").build(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerWithRestTemplateRootUriIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerWithRestTemplateRootUriIntegrationTests.java new file mode 100644 index 000000000000..fabdbf9602ad --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerWithRestTemplateRootUriIntegrationTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.web.client; + +import io.micrometer.core.instrument.MeterRegistry; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for + * {@link AutoConfigureMockRestServiceServer @AutoConfigureMockRestServiceServer} with a + * {@link RestTemplate} configured with a root URI. + * + * @author Andy Wilkinson + */ +@SpringBootTest +@AutoConfigureMockRestServiceServer +class AutoConfigureMockRestServiceServerWithRestTemplateRootUriIntegrationTests { + + @Autowired + private RestTemplate restTemplate; + + @Autowired + private MockRestServiceServer server; + + @Autowired + MeterRegistry meterRegistry; + + @Test + void whenRestTemplateAppliesARootUriThenMockServerExpectationsAreStillMatched() { + this.server.expect(requestTo("/test")).andRespond(withSuccess("hello", MediaType.TEXT_HTML)); + ResponseEntity entity = this.restTemplate.getForEntity("/test", String.class); + assertThat(entity.getBody()).isEqualTo("hello"); + assertThat(this.meterRegistry.find("http.client.requests").tag("uri", "/test").timer()).isNotNull(); + } + + @EnableAutoConfiguration(exclude = CassandraAutoConfiguration.class) + @Configuration(proxyBeanMethods = false) + static class RootUriConfiguration { + + @Bean + RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) { + return restTemplateBuilder.rootUri("/rest").build(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerWithRootUriIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerWithRootUriIntegrationTests.java deleted file mode 100644 index 2ac9b41f4144..000000000000 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerWithRootUriIntegrationTests.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.test.autoconfigure.web.client; - -import io.micrometer.core.instrument.MeterRegistry; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.test.web.client.MockRestServiceServer; -import org.springframework.web.client.RestTemplate; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; -import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; - -/** - * Tests for - * {@link AutoConfigureMockRestServiceServer @AutoConfigureMockRestServiceServer} with a - * {@link RestTemplate} configured with a root URI. - * - * @author Andy Wilkinson - */ -@SpringBootTest -@AutoConfigureMockRestServiceServer -class AutoConfigureMockRestServiceServerWithRootUriIntegrationTests { - - @Autowired - private RestTemplate restTemplate; - - @Autowired - private MockRestServiceServer server; - - @Autowired - MeterRegistry meterRegistry; - - @Test - void whenRestTemplateAppliesARootUriThenMockServerExpectationsAreStillMatched() { - this.server.expect(requestTo("/test")).andRespond(withSuccess("hello", MediaType.TEXT_HTML)); - ResponseEntity entity = this.restTemplate.getForEntity("/test", String.class); - assertThat(entity.getBody()).isEqualTo("hello"); - assertThat(this.meterRegistry.find("http.client.requests").tag("uri", "/test").timer()).isNotNull(); - } - - @EnableAutoConfiguration(exclude = CassandraAutoConfiguration.class) - @Configuration(proxyBeanMethods = false) - static class RootUriConfiguration { - - @Bean - RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) { - return restTemplateBuilder.rootUri("/rest").build(); - } - - } - -} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestClient.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestClient.java deleted file mode 100644 index 06b923f7acbf..000000000000 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestClient.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.test.autoconfigure.web.client; - -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; - -/** - * Example web client used with {@link RestClientTest @RestClientTest} tests. - * - * @author Phillip Webb - */ -@Service -public class ExampleRestClient { - - private final RestTemplate restTemplate; - - public ExampleRestClient(RestTemplateBuilder builder) { - this.restTemplate = builder.rootUri("https://example.com").build(); - } - - protected RestTemplate getRestTemplate() { - return this.restTemplate; - } - - public String test() { - return this.restTemplate.getForEntity("/test", String.class).getBody(); - } - - public void testPostWithBody(String body) { - this.restTemplate.postForObject("/test", body, String.class); - } - -} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestClientService.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestClientService.java new file mode 100644 index 000000000000..f4e4c922a860 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestClientService.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.web.client; + +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClient.Builder; + +/** + * Example web client using {@code RestClient} with {@link RestClientTest @RestClientTest} + * tests. + * + * @author Scott Frederick + */ +@Service +public class ExampleRestClientService { + + private final Builder builder; + + private final RestClient restClient; + + public ExampleRestClientService(RestClient.Builder builder) { + this.builder = builder; + this.restClient = builder.baseUrl("https://example.com").build(); + } + + protected Builder getRestClientBuilder() { + return this.builder; + } + + public String test() { + return this.restClient.get().uri("/test").retrieve().toEntity(String.class).getBody(); + } + + public void testPostWithBody(String body) { + this.restClient.post().uri("/test").body(body).retrieve().toBodilessEntity(); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestTemplateService.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestTemplateService.java new file mode 100644 index 000000000000..95f55211753c --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestTemplateService.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.web.client; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +/** + * Example web client using {@code RestTemplate} with + * {@link RestClientTest @RestClientTest} tests. + * + * @author Phillip Webb + */ +@Service +public class ExampleRestTemplateService { + + private final RestTemplate restTemplate; + + public ExampleRestTemplateService(RestTemplateBuilder builder) { + this.restTemplate = builder.rootUri("https://example.com").build(); + } + + protected RestTemplate getRestTemplate() { + return this.restTemplate; + } + + public String test() { + return this.restTemplate.getForEntity("/test", String.class).getBody(); + } + + public void testPostWithBody(String body) { + this.restTemplate.postForObject("/test", body, String.class); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientRestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientRestIntegrationTests.java deleted file mode 100644 index 3966b197e28c..000000000000 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientRestIntegrationTests.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.test.autoconfigure.web.client; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.test.web.client.MockRestServiceServer; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.client.match.MockRestRequestMatchers.content; -import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; -import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; - -/** - * Tests for {@link RestClientTest @RestClientTest} gets reset after test methods. - * - * @author Phillip Webb - */ -@RestClientTest(ExampleRestClient.class) -class RestClientRestIntegrationTests { - - @Autowired - private MockRestServiceServer server; - - @Autowired - private ExampleRestClient client; - - @Test - void mockServerCall1() { - this.server.expect(requestTo("/test")).andRespond(withSuccess("1", MediaType.TEXT_HTML)); - assertThat(this.client.test()).isEqualTo("1"); - } - - @Test - void mockServerCall2() { - this.server.expect(requestTo("/test")).andRespond(withSuccess("2", MediaType.TEXT_HTML)); - assertThat(this.client.test()).isEqualTo("2"); - } - - @Test - void mockServerCallWithContent() { - this.server.expect(requestTo("/test")) - .andExpect(content().string("test")) - .andRespond(withSuccess("1", MediaType.TEXT_HTML)); - this.client.testPostWithBody("test"); - } - -} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestNoComponentIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestNoComponentIntegrationTests.java index 6ed3e63e46d5..3da4654a6b8d 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestNoComponentIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestNoComponentIntegrationTests.java @@ -50,7 +50,7 @@ class RestClientTestNoComponentIntegrationTests { @Test void exampleRestClientIsNotInjected() { assertThatExceptionOfType(NoSuchBeanDefinitionException.class) - .isThrownBy(() -> this.applicationContext.getBean(ExampleRestClient.class)); + .isThrownBy(() -> this.applicationContext.getBean(ExampleRestTemplateService.class)); } @Test @@ -61,7 +61,7 @@ void examplePropertiesIsNotInjected() { @Test void manuallyCreateBean() { - ExampleRestClient client = new ExampleRestClient(this.restTemplateBuilder); + ExampleRestTemplateService client = new ExampleRestTemplateService(this.restTemplateBuilder); this.server.expect(requestTo("/test")).andRespond(withSuccess("hello", MediaType.TEXT_HTML)); assertThat(client.test()).isEqualTo("hello"); } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestClientIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestClientIntegrationTests.java new file mode 100644 index 000000000000..098f6456a9b5 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestClientIntegrationTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.web.client; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.content; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for {@link RestClientTest @RestClientTest} with a {@link RestClient}. + * + * @author Scott Frederick + */ +@RestClientTest(ExampleRestClientService.class) +class RestClientTestRestClientIntegrationTests { + + @Autowired + private MockRestServiceServer server; + + @Autowired + private ExampleRestClientService client; + + @Test + void mockServerCall1() { + this.server.expect(requestTo(uri("/test"))).andRespond(withSuccess("1", MediaType.TEXT_HTML)); + assertThat(this.client.test()).isEqualTo("1"); + } + + @Test + void mockServerCall2() { + this.server.expect(requestTo(uri("/test"))).andRespond(withSuccess("2", MediaType.TEXT_HTML)); + assertThat(this.client.test()).isEqualTo("2"); + } + + @Test + void mockServerCallWithContent() { + this.server.expect(requestTo(uri("/test"))) + .andExpect(content().string("test")) + .andRespond(withSuccess("1", MediaType.TEXT_HTML)); + this.client.testPostWithBody("test"); + } + + private static String uri(String path) { + return "https://example.com" + path; + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestClientTwoComponentsIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestClientTwoComponentsIntegrationTests.java new file mode 100644 index 000000000000..15695607fe99 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestClientTwoComponentsIntegrationTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.web.client; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.client.MockServerRestClientCustomizer; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.MockRestServiceServer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for {@link RestClientTest @RestClientTest} with two {@code RestClient} clients. + * + * @author Phillip Webb + * @author Scott Frederick + */ +@RestClientTest({ ExampleRestClientService.class, AnotherExampleRestClientService.class }) +class RestClientTestRestClientTwoComponentsIntegrationTests { + + @Autowired + private ExampleRestClientService client1; + + @Autowired + private AnotherExampleRestClientService client2; + + @Autowired + private MockServerRestClientCustomizer customizer; + + @Autowired + private MockRestServiceServer server; + + @Test + void serverShouldNotWork() { + assertThatIllegalStateException().isThrownBy( + () -> this.server.expect(requestTo(uri("/test"))).andRespond(withSuccess("hello", MediaType.TEXT_HTML))) + .withMessageContaining("Unable to use auto-configured"); + } + + @Test + void client1RestCallViaCustomizer() { + this.customizer.getServer(this.client1.getRestClientBuilder()) + .expect(requestTo(uri("/test"))) + .andRespond(withSuccess("hello", MediaType.TEXT_HTML)); + assertThat(this.client1.test()).isEqualTo("hello"); + } + + @Test + void client2RestCallViaCustomizer() { + this.customizer.getServer(this.client2.getRestClientBuilder()) + .expect(requestTo(uri("/test"))) + .andRespond(withSuccess("there", MediaType.TEXT_HTML)); + assertThat(this.client2.test()).isEqualTo("there"); + } + + private static String uri(String path) { + return "https://example.com" + path; + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestTemplateAndRestClientTogetherIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestTemplateAndRestClientTogetherIntegrationTests.java new file mode 100644 index 000000000000..92edda4ef598 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestTemplateAndRestClientTogetherIntegrationTests.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.web.client; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.client.MockServerRestClientCustomizer; +import org.springframework.boot.test.web.client.MockServerRestTemplateCustomizer; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.MockRestServiceServer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for {@link RestClientTest @RestClientTest} with a {@code RestTemplate} and a + * {@code RestClient} clients. + * + * @author Scott Frederick + */ +@RestClientTest({ ExampleRestTemplateService.class, ExampleRestClientService.class }) +class RestClientTestRestTemplateAndRestClientTogetherIntegrationTests { + + @Autowired + private ExampleRestTemplateService restTemplateClient; + + @Autowired + private ExampleRestClientService restClientClient; + + @Autowired + private MockServerRestTemplateCustomizer templateCustomizer; + + @Autowired + private MockServerRestClientCustomizer clientCustomizer; + + @Autowired + private MockRestServiceServer server; + + @Test + void serverShouldNotWork() { + assertThatIllegalStateException().isThrownBy( + () -> this.server.expect(requestTo(uri("/test"))).andRespond(withSuccess("hello", MediaType.TEXT_HTML))) + .withMessageContaining("Unable to use auto-configured"); + } + + @Test + void restTemplateClientRestCallViaCustomizer() { + this.templateCustomizer.getServer() + .expect(requestTo("/test")) + .andRespond(withSuccess("hello", MediaType.TEXT_HTML)); + assertThat(this.restTemplateClient.test()).isEqualTo("hello"); + } + + @Test + void restClientClientRestCallViaCustomizer() { + this.clientCustomizer.getServer() + .expect(requestTo(uri("/test"))) + .andRespond(withSuccess("there", MediaType.TEXT_HTML)); + assertThat(this.restClientClient.test()).isEqualTo("there"); + } + + private static String uri(String path) { + return "https://example.com" + path; + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestTemplateIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestTemplateIntegrationTests.java new file mode 100644 index 000000000000..7f92f7550070 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestTemplateIntegrationTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.web.client; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.MockRestServiceServer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.content; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for {@link RestClientTest @RestClientTest} gets reset after test methods. + * + * @author Phillip Webb + */ +@RestClientTest(ExampleRestTemplateService.class) +class RestClientTestRestTemplateIntegrationTests { + + @Autowired + private MockRestServiceServer server; + + @Autowired + private ExampleRestTemplateService client; + + @Test + void mockServerCall1() { + this.server.expect(requestTo("/test")).andRespond(withSuccess("1", MediaType.TEXT_HTML)); + assertThat(this.client.test()).isEqualTo("1"); + } + + @Test + void mockServerCall2() { + this.server.expect(requestTo("/test")).andRespond(withSuccess("2", MediaType.TEXT_HTML)); + assertThat(this.client.test()).isEqualTo("2"); + } + + @Test + void mockServerCallWithContent() { + this.server.expect(requestTo("/test")) + .andExpect(content().string("test")) + .andRespond(withSuccess("1", MediaType.TEXT_HTML)); + this.client.testPostWithBody("test"); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestTemplateTwoComponentsIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestTemplateTwoComponentsIntegrationTests.java new file mode 100644 index 000000000000..c106df3776fc --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestTemplateTwoComponentsIntegrationTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.web.client; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.client.MockServerRestTemplateCustomizer; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.MockRestServiceServer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for {@link RestClientTest @RestClientTest} with two {@code RestTemplate} clients. + * + * @author Phillip Webb + */ +@RestClientTest({ ExampleRestTemplateService.class, AnotherExampleRestTemplateService.class }) +class RestClientTestRestTemplateTwoComponentsIntegrationTests { + + @Autowired + private ExampleRestTemplateService client1; + + @Autowired + private AnotherExampleRestTemplateService client2; + + @Autowired + private MockServerRestTemplateCustomizer customizer; + + @Autowired + private MockRestServiceServer server; + + @Test + void serverShouldNotWork() { + assertThatIllegalStateException() + .isThrownBy( + () -> this.server.expect(requestTo("/test")).andRespond(withSuccess("hello", MediaType.TEXT_HTML))) + .withMessageContaining("Unable to use auto-configured"); + } + + @Test + void client1RestCallViaCustomizer() { + this.customizer.getServer(this.client1.getRestTemplate()) + .expect(requestTo("/test")) + .andRespond(withSuccess("hello", MediaType.TEXT_HTML)); + assertThat(this.client1.test()).isEqualTo("hello"); + } + + @Test + void client2RestCallViaCustomizer() { + this.customizer.getServer(this.client2.getRestTemplate()) + .expect(requestTo("/test")) + .andRespond(withSuccess("there", MediaType.TEXT_HTML)); + assertThat(this.client2.test()).isEqualTo("there"); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestTwoComponentsIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestTwoComponentsIntegrationTests.java deleted file mode 100644 index 08e00159c61a..000000000000 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestTwoComponentsIntegrationTests.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.test.autoconfigure.web.client; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.web.client.MockServerRestTemplateCustomizer; -import org.springframework.http.MediaType; -import org.springframework.test.web.client.MockRestServiceServer; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; -import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; -import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; - -/** - * Tests for {@link RestClientTest @RestClientTest} with two clients. - * - * @author Phillip Webb - */ -@RestClientTest({ ExampleRestClient.class, AnotherExampleRestClient.class }) -class RestClientTestTwoComponentsIntegrationTests { - - @Autowired - private ExampleRestClient client1; - - @Autowired - private AnotherExampleRestClient client2; - - @Autowired - private MockServerRestTemplateCustomizer customizer; - - @Autowired - private MockRestServiceServer server; - - @Test - void serverShouldNotWork() { - assertThatIllegalStateException() - .isThrownBy( - () -> this.server.expect(requestTo("/test")).andRespond(withSuccess("hello", MediaType.TEXT_HTML))) - .withMessageContaining("Unable to use auto-configured"); - } - - @Test - void client1RestCallViaCustomizer() { - this.customizer.getServer(this.client1.getRestTemplate()) - .expect(requestTo("/test")) - .andRespond(withSuccess("hello", MediaType.TEXT_HTML)); - assertThat(this.client1.test()).isEqualTo("hello"); - } - - @Test - void client2RestCallViaCustomizer() { - this.customizer.getServer(this.client2.getRestTemplate()) - .expect(requestTo("/test")) - .andRespond(withSuccess("there", MediaType.TEXT_HTML)); - assertThat(this.client2.test()).isEqualTo("there"); - } - -} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithComponentIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithComponentIntegrationTests.java deleted file mode 100644 index 09ba31463e93..000000000000 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithComponentIntegrationTests.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.test.autoconfigure.web.client; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.test.web.client.MockRestServiceServer; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; -import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; - -/** - * Tests for {@link RestClientTest @RestClientTest} with a single client. - * - * @author Phillip Webb - */ -@RestClientTest(ExampleRestClient.class) -class RestClientTestWithComponentIntegrationTests { - - @Autowired - private MockRestServiceServer server; - - @Autowired - private ExampleRestClient client; - - @Test - void mockServerCall() { - this.server.expect(requestTo("/test")).andRespond(withSuccess("hello", MediaType.TEXT_HTML)); - assertThat(this.client.test()).isEqualTo("hello"); - } - -} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithRestClientComponentIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithRestClientComponentIntegrationTests.java new file mode 100644 index 000000000000..954ecfbb1bb5 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithRestClientComponentIntegrationTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.web.client; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.MockRestServiceServer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for {@link RestClientTest @RestClientTest} with a single client using + * {@code RestClient}. + * + * @author Phillip Webb + * @author Scott Frederick + */ +@RestClientTest(ExampleRestClientService.class) +class RestClientTestWithRestClientComponentIntegrationTests { + + @Autowired + private MockRestServiceServer server; + + @Autowired + private ExampleRestClientService client; + + @Test + void mockServerCall() { + this.server.expect(requestTo("https://example.com/test")).andRespond(withSuccess("hello", MediaType.TEXT_HTML)); + assertThat(this.client.test()).isEqualTo("hello"); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithRestTemplateComponentIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithRestTemplateComponentIntegrationTests.java new file mode 100644 index 000000000000..f495765f1823 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithRestTemplateComponentIntegrationTests.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.web.client; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.MockRestServiceServer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for {@link RestClientTest @RestClientTest} with a single client using + * {@code RestTemplate}. + * + * @author Phillip Webb + */ +@RestClientTest(ExampleRestTemplateService.class) +class RestClientTestWithRestTemplateComponentIntegrationTests { + + @Autowired + private MockRestServiceServer server; + + @Autowired + private ExampleRestTemplateService client; + + @Test + void mockServerCall() { + this.server.expect(requestTo("/test")).andRespond(withSuccess("hello", MediaType.TEXT_HTML)); + assertThat(this.client.test()).isEqualTo("hello"); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithoutJacksonIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithoutJacksonIntegrationTests.java index b6aa993467b5..250f50f5d183 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithoutJacksonIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithoutJacksonIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,14 +34,14 @@ * @author Andy Wilkinson */ @ClassPathExclusions("jackson-*.jar") -@RestClientTest(ExampleRestClient.class) +@RestClientTest(ExampleRestTemplateService.class) class RestClientTestWithoutJacksonIntegrationTests { @Autowired private MockRestServiceServer server; @Autowired - private ExampleRestClient client; + private ExampleRestTemplateService client; @Test void restClientTestCanBeUsedWhenJacksonIsNotOnTheClassPath() { diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientWithRestTemplateBuilderTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientWithRestTemplateBuilderTests.java new file mode 100644 index 000000000000..6a216e114e81 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientWithRestTemplateBuilderTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.web.client; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClient.Builder; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for building a {@link RestClient} from a {@link RestTemplateBuilder}. + * + * @author Scott Frederick + */ +class RestClientWithRestTemplateBuilderTests { + + @Test + void buildUsingRestTemplateBuilderRootUri() { + RestTemplate restTemplate = new RestTemplateBuilder().rootUri("https://resttemplate.example.com").build(); + RestClient.Builder builder = RestClient.builder(restTemplate); + RestClient client = buildMockedClient(builder, "https://resttemplate.example.com/test"); + assertThat(client.get().uri("/test").retrieve().toBodilessEntity().getStatusCode().is2xxSuccessful()).isTrue(); + } + + @Test + void buildUsingRestClientBuilderBaseUrl() { + RestTemplate restTemplate = new RestTemplateBuilder().build(); + RestClient.Builder builder = RestClient.builder(restTemplate).baseUrl("https://restclient.example.com"); + RestClient client = buildMockedClient(builder, "https://restclient.example.com/test"); + assertThat(client.get().uri("/test").retrieve().toBodilessEntity().getStatusCode().is2xxSuccessful()).isTrue(); + } + + @Test + void buildRestTemplateBuilderRootUriAndRestClientBuilderBaseUrl() { + RestTemplate restTemplate = new RestTemplateBuilder().rootUri("https://resttemplate.example.com").build(); + RestClient.Builder builder = RestClient.builder(restTemplate).baseUrl("https://restclient.example.com"); + RestClient client = buildMockedClient(builder, "https://resttemplate.example.com/test"); + assertThat(client.get().uri("/test").retrieve().toBodilessEntity().getStatusCode().is2xxSuccessful()).isTrue(); + } + + private RestClient buildMockedClient(Builder builder, String url) { + MockRestServiceServer server = MockRestServiceServer.bindTo(builder).build(); + server.expect(requestTo(url)).andRespond(withSuccess()); + return builder.build(); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientWithRestTemplateTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientWithRestTemplateTests.java new file mode 100644 index 000000000000..556991d9347f --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientWithRestTemplateTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.web.client; + +import org.junit.jupiter.api.Test; + +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClient.Builder; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.DefaultUriBuilderFactory; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for building a {@link RestClient} from a {@link RestTemplate}. + * + * @author Scott Frederick + */ +class RestClientWithRestTemplateTests { + + @Test + void buildUsingRestTemplateUriTemplateHandler() { + RestTemplate restTemplate = new RestTemplate(); + DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("https://resttemplate.example.com"); + restTemplate.setUriTemplateHandler(uriBuilderFactory); + Builder builder = RestClient.builder(restTemplate); + RestClient client = buildMockedClient(builder, "https://resttemplate.example.com/test"); + assertThat(client.get().uri("/test").retrieve().toBodilessEntity().getStatusCode().is2xxSuccessful()).isTrue(); + } + + @Test + void buildUsingRestClientBuilderBaseUrl() { + RestTemplate restTemplate = new RestTemplate(); + Builder builder = RestClient.builder(restTemplate).baseUrl("https://restclient.example.com"); + RestClient client = buildMockedClient(builder, "https://restclient.example.com/test"); + assertThat(client.get().uri("/test").retrieve().toBodilessEntity().getStatusCode().is2xxSuccessful()).isTrue(); + } + + @Test + void buildUsingRestTemplateUriTemplateHandlerAndRestClientBuilderBaseUrl() { + RestTemplate restTemplate = new RestTemplate(); + DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("https://resttemplate.example.com"); + restTemplate.setUriTemplateHandler(uriBuilderFactory); + Builder builder = RestClient.builder(restTemplate).baseUrl("https://restclient.example.com"); + RestClient client = buildMockedClient(builder, "https://resttemplate.example.com/test"); + assertThat(client.get().uri("/test").retrieve().toBodilessEntity().getStatusCode().is2xxSuccessful()).isTrue(); + } + + private RestClient buildMockedClient(Builder builder, String url) { + MockRestServiceServer server = MockRestServiceServer.bindTo(builder).build(); + server.expect(requestTo(url)).andRespond(withSuccess()); + return builder.build(); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfigurationTests.java index 4c88271d307f..7151ef667948 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.servlet.DispatcherServlet; @@ -54,6 +56,28 @@ void registersDispatcherServletFromMockMvc() { }); } + @Test + void registersMockMvcTester() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(MockMvcTester.class)); + } + + @Test + void shouldNotRegisterMockMvcTesterIfAssertJMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader(org.assertj.core.api.Assert.class)) + .run((context) -> assertThat(context).doesNotHaveBean(MockMvcTester.class)); + } + + @Test + void registeredMockMvcTesterDelegatesToConfiguredMockMvc() { + MockMvc mockMvc = mock(MockMvc.class); + this.contextRunner.withBean("customMockMvc", MockMvc.class, () -> mockMvc).run((context) -> { + assertThat(context).hasSingleBean(MockMvc.class).hasSingleBean(MockMvcTester.class); + MockMvcTester mvc = context.getBean(MockMvcTester.class); + mvc.get().uri("/dummy").exchange(); + then(mockMvc).should().perform(any(RequestBuilder.class)); + }); + } + @Test void registersWebTestClient() { this.contextRunner.run((context) -> assertThat(context).hasSingleBean(WebTestClient.class)); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/SpringBootMockMvcBuilderCustomizerTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/SpringBootMockMvcBuilderCustomizerTests.java index 16c8126396cd..87c5cee78d66 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/SpringBootMockMvcBuilderCustomizerTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/SpringBootMockMvcBuilderCustomizerTests.java @@ -18,16 +18,22 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import jakarta.servlet.DispatcherType; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServlet; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.web.servlet.SpringBootMockMvcBuilderCustomizer.DeferredLinesWriter; @@ -37,11 +43,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.mock.web.MockServletContext; -import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; /** * Tests for {@link SpringBootMockMvcBuilderCustomizer}. @@ -50,10 +57,7 @@ */ class SpringBootMockMvcBuilderCustomizerTests { - private SpringBootMockMvcBuilderCustomizer customizer; - @Test - @SuppressWarnings("unchecked") void customizeShouldAddFilters() { AnnotationConfigServletWebApplicationContext context = new AnnotationConfigServletWebApplicationContext(); MockServletContext servletContext = new MockServletContext(); @@ -61,14 +65,20 @@ void customizeShouldAddFilters() { context.register(ServletConfiguration.class, FilterConfiguration.class); context.refresh(); DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(context); - this.customizer = new SpringBootMockMvcBuilderCustomizer(context); - this.customizer.customize(builder); - FilterRegistrationBean registrationBean = (FilterRegistrationBean) context - .getBean("filterRegistrationBean"); - Filter testFilter = (Filter) context.getBean("testFilter"); - Filter otherTestFilter = registrationBean.getFilter(); - List filters = (List) ReflectionTestUtils.getField(builder, "filters"); - assertThat(filters).containsExactlyInAnyOrder(testFilter, otherTestFilter); + SpringBootMockMvcBuilderCustomizer customizer = new SpringBootMockMvcBuilderCustomizer(context); + customizer.customize(builder); + FilterRegistrationBean registrationBean = (FilterRegistrationBean) context.getBean("otherTestFilter"); + TestFilter testFilter = context.getBean("testFilter", TestFilter.class); + OtherTestFilter otherTestFilter = (OtherTestFilter) registrationBean.getFilter(); + assertThat(builder).extracting("filters", as(InstanceOfAssertFactories.LIST)) + .extracting("delegate", "dispatcherTypes") + .containsExactlyInAnyOrder(tuple(testFilter, EnumSet.of(DispatcherType.REQUEST)), + tuple(otherTestFilter, EnumSet.of(DispatcherType.REQUEST, DispatcherType.ERROR))); + builder.build(); + assertThat(testFilter.filterName).isEqualTo("testFilter"); + assertThat(testFilter.initParams).isEmpty(); + assertThat(otherTestFilter.filterName).isEqualTo("otherTestFilter"); + assertThat(otherTestFilter.initParams).isEqualTo(Map.of("a", "alpha", "b", "bravo")); } @Test @@ -94,7 +104,7 @@ void whenCalledInParallelDeferredLinesWriterSeparatesOutputByThread() throws Exc }); thread.start(); } - latch.await(60, TimeUnit.SECONDS); + assertThat(latch.await(60, TimeUnit.SECONDS)).isTrue(); assertThat(delegate.allWritten).hasSize(10000); assertThat(delegate.allWritten) @@ -131,8 +141,12 @@ TestServlet testServlet() { static class FilterConfiguration { @Bean - FilterRegistrationBean filterRegistrationBean() { - return new FilterRegistrationBean<>(new OtherTestFilter()); + FilterRegistrationBean otherTestFilter() { + FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>( + new OtherTestFilter()); + filterRegistrationBean.setInitParameters(Map.of("a", "alpha", "b", "bravo")); + filterRegistrationBean.setDispatcherTypes(EnumSet.of(DispatcherType.REQUEST, DispatcherType.ERROR)); + return filterRegistrationBean; } @Bean @@ -148,9 +162,15 @@ static class TestServlet extends HttpServlet { static class TestFilter implements Filter { + private String filterName; + + private Map initParams = new HashMap<>(); + @Override public void init(FilterConfig filterConfig) { - + this.filterName = filterConfig.getFilterName(); + Collections.list(filterConfig.getInitParameterNames()) + .forEach((name) -> this.initParams.put(name, filterConfig.getInitParameter(name))); } @Override @@ -167,9 +187,15 @@ public void destroy() { static class OtherTestFilter implements Filter { + private String filterName; + + private Map initParams = new HashMap<>(); + @Override public void init(FilterConfig filterConfig) { - + this.filterName = filterConfig.getFilterName(); + Collections.list(filterConfig.getInitParameterNames()) + .forEach((name) -> this.initParams.put(name, filterConfig.getInitParameter(name))); } @Override diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/AutoConfigureMockMvcSecurityFilterOrderingIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/AutoConfigureMockMvcSecurityFilterOrderingIntegrationTests.java index fdd388598405..18f3ef11a65a 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/AutoConfigureMockMvcSecurityFilterOrderingIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/AutoConfigureMockMvcSecurityFilterOrderingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,11 +23,9 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.context.annotation.Import; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link AutoConfigureMockMvc @AutoConfigureMockMvc} and the ordering of Spring @@ -41,11 +39,11 @@ class AutoConfigureMockMvcSecurityFilterOrderingIntegrationTests { @Autowired - private MockMvc mvc; + private MockMvcTester mvc; @Test - void afterSecurityFilterShouldFindAUserPrincipal() throws Exception { - this.mvc.perform(get("/one")).andExpect(status().isOk()).andExpect(content().string("user")); + void afterSecurityFilterShouldFindAUserPrincipal() { + assertThat(this.mvc.get().uri("/one")).hasStatusOk().hasBodyTextEqualTo("user"); } } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/MockMvcSpringBootTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/MockMvcSpringBootTestIntegrationTests.java index b267b7391af7..30ac47d27afe 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/MockMvcSpringBootTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/MockMvcSpringBootTestIntegrationTests.java @@ -23,11 +23,11 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrint; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.context.ApplicationContext; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.servlet.MockMvc; @@ -39,6 +39,8 @@ /** * Tests for {@link SpringBootTest @SpringBootTest} with * {@link AutoConfigureMockMvc @AutoConfigureMockMvc} (i.e. full integration test). + *

+ * This uses the regular {@link MockMvc} (Hamcrest integration). * * @author Phillip Webb * @author Moritz Halbritter @@ -49,7 +51,7 @@ @ExtendWith(OutputCaptureExtension.class) class MockMvcSpringBootTestIntegrationTests { - @MockBean + @MockitoBean private ExampleMockableService service; @Autowired diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/MockMvcTesterSpringBootTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/MockMvcTesterSpringBootTestIntegrationTests.java new file mode 100644 index 000000000000..4c66f5a9943a --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/MockMvcTesterSpringBootTestIntegrationTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.web.servlet.mockmvc; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrint; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.context.ApplicationContext; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.test.web.servlet.assertj.MockMvcTester; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SpringBootTest @SpringBootTest} with + * {@link AutoConfigureMockMvc @AutoConfigureMockMvc} (i.e. full integration test). + *

+ * This uses {@link MockMvcTester} (AssertJ integration). + * + * @author Stephane Nicoll + */ +@SpringBootTest +@AutoConfigureMockMvc(print = MockMvcPrint.SYSTEM_ERR, printOnlyOnFailure = false) +@WithMockUser(username = "user", password = "secret") +@ExtendWith(OutputCaptureExtension.class) +class MockMvcTesterSpringBootTestIntegrationTests { + + @MockitoBean + private ExampleMockableService service; + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private MockMvcTester mvc; + + @Test + void shouldFindController1(CapturedOutput output) { + assertThat(this.mvc.get().uri("/one")).hasStatusOk().hasBodyTextEqualTo("one"); + assertThat(output).contains("Request URI = /one"); + } + + @Test + void shouldFindController2() { + assertThat(this.mvc.get().uri("/two")).hasStatusOk().hasBodyTextEqualTo("hellotwo"); + } + + @Test + void shouldFindControllerAdvice() { + assertThat(this.mvc.get().uri("/error")).hasStatusOk().hasBodyTextEqualTo("recovered"); + } + + @Test + void shouldHaveRealService() { + assertThat(this.applicationContext.getBean(ExampleRealService.class)).isNotNull(); + } + + @Test + void shouldTestWithWebTestClient(@Autowired WebTestClient webTestClient) { + webTestClient.get().uri("/one").exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("one"); + } + + @Test + void shouldNotFailIfFormattingValueThrowsException(CapturedOutput output) { + assertThat(this.mvc.get().uri("/formatting")).hasStatusOk().hasBodyTextEqualTo("formatting"); + assertThat(output).contains( + "Session Attrs = << Exception 'java.lang.IllegalStateException: Formatting failed' occurred while formatting >>"); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestAllControllersIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestAllControllersIntegrationTests.java index 3dc1bd719af4..88048c5f711d 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestAllControllersIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestAllControllersIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.boot.test.autoconfigure.web.servlet.mockmvc; +import java.util.function.Consumer; + import jakarta.servlet.ServletException; import jakarta.validation.ConstraintViolationException; import org.junit.jupiter.api.Test; @@ -24,13 +26,10 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.web.servlet.error.ErrorAttributes; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; +import org.springframework.test.web.servlet.assertj.MvcTestResult; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link WebMvcTest @WebMvcTest} when no explicit controller is defined. @@ -43,35 +42,36 @@ class WebMvcTestAllControllersIntegrationTests { @Autowired - private MockMvc mvc; + private MockMvcTester mvc; @Autowired(required = false) private ErrorAttributes errorAttributes; @Test - void shouldFindController1() throws Exception { - this.mvc.perform(get("/one")).andExpect(content().string("one")).andExpect(status().isOk()); + void shouldFindController1() { + assertThat(this.mvc.get().uri("/one")).satisfies(hasBody("one")); } @Test - void shouldFindController2() throws Exception { - this.mvc.perform(get("/two")).andExpect(content().string("hellotwo")).andExpect(status().isOk()); + void shouldFindController2() { + assertThat(this.mvc.get().uri("/two")).satisfies(hasBody("hellotwo")); } @Test - void shouldFindControllerAdvice() throws Exception { - this.mvc.perform(get("/error")).andExpect(content().string("recovered")).andExpect(status().isOk()); + void shouldFindControllerAdvice() { + assertThat(this.mvc.get().uri("/error")).satisfies(hasBody("recovered")); } @Test - void shouldRunValidationSuccess() throws Exception { - this.mvc.perform(get("/three/OK")).andExpect(status().isOk()).andExpect(content().string("Hello OK")); + void shouldRunValidationSuccess() { + assertThat(this.mvc.get().uri("/three/OK")).satisfies(hasBody("Hello OK")); } @Test void shouldRunValidationFailure() { - assertThatExceptionOfType(ServletException.class).isThrownBy(() -> this.mvc.perform(get("/three/invalid"))) - .withCauseInstanceOf(ConstraintViolationException.class); + assertThat(this.mvc.get().uri("/three/invalid")).failure() + .isInstanceOf(ServletException.class) + .hasCauseInstanceOf(ConstraintViolationException.class); } @Test @@ -80,4 +80,8 @@ void shouldNotFilterErrorAttributes() { } + private Consumer hasBody(String expected) { + return (result) -> assertThat(result).hasStatusOk().hasBodyTextEqualTo(expected); + } + } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestConverterIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestConverterIntegrationTests.java index 1a618ea4db2d..0e31f1dcedf2 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestConverterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestConverterIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,11 +23,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link WebMvcTest @WebMvcTest} to validate converters are discovered. @@ -39,12 +37,12 @@ class WebMvcTestConverterIntegrationTests { @Autowired - private MockMvc mvc; + private MockMvcTester mvc; @Test - void shouldFindConverter() throws Exception { + void shouldFindConverter() { String id = UUID.randomUUID().toString(); - this.mvc.perform(get("/two/" + id)).andExpect(content().string(id + "two")).andExpect(status().isOk()); + assertThat(this.mvc.get().uri("/two/" + id)).hasStatusOk().hasBodyTextEqualTo(id + "two"); } } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestCustomDispatcherServletIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestCustomDispatcherServletIntegrationTests.java index f7f5c8e5fcc2..c3e617f0b003 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestCustomDispatcherServletIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestCustomDispatcherServletIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,14 +20,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import org.springframework.web.servlet.DispatcherServlet; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.assertj.core.api.Assertions.assertThat; /** * Tests for Test {@link DispatcherServlet} customizations. @@ -41,13 +40,12 @@ class WebMvcTestCustomDispatcherServletIntegrationTests { @Autowired - private MockMvc mvc; + private MockMvcTester mvc; @Test - void dispatcherServletIsCustomized() throws Exception { - this.mvc.perform(get("/does-not-exist")) - .andExpect(status().isBadRequest()) - .andExpect(content().string("Invalid request: /does-not-exist")); + void dispatcherServletIsCustomized() { + assertThat(this.mvc.get().uri("/does-not-exist")).hasStatus(HttpStatus.BAD_REQUEST) + .hasBodyTextEqualTo("Invalid request: /does-not-exist"); } } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestHateoasIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestHateoasIntegrationTests.java index 735ebb950f0e..1b3c577a94e3 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestHateoasIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestHateoasIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,12 +20,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.http.HttpHeaders; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link WebMvcTest @WebMvcTest} and Spring HATEOAS. @@ -37,18 +35,16 @@ class WebMvcTestHateoasIntegrationTests { @Autowired - private MockMvc mockMvc; + private MockMvcTester mvc; @Test - void plainResponse() throws Exception { - this.mockMvc.perform(get("/hateoas/plain")) - .andExpect(header().string(HttpHeaders.CONTENT_TYPE, "application/json")); + void plainResponse() { + assertThat(this.mvc.get().uri("/hateoas/plain")).hasContentType("application/json"); } @Test - void hateoasResponse() throws Exception { - this.mockMvc.perform(get("/hateoas/resource")) - .andExpect(header().string(HttpHeaders.CONTENT_TYPE, "application/hal+json")); + void hateoasResponse() { + assertThat(this.mvc.get().uri("/hateoas/resource")).hasContentType("application/hal+json"); } } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestNestedIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestNestedIntegrationTests.java index 7dcdb8ee99ba..05dc2732f35c 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestNestedIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestNestedIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,12 +21,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link WebMvcTest @WebMvcTest} using {@link Nested}. @@ -38,16 +37,16 @@ class WebMvcTestNestedIntegrationTests { @Autowired - private MockMvc mvc; + private MockMvcTester mvc; @Test - void shouldNotFindController1() throws Exception { - this.mvc.perform(get("/one")).andExpect(status().isNotFound()); + void shouldNotFindController1() { + assertThat(this.mvc.get().uri("/one")).hasStatus(HttpStatus.NOT_FOUND); } @Test - void shouldFindController2() throws Exception { - this.mvc.perform(get("/two")).andExpect(content().string("hellotwo")).andExpect(status().isOk()); + void shouldFindController2() { + assertThat(this.mvc.get().uri("/two")).hasStatusOk().hasBodyTextEqualTo("hellotwo"); } @Nested @@ -55,15 +54,14 @@ void shouldFindController2() throws Exception { class NestedTests { @Test - void shouldNotFindController1() throws Exception { - WebMvcTestNestedIntegrationTests.this.mvc.perform(get("/one")).andExpect(status().isNotFound()); + void shouldNotFindController1() { + assertThat(WebMvcTestNestedIntegrationTests.this.mvc.get().uri("/one")).hasStatus(HttpStatus.NOT_FOUND); } @Test - void shouldFindController2() throws Exception { - WebMvcTestNestedIntegrationTests.this.mvc.perform(get("/two")) - .andExpect(content().string("hellotwo")) - .andExpect(status().isOk()); + void shouldFindController2() { + assertThat(WebMvcTestNestedIntegrationTests.this.mvc.get().uri("/two")).hasStatusOk() + .hasBodyTextEqualTo("hellotwo"); } } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestOneControllerIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestOneControllerIntegrationTests.java index a458ea1e87cf..285601171fad 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestOneControllerIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestOneControllerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,12 +20,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link WebMvcTest @WebMvcTest} when a specific controller is defined. @@ -37,16 +36,16 @@ class WebMvcTestOneControllerIntegrationTests { @Autowired - private MockMvc mvc; + private MockMvcTester mvc; @Test - void shouldNotFindController1() throws Exception { - this.mvc.perform(get("/one")).andExpect(status().isNotFound()); + void shouldNotFindController1() { + assertThat(this.mvc.get().uri("/one")).hasStatus(HttpStatus.NOT_FOUND); } @Test - void shouldFindController2() throws Exception { - this.mvc.perform(get("/two")).andExpect(content().string("hellotwo")).andExpect(status().isOk()); + void shouldFindController2() { + assertThat(this.mvc.get().uri("/two")).hasStatusOk().hasBodyTextEqualTo("hellotwo"); } } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPageableIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPageableIntegrationTests.java index 7482bb090ce6..cfb20b7a3f3b 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPageableIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPageableIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,11 +21,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link WebMvcTest @WebMvcTest} and Pageable support. @@ -37,13 +35,12 @@ class WebMvcTestPageableIntegrationTests { @Autowired - private MockMvc mvc; + private MockMvcTester mvc; @Test - void shouldSupportPageable() throws Exception { - this.mvc.perform(get("/paged").param("page", "2").param("size", "42")) - .andExpect(status().isOk()) - .andExpect(content().string("2:42")); + void shouldSupportPageable() { + assertThat(this.mvc.get().uri("/paged").param("page", "2").param("size", "42")).hasStatusOk() + .hasBodyTextEqualTo("2:42"); } } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPrintAlwaysIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPrintAlwaysIntegrationTests.java index bd7ca7153a53..210bfbf4cc69 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPrintAlwaysIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPrintAlwaysIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,12 +25,9 @@ import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link WebMvcTest @WebMvcTest} default print output. @@ -44,11 +41,11 @@ class WebMvcTestPrintAlwaysIntegrationTests { @Autowired - private MockMvc mvc; + private MockMvcTester mvc; @Test - void shouldPrint(CapturedOutput output) throws Exception { - this.mvc.perform(get("/one")).andExpect(content().string("one")).andExpect(status().isOk()); + void shouldPrint(CapturedOutput output) { + assertThat(this.mvc.get().uri("/one")).hasStatusOk().hasBodyTextEqualTo("one"); assertThat(output).contains("Request URI = /one"); } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPrintDefaultIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPrintDefaultIntegrationTests.java index ca3cb41e185a..1dc53e0e4fa7 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPrintDefaultIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPrintDefaultIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,12 +32,9 @@ import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link WebMvcTest @WebMvcTest} default print output. @@ -75,11 +72,11 @@ private void executeTests(Class testClass) { static class ShouldNotPrint { @Autowired - private MockMvc mvc; + private MockMvcTester mvc; @Test - void test() throws Exception { - this.mvc.perform(get("/one")).andExpect(content().string("one")).andExpect(status().isOk()); + void test() { + assertThat(this.mvc.get().uri("/one")).hasStatusOk().hasBodyTextEqualTo("one"); } } @@ -90,11 +87,11 @@ void test() throws Exception { static class ShouldPrint { @Autowired - private MockMvc mvc; + private MockMvcTester mvc; @Test - void test() throws Exception { - this.mvc.perform(get("/one")).andExpect(content().string("none")).andExpect(status().isOk()); + void test() { + assertThat(this.mvc.get().uri("/one")).hasStatusOk().hasBodyTextEqualTo("none"); } } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPrintDefaultOverrideIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPrintDefaultOverrideIntegrationTests.java index b2c1eafd7a7a..a96957977b38 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPrintDefaultOverrideIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPrintDefaultOverrideIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,12 +25,9 @@ import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link WebMvcTest @WebMvcTest} when a specific controller is defined. @@ -44,11 +41,11 @@ class WebMvcTestPrintDefaultOverrideIntegrationTests { @Autowired - private MockMvc mvc; + private MockMvcTester mvc; @Test - void shouldFindController1(CapturedOutput output) throws Exception { - this.mvc.perform(get("/one")).andExpect(content().string("one")).andExpect(status().isOk()); + void shouldFindController1(CapturedOutput output) { + assertThat(this.mvc.get().uri("/one")).hasStatusOk().hasBodyTextEqualTo("one"); assertThat(output).doesNotContain("Request URI = /one"); } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPrintOverrideIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPrintOverrideIntegrationTests.java index 88a2e94247cd..0979785bc8b6 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPrintOverrideIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestPrintOverrideIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,12 +26,9 @@ import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link WebMvcTest @WebMvcTest} when a specific print option is defined. @@ -45,11 +42,11 @@ class WebMvcTestPrintOverrideIntegrationTests { @Autowired - private MockMvc mvc; + private MockMvcTester mvc; @Test - void shouldNotPrint(CapturedOutput output) throws Exception { - this.mvc.perform(get("/one")).andExpect(content().string("one")).andExpect(status().isOk()); + void shouldNotPrint(CapturedOutput output) { + assertThat(this.mvc.get().uri("/one")).hasStatusOk().hasBodyTextEqualTo("one"); assertThat(output).doesNotContain("Request URI = /one"); } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestServletFilterIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestServletFilterIntegrationTests.java index c4cc7d7825bf..6728ef48917a 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestServletFilterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestServletFilterIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,10 +20,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link WebMvcTest @WebMvcTest} servlet filter registration. @@ -34,11 +33,11 @@ class WebMvcTestServletFilterIntegrationTests { @Autowired - private MockMvc mvc; + private MockMvcTester mvc; @Test - void shouldApplyFilter() throws Exception { - this.mvc.perform(get("/one")).andExpect(header().string("x-test", "abc")); + void shouldApplyFilter() { + assertThat(this.mvc.get().uri("/one")).hasHeader("x-test", "abc"); } } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestServletFilterRegistrationDisabledIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestServletFilterRegistrationDisabledIntegrationTests.java index d0ab9210be39..7a1967e6925e 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestServletFilterRegistrationDisabledIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestServletFilterRegistrationDisabledIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,10 +23,9 @@ import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link WebMvcTest @WebMvcTest} with a disabled filter registration. @@ -37,11 +36,11 @@ class WebMvcTestServletFilterRegistrationDisabledIntegrationTests { @Autowired - private MockMvc mvc; + private MockMvcTester mvc; @Test - void shouldNotApplyFilter() throws Exception { - this.mvc.perform(get("/one")).andExpect(header().string("x-test", (String) null)); + void shouldNotApplyFilter() { + assertThat(this.mvc.get().uri("/one")).doesNotContainHeader("x-test"); } @TestConfiguration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestWebClientIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestWebClientIntegrationTests.java index eec09dffa28b..e4d481d29541 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestWebClientIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestWebClientIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ package org.springframework.boot.test.autoconfigure.web.servlet.mockmvc; -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.html.HtmlPage; +import org.htmlunit.WebClient; +import org.htmlunit.html.HtmlPage; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestWebDriverIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestWebDriverIntegrationTests.java index 519dedc05a3e..2e5a912344d1 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestWebDriverIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestWebDriverIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.openqa.selenium.By; -import org.openqa.selenium.NoSuchWindowException; +import org.openqa.selenium.NoSuchSessionException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; @@ -59,7 +59,7 @@ void shouldBeADifferentWebClient() { this.webDriver.get("/html"); WebElement element = this.webDriver.findElement(By.tagName("body")); assertThat(element.getText()).isEqualTo("Hello"); - assertThatExceptionOfType(NoSuchWindowException.class).isThrownBy(previousWebDriver::getWindowHandle); + assertThatExceptionOfType(NoSuchSessionException.class).isThrownBy(previousWebDriver::getWindowHandle); assertThat(previousWebDriver).isNotNull().isNotSameAs(this.webDriver); } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestWithAutoConfigureMockMvcIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestWithAutoConfigureMockMvcIntegrationTests.java index e2ce3c06322f..067b90e43e26 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestWithAutoConfigureMockMvcIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestWithAutoConfigureMockMvcIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package org.springframework.boot.test.autoconfigure.web.servlet.mockmvc; -import com.gargoylesoftware.htmlunit.WebClient; +import org.htmlunit.WebClient; import org.junit.jupiter.api.Test; import org.openqa.selenium.WebDriver; @@ -25,11 +25,10 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.context.ApplicationContext; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; /** * Tests for {@link WebMvcTest @WebMvcTest} with @@ -46,11 +45,11 @@ class WebMvcTestWithAutoConfigureMockMvcIntegrationTests { private ApplicationContext context; @Autowired - private MockMvc mvc; + private MockMvcTester mvc; @Test - void shouldNotAddFilters() throws Exception { - this.mvc.perform(get("/one")).andExpect(header().doesNotExist("x-test")); + void shouldNotAddFilters() { + assertThat(this.mvc.get().uri("/one")).doesNotContainHeader("x-test"); } @Test diff --git a/spring-boot-project/spring-boot-test/build.gradle b/spring-boot-project/spring-boot-test/build.gradle index 3094feadbde3..1274c3f36b9d 100644 --- a/spring-boot-project/spring-boot-test/build.gradle +++ b/spring-boot-project/spring-boot-test/build.gradle @@ -10,6 +10,7 @@ description = "Spring Boot Test" dependencies { api(project(":spring-boot-project:spring-boot")) + api("org.springframework:spring-test") optional("com.fasterxml.jackson.core:jackson-databind") optional("com.google.code.gson:gson") @@ -22,23 +23,22 @@ dependencies { optional("org.assertj:assertj-core") optional("org.hamcrest:hamcrest-core") optional("org.hamcrest:hamcrest-library") + optional("org.htmlunit:htmlunit") { + exclude(group: "commons-logging", module: "commons-logging") + } optional("org.jetbrains.kotlin:kotlin-stdlib") optional("org.jetbrains.kotlin:kotlin-reflect") optional("org.junit.jupiter:junit-jupiter-api") optional("org.mockito:mockito-core") optional("org.skyscreamer:jsonassert") - optional("org.seleniumhq.selenium:htmlunit-driver") { + optional("org.seleniumhq.selenium:htmlunit3-driver") { exclude(group: "commons-logging", module: "commons-logging") exclude(group: "com.sun.activation", module: "jakarta.activation") } optional("org.seleniumhq.selenium:selenium-api") - optional("org.springframework:spring-test") optional("org.springframework:spring-web") optional("org.springframework:spring-webflux") optional("org.springframework.graphql:spring-graphql-test") - optional("net.sourceforge.htmlunit:htmlunit") { - exclude(group: "commons-logging", module: "commons-logging") - } testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation("io.mockk:mockk") @@ -55,6 +55,7 @@ dependencies { testImplementation("org.spockframework:spock-core") testImplementation("org.springframework:spring-webmvc") testImplementation("org.springframework:spring-core-test") + testImplementation("org.springframework:spring-test") testImplementation("org.testng:testng") testRuntimeOnly("org.junit.vintage:junit-vintage-engine") diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/DefaultTestExecutionListenersPostProcessor.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/DefaultTestExecutionListenersPostProcessor.java deleted file mode 100644 index 695c3f460f6b..000000000000 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/DefaultTestExecutionListenersPostProcessor.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.test.context; - -import java.util.List; - -import org.springframework.test.context.ApplicationContextFailureProcessor; -import org.springframework.test.context.TestExecutionListener; - -/** - * Callback interface trigger from {@link SpringBootTestContextBootstrapper} that can be - * used to post-process the list of default {@link TestExecutionListener - * TestExecutionListeners} to be used by a test. Can be used to add or remove existing - * listeners. - * - * @author Phillip Webb - * @since 1.4.1 - * @deprecated since 3.0.0 removal in 3.2.0 in favor of - * {@link ApplicationContextFailureProcessor} - */ -@FunctionalInterface -@Deprecated(since = "3.0.0", forRemoval = true) -public interface DefaultTestExecutionListenersPostProcessor { - - /** - * Post process the list of default {@link TestExecutionListener listeners} to be - * used. - * @param listeners the source listeners - * @return the actual listeners that should be used - * @since 3.0.0 - */ - List postProcessDefaultTestExecutionListeners(List listeners); - -} diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java index 4ec97096d575..51517cb961d4 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java @@ -17,7 +17,6 @@ package org.springframework.boot.test.context; import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.util.Collections; import java.util.HashSet; @@ -44,10 +43,9 @@ import org.springframework.context.annotation.ImportSelector; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.core.Ordered; -import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.AnnotationFilter; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; import org.springframework.core.annotation.Order; import org.springframework.core.style.ToStringCreator; import org.springframework.core.type.AnnotationMetadata; @@ -61,6 +59,7 @@ * * @author Phillip Webb * @author Andy Wilkinson + * @author Laurent Martelli * @see ImportsContextCustomizerFactory */ class ImportsContextCustomizer implements ContextCustomizer { @@ -220,79 +219,47 @@ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) t */ static class ContextCustomizerKey { - private static final Class[] NO_IMPORTS = {}; - private static final Set ANNOTATION_FILTERS; - static { - Set filters = new HashSet<>(); - filters.add(new JavaLangAnnotationFilter()); - filters.add(new KotlinAnnotationFilter()); - filters.add(new SpockAnnotationFilter()); - filters.add(new JUnitAnnotationFilter()); - ANNOTATION_FILTERS = Collections.unmodifiableSet(filters); + Set annotationFilters = new LinkedHashSet<>(); + annotationFilters.add(AnnotationFilter.PLAIN); + annotationFilters.add("kotlin.Metadata"::equals); + annotationFilters.add(AnnotationFilter.packages("kotlin.annotation")); + annotationFilters.add(AnnotationFilter.packages("org.spockframework", "spock")); + annotationFilters.add(AnnotationFilter.packages("org.junit")); + ANNOTATION_FILTERS = Collections.unmodifiableSet(annotationFilters); } - private final Set key; ContextCustomizerKey(Class testClass) { - Set annotations = new HashSet<>(); - Set> seen = new HashSet<>(); - collectClassAnnotations(testClass, annotations, seen); + MergedAnnotations annotations = MergedAnnotations.search(MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) + .withAnnotationFilter(this::isFilteredAnnotation) + .from(testClass); Set determinedImports = determineImports(annotations, testClass); if (determinedImports == null) { - this.key = Collections.unmodifiableSet(annotations); + this.key = Collections.unmodifiableSet(synthesize(annotations)); } else { - Set key = new HashSet<>(); - key.addAll(determinedImports); + Set key = new HashSet<>(determinedImports); Set componentScanning = annotations.stream() - .filter(ComponentScan.class::isInstance) + .filter((annotation) -> annotation.getType().equals(ComponentScan.class)) + .map(MergedAnnotation::synthesize) .collect(Collectors.toSet()); key.addAll(componentScanning); this.key = Collections.unmodifiableSet(key); } } - private void collectClassAnnotations(Class classType, Set annotations, Set> seen) { - if (seen.add(classType)) { - collectElementAnnotations(classType, annotations, seen); - for (Class interfaceType : classType.getInterfaces()) { - collectClassAnnotations(interfaceType, annotations, seen); - } - if (classType.getSuperclass() != null) { - collectClassAnnotations(classType.getSuperclass(), annotations, seen); - } - } - } - - private void collectElementAnnotations(AnnotatedElement element, Set annotations, - Set> seen) { - for (MergedAnnotation mergedAnnotation : MergedAnnotations.from(element, - SearchStrategy.DIRECT)) { - Annotation annotation = mergedAnnotation.synthesize(); - if (!isIgnoredAnnotation(annotation)) { - annotations.add(annotation); - collectClassAnnotations(annotation.annotationType(), annotations, seen); - } - } - } - - private boolean isIgnoredAnnotation(Annotation annotation) { - for (AnnotationFilter annotationFilter : ANNOTATION_FILTERS) { - if (annotationFilter.isIgnored(annotation)) { - return true; - } - } - return false; + private boolean isFilteredAnnotation(String typeName) { + return ANNOTATION_FILTERS.stream().anyMatch((filter) -> filter.matches(typeName)); } - private Set determineImports(Set annotations, Class testClass) { + private Set determineImports(MergedAnnotations annotations, Class testClass) { Set determinedImports = new LinkedHashSet<>(); - AnnotationMetadata testClassMetadata = AnnotationMetadata.introspect(testClass); - for (Annotation annotation : annotations) { - for (Class source : getImports(annotation)) { - Set determinedSourceImports = determineImports(source, testClassMetadata); + AnnotationMetadata metadata = AnnotationMetadata.introspect(testClass); + for (MergedAnnotation annotation : annotations.stream(Import.class).toList()) { + for (Class source : annotation.getClassArray(MergedAnnotation.VALUE)) { + Set determinedSourceImports = determineImports(source, metadata); if (determinedSourceImports == null) { return null; } @@ -302,13 +269,6 @@ private Set determineImports(Set annotations, Class testC return determinedImports; } - private Class[] getImports(Annotation annotation) { - if (annotation instanceof Import importAnnotation) { - return importAnnotation.value(); - } - return NO_IMPORTS; - } - private Set determineImports(Class source, AnnotationMetadata metadata) { if (DeterminableImports.class.isAssignableFrom(source)) { // We can determine the imports @@ -324,6 +284,10 @@ private Set determineImports(Class source, AnnotationMetadata metadat return Collections.singleton(source.getName()); } + private Set synthesize(MergedAnnotations annotations) { + return annotations.stream().map(MergedAnnotation::synthesize).collect(Collectors.toSet()); + } + @SuppressWarnings("unchecked") private T instantiate(Class source) { try { @@ -354,67 +318,4 @@ public String toString() { } - /** - * Filter used to limit considered annotations. - */ - private interface AnnotationFilter { - - boolean isIgnored(Annotation annotation); - - } - - /** - * {@link AnnotationFilter} for {@literal java.lang} annotations. - */ - private static final class JavaLangAnnotationFilter implements AnnotationFilter { - - @Override - public boolean isIgnored(Annotation annotation) { - return AnnotationUtils.isInJavaLangAnnotationPackage(annotation); - } - - } - - /** - * {@link AnnotationFilter} for Kotlin annotations. - */ - private static final class KotlinAnnotationFilter implements AnnotationFilter { - - @Override - public boolean isIgnored(Annotation annotation) { - return "kotlin.Metadata".equals(annotation.annotationType().getName()) - || isInKotlinAnnotationPackage(annotation); - } - - private boolean isInKotlinAnnotationPackage(Annotation annotation) { - return annotation.annotationType().getName().startsWith("kotlin.annotation."); - } - - } - - /** - * {@link AnnotationFilter} for Spock annotations. - */ - private static final class SpockAnnotationFilter implements AnnotationFilter { - - @Override - public boolean isIgnored(Annotation annotation) { - return annotation.annotationType().getName().startsWith("org.spockframework.") - || annotation.annotationType().getName().startsWith("spock."); - } - - } - - /** - * {@link AnnotationFilter} for JUnit annotations. - */ - private static final class JUnitAnnotationFilter implements AnnotationFilter { - - @Override - public boolean isIgnored(Annotation annotation) { - return annotation.annotationType().getName().startsWith("org.junit."); - } - - } - } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootContextLoader.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootContextLoader.java index 663f56fa818e..c58f1bc4da2e 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootContextLoader.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootContextLoader.java @@ -188,7 +188,7 @@ private void configure(MergedContextConfiguration mergedConfig, SpringApplicatio if (mergedConfig instanceof WebMergedContextConfiguration) { application.setWebApplicationType(WebApplicationType.SERVLET); if (!isEmbeddedWebEnvironment(mergedConfig)) { - new WebConfigurer().configure(mergedConfig, application, initializers); + new WebConfigurer().configure(mergedConfig, initializers); } } else if (mergedConfig instanceof ReactiveWebMergedContextConfiguration) { @@ -197,8 +197,7 @@ else if (mergedConfig instanceof ReactiveWebMergedContextConfiguration) { else { application.setWebApplicationType(WebApplicationType.NONE); } - application.setApplicationContextFactory( - (webApplicationType) -> getApplicationContextFactory(mergedConfig, webApplicationType)); + application.setApplicationContextFactory(getApplicationContextFactory(mergedConfig)); if (mergedConfig.getParent() != null) { application.setBannerMode(Banner.Mode.OFF); } @@ -213,17 +212,26 @@ else if (mergedConfig instanceof ReactiveWebMergedContextConfiguration) { } } - private ConfigurableApplicationContext getApplicationContextFactory(MergedContextConfiguration mergedConfig, - WebApplicationType webApplicationType) { - if (webApplicationType != WebApplicationType.NONE && !isEmbeddedWebEnvironment(mergedConfig)) { - if (webApplicationType == WebApplicationType.REACTIVE) { - return new GenericReactiveWebApplicationContext(); - } - if (webApplicationType == WebApplicationType.SERVLET) { - return new GenericWebApplicationContext(); + /** + * Return the {@link ApplicationContextFactory} that should be used for the test. By + * default this method will return a factory that will create an appropriate + * {@link ApplicationContext} for the {@link WebApplicationType}. + * @param mergedConfig the merged context configuration + * @return the application context factory to use + * @since 3.2.0 + */ + protected ApplicationContextFactory getApplicationContextFactory(MergedContextConfiguration mergedConfig) { + return (webApplicationType) -> { + if (webApplicationType != WebApplicationType.NONE && !isEmbeddedWebEnvironment(mergedConfig)) { + if (webApplicationType == WebApplicationType.REACTIVE) { + return new GenericReactiveWebApplicationContext(); + } + if (webApplicationType == WebApplicationType.SERVLET) { + return new GenericWebApplicationContext(); + } } - } - return ApplicationContextFactory.DEFAULT.create(webApplicationType); + return ApplicationContextFactory.DEFAULT.create(webApplicationType); + }; } private void prepareEnvironment(MergedContextConfiguration mergedConfig, SpringApplication application, @@ -231,8 +239,8 @@ private void prepareEnvironment(MergedContextConfiguration mergedConfig, SpringA setActiveProfiles(environment, mergedConfig.getActiveProfiles(), applicationEnvironment); ResourceLoader resourceLoader = (application.getResourceLoader() != null) ? application.getResourceLoader() : new DefaultResourceLoader(null); - TestPropertySourceUtils.addPropertiesFilesToEnvironment(environment, resourceLoader, - mergedConfig.getPropertySourceLocations()); + TestPropertySourceUtils.addPropertySourcesToEnvironment(environment, resourceLoader, + mergedConfig.getPropertySourceDescriptors()); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(environment, getInlinedProperties(mergedConfig)); } @@ -375,8 +383,7 @@ private enum Mode { */ private static final class WebConfigurer { - void configure(MergedContextConfiguration mergedConfig, SpringApplication application, - List> initializers) { + void configure(MergedContextConfiguration mergedConfig, List> initializers) { WebMergedContextConfiguration webMergedConfig = (WebMergedContextConfiguration) mergedConfig; addMockServletContext(initializers, webMergedConfig); } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTest.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTest.java index 95a053363f41..41cb55b1557f 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTest.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -211,7 +211,7 @@ enum UseMainMethod { * that class does not have a main method, a test-specific * {@link SpringApplication} will be used. */ - WHEN_AVAILABLE; + WHEN_AVAILABLE } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java index 0f812a30ebba..40970888546f 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java @@ -38,7 +38,6 @@ import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; import org.springframework.core.env.Environment; -import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextCustomizer; @@ -48,7 +47,6 @@ import org.springframework.test.context.TestContext; import org.springframework.test.context.TestContextAnnotationUtils; import org.springframework.test.context.TestContextBootstrapper; -import org.springframework.test.context.TestExecutionListener; import org.springframework.test.context.aot.AotTestAttributes; import org.springframework.test.context.support.DefaultTestContextBootstrapper; import org.springframework.test.context.support.TestPropertySourceUtils; @@ -122,18 +120,6 @@ else if (webEnvironment != null && webEnvironment.isEmbedded()) { return context; } - @Override - @SuppressWarnings("removal") - protected List getDefaultTestExecutionListeners() { - List listeners = new ArrayList<>(super.getDefaultTestExecutionListeners()); - List postProcessors = SpringFactoriesLoader - .loadFactories(DefaultTestExecutionListenersPostProcessor.class, getClass().getClassLoader()); - for (DefaultTestExecutionListenersPostProcessor postProcessor : postProcessors) { - listeners = postProcessor.postProcessDefaultTestExecutionListeners(listeners); - } - return listeners; - } - @Override protected ContextLoader resolveContextLoader(Class testClass, List configAttributesList) { @@ -390,7 +376,7 @@ protected final MergedContextConfiguration createModifiedConfig(MergedContextCon contextCustomizers.add(new SpringBootTestAnnotation(mergedConfig.getTestClass())); return new MergedContextConfiguration(mergedConfig.getTestClass(), mergedConfig.getLocations(), classes, mergedConfig.getContextInitializerClasses(), mergedConfig.getActiveProfiles(), - mergedConfig.getPropertySourceLocations(), propertySourceProperties, contextCustomizers, + mergedConfig.getPropertySourceDescriptors(), propertySourceProperties, contextCustomizers, mergedConfig.getContextLoader(), getCacheAwareContextLoaderDelegate(), mergedConfig.getParent()); } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/Definition.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/Definition.java index 15d755f62b7a..1886754f9823 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/Definition.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/Definition.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,8 @@ * @author Phillip Webb * @see DefinitionsParser */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) abstract class Definition { private static final int MULTIPLIER = 31; diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/DefinitionsParser.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/DefinitionsParser.java index 0deea2e35f3f..dccfc135e5c7 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/DefinitionsParser.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/DefinitionsParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,8 @@ * @author Phillip Webb * @author Stephane Nicoll */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class DefinitionsParser { private final Set definitions; diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockBean.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockBean.java index 201a3e2e794e..34e826bfbf5a 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockBean.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -91,7 +91,11 @@ * @author Phillip Webb * @since 1.4.0 * @see MockitoPostProcessor + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of + * {@link org.springframework.test.context.bean.override.mockito.MockitoBean} */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @Target({ ElementType.TYPE, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Documented diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockBeans.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockBeans.java index 034149141ff8..77913cd79d39 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockBeans.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockBeans.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,11 @@ * * @author Phillip Webb * @since 1.4.0 + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of + * {@link org.springframework.test.context.bean.override.mockito.MockitoBean} */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockDefinition.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockDefinition.java index ff6cdff2382b..04a6efa40fe3 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockDefinition.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,6 +38,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class MockDefinition extends Definition { private static final int MULTIPLIER = 31; diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockReset.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockReset.java index 6ac21a59dd22..d4e77598801e 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockReset.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockReset.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,11 @@ * @author Phillip Webb * @since 1.4.0 * @see ResetMocksTestExecutionListener + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of + * {@link org.springframework.test.context.bean.override.mockito.MockReset} */ + +@Deprecated(since = "3.4.0", forRemoval = true) public enum MockReset { /** diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoBeans.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoBeans.java index 2efbd39325c0..d1f201e09282 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoBeans.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoBeans.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizer.java index a739c364428e..349d9371e1f9 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizer.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class MockitoContextCustomizer implements ContextCustomizer { private final Set definitions; diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerFactory.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerFactory.java index 2fef24d8b383..9a179c7c564c 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerFactory.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class MockitoContextCustomizerFactory implements ContextCustomizerFactory { @Override diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessor.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessor.java index dfc97c91eaf1..069fc3b5c851 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessor.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,6 +57,8 @@ import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; import org.springframework.core.ResolvableType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -76,7 +78,11 @@ * @author Stephane Nicoll * @author Andreas Neiser * @since 1.4.0 + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of Spring Framework's + * {@link MockitoBean} and {@link MockitoSpyBean} support */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0") public class MockitoPostProcessor implements InstantiationAwareBeanPostProcessor, BeanClassLoaderAware, BeanFactoryAware, BeanFactoryPostProcessor, Ordered { @@ -252,18 +258,15 @@ private Set getExistingBeans(ConfigurableListableBeanFactory beanFactory Set beans = new LinkedHashSet<>( Arrays.asList(beanFactory.getBeanNamesForType(resolvableType, true, false))); Class type = resolvableType.resolve(Object.class); - String typeName = type.getName(); for (String beanName : beanFactory.getBeanNamesForType(FactoryBean.class, true, false)) { beanName = BeanFactoryUtils.transformedBeanName(beanName); - BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); - Object attribute = beanDefinition.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE); - if (resolvableType.equals(attribute) || type.equals(attribute) || typeName.equals(attribute)) { + Class producedType = beanFactory.getType(beanName, false); + if (type.equals(producedType)) { beans.add(beanName); } } beans.removeIf(this::isScopedTarget); return beans; - } private boolean isScopedTarget(String beanName) { @@ -423,7 +426,7 @@ private static BeanDefinition getOrAddBeanDefinition(BeanDefinitionRegistry regi RootBeanDefinition definition = new RootBeanDefinition(postProcessor); definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); ConstructorArgumentValues constructorArguments = definition.getConstructorArgumentValues(); - constructorArguments.addIndexedArgumentValue(0, new LinkedHashSet()); + constructorArguments.addIndexedArgumentValue(0, new LinkedHashSet<>()); registry.registerBeanDefinition(BEAN_NAME, definition); return definition; } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListener.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListener.java index be4561d45a53..56d9568457b1 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListener.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListener.java @@ -16,27 +16,18 @@ package org.springframework.boot.test.mock.mockito; -import java.lang.annotation.Annotation; import java.lang.reflect.Field; -import java.util.LinkedHashSet; -import java.util.Set; import java.util.function.BiConsumer; -import org.mockito.Captor; -import org.mockito.MockitoAnnotations; - import org.springframework.test.context.TestContext; import org.springframework.test.context.TestExecutionListener; import org.springframework.test.context.support.AbstractTestExecutionListener; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.util.ReflectionUtils; -import org.springframework.util.ReflectionUtils.FieldCallback; /** * {@link TestExecutionListener} to enable {@link MockBean @MockBean} and - * {@link SpyBean @SpyBean} support. Also triggers - * {@link MockitoAnnotations#openMocks(Object)} when any Mockito annotations used, - * primarily to allow {@link Captor @Captor} annotations. + * {@link SpyBean @SpyBean} support. *

* To use the automatic reset support of {@code @MockBean} and {@code @SpyBean}, configure * {@link ResetMocksTestExecutionListener} as well. @@ -46,11 +37,13 @@ * @author Moritz Halbritter * @since 1.4.2 * @see ResetMocksTestExecutionListener + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of + * {@link org.springframework.test.context.bean.override.mockito.MockitoTestExecutionListener} */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) public class MockitoTestExecutionListener extends AbstractTestExecutionListener { - private static final String MOCKS_ATTRIBUTE_NAME = MockitoTestExecutionListener.class.getName() + ".mocks"; - @Override public final int getOrder() { return 1950; @@ -58,8 +51,6 @@ public final int getOrder() { @Override public void prepareTestInstance(TestContext testContext) throws Exception { - closeMocks(testContext); - initMocks(testContext); injectFields(testContext); } @@ -67,41 +58,10 @@ public void prepareTestInstance(TestContext testContext) throws Exception { public void beforeTestMethod(TestContext testContext) throws Exception { if (Boolean.TRUE.equals( testContext.getAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE))) { - closeMocks(testContext); - initMocks(testContext); reinjectFields(testContext); } } - @Override - public void afterTestMethod(TestContext testContext) throws Exception { - closeMocks(testContext); - } - - @Override - public void afterTestClass(TestContext testContext) throws Exception { - closeMocks(testContext); - } - - private void initMocks(TestContext testContext) { - if (hasMockitoAnnotations(testContext)) { - testContext.setAttribute(MOCKS_ATTRIBUTE_NAME, MockitoAnnotations.openMocks(testContext.getTestInstance())); - } - } - - private void closeMocks(TestContext testContext) throws Exception { - Object mocks = testContext.getAttribute(MOCKS_ATTRIBUTE_NAME); - if (mocks instanceof AutoCloseable closeable) { - closeable.close(); - } - } - - private boolean hasMockitoAnnotations(TestContext testContext) { - MockitoAnnotationCollection collector = new MockitoAnnotationCollection(); - ReflectionUtils.doWithFields(testContext.getTestClass(), collector); - return collector.hasAnnotations(); - } - private void injectFields(TestContext testContext) { postProcessFields(testContext, (mockitoField, postProcessor) -> postProcessor.inject(mockitoField.field, mockitoField.target, mockitoField.definition)); @@ -130,28 +90,6 @@ private void postProcessFields(TestContext testContext, BiConsumer annotations = new LinkedHashSet<>(); - - @Override - public void doWith(Field field) throws IllegalArgumentException { - for (Annotation annotation : field.getDeclaredAnnotations()) { - if (annotation.annotationType().getName().startsWith("org.mockito")) { - this.annotations.add(annotation); - } - } - } - - boolean hasAnnotations() { - return !this.annotations.isEmpty(); - } - - } - private static final class MockitoField { private final Field field; diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/QualifierDefinition.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/QualifierDefinition.java index b2bab55feac4..3468471eea1f 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/QualifierDefinition.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/QualifierDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ * @author Stephane Nicoll * @see Definition */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class QualifierDefinition { private final Field field; diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/ResetMocksTestExecutionListener.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/ResetMocksTestExecutionListener.java index b353cd41c6cf..7126b0d4dc46 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/ResetMocksTestExecutionListener.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/ResetMocksTestExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,11 @@ * @author Phillip Webb * @since 1.4.0 * @see MockitoTestExecutionListener + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of + * {@link org.springframework.test.context.bean.override.mockito.MockitoResetTestExecutionListener} */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) public class ResetMocksTestExecutionListener extends AbstractTestExecutionListener { private static final boolean MOCKITO_IS_PRESENT = ClassUtils.isPresent("org.mockito.MockSettings", @@ -119,9 +123,7 @@ private boolean isStandardBeanOrSingletonFactoryBean(ConfigurableListableBeanFac String factoryBeanName = BeanFactory.FACTORY_BEAN_PREFIX + name; if (beanFactory.containsBean(factoryBeanName)) { FactoryBean factoryBean = (FactoryBean) beanFactory.getBean(factoryBeanName); - if (!factoryBean.isSingleton()) { - return false; - } + return factoryBean.isSingleton(); } return true; } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolver.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolver.java index 75e5d86699f7..c78ce88b213e 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolver.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import org.springframework.aop.TargetSource; import org.springframework.aop.framework.Advised; import org.springframework.aop.support.AopUtils; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; import org.springframework.util.Assert; /** @@ -29,7 +31,10 @@ * * @author Andy Wilkinson * @since 2.4.0 + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of Spring Framework's + * {@link MockitoBean} and {@link MockitoSpyBean} */ +@Deprecated(since = "3.4.0", forRemoval = true) public class SpringBootMockResolver implements MockResolver { @Override diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyBean.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyBean.java index bf5146805cc1..2ce011caef79 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyBean.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -89,7 +89,11 @@ * @author Phillip Webb * @since 1.4.0 * @see MockitoPostProcessor + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of + * {@link org.springframework.test.context.bean.override.mockito.MockitoSpyBean} */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @Target({ ElementType.TYPE, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Documented diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyBeans.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyBeans.java index 59678f4783a9..4a0b6d6a1e17 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyBeans.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyBeans.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,11 @@ * * @author Phillip Webb * @since 1.4.0 + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of + * {@link org.springframework.test.context.bean.override.mockito.MockitoSpyBean} */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyDefinition.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyDefinition.java index 947bdf1ada4b..bf192207c6ed 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyDefinition.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,6 +38,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class SpyDefinition extends Definition { private static final int MULTIPLIER = 31; diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/package-info.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/package-info.java index 169dfd18fe0d..83f0038bac43 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/package-info.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,5 +16,9 @@ /** * Mockito integration for Spring Boot tests. + *

+ * Deprecated since 3.4.0 for removal in 3.6.0 in favor of Spring Framework's + * {@link org.springframework.test.context.bean.override.mockito.MockitoBean} and + * {@link org.springframework.test.context.bean.override.mockito.MockitoSpyBean} */ package org.springframework.boot.test.mock.mockito; diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/web/SpringBootMockServletContext.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/web/SpringBootMockServletContext.java index ec810bac3fd5..d1c005c78d46 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/web/SpringBootMockServletContext.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/web/SpringBootMockServletContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/MockServerRestClientCustomizer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/MockServerRestClientCustomizer.java new file mode 100644 index 000000000000..b34d47261652 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/MockServerRestClientCustomizer.java @@ -0,0 +1,139 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.web.client; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +import org.springframework.beans.BeanUtils; +import org.springframework.boot.web.client.RestClientCustomizer; +import org.springframework.http.client.BufferingClientHttpRequestFactory; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.test.web.client.MockRestServiceServer.MockRestServiceServerBuilder; +import org.springframework.test.web.client.RequestExpectationManager; +import org.springframework.test.web.client.SimpleRequestExpectationManager; +import org.springframework.util.Assert; +import org.springframework.web.client.RestClient; + +/** + * {@link RestClientCustomizer} that can be applied to {@link RestClient.Builder} + * instances to add {@link MockRestServiceServer} support. + *

+ * Typically applied to an existing builder before it is used, for example: + *

+ * MockServerRestClientCustomizer customizer = new MockServerRestClientCustomizer();
+ * RestClient.Builder builder = RestClient.builder();
+ * customizer.customize(builder);
+ * MyBean bean = new MyBean(client.build());
+ * customizer.getServer().expect(requestTo("/hello")).andRespond(withSuccess());
+ * bean.makeRestCall();
+ * 
+ *

+ * If the customizer is only used once, the {@link #getServer()} method can be used to + * obtain the mock server. If the customizer has been used more than once the + * {@link #getServer(RestClient.Builder)} or {@link #getServers()} method must be used to + * access the related server. + * + * @author Scott Frederick + * @since 3.2.0 + * @see #getServer() + * @see #getServer(RestClient.Builder) + */ +public class MockServerRestClientCustomizer implements RestClientCustomizer { + + private final Map expectationManagers = new ConcurrentHashMap<>(); + + private final Map servers = new ConcurrentHashMap<>(); + + private final Supplier expectationManagerSupplier; + + private boolean bufferContent = false; + + public MockServerRestClientCustomizer() { + this(SimpleRequestExpectationManager::new); + } + + /** + * Create a new {@link MockServerRestClientCustomizer} instance. + * @param expectationManager the expectation manager class to use + */ + public MockServerRestClientCustomizer(Class expectationManager) { + this(() -> BeanUtils.instantiateClass(expectationManager)); + Assert.notNull(expectationManager, "ExpectationManager must not be null"); + } + + /** + * Create a new {@link MockServerRestClientCustomizer} instance. + * @param expectationManagerSupplier a supplier that provides the + * {@link RequestExpectationManager} to use + * @since 3.0.0 + */ + public MockServerRestClientCustomizer(Supplier expectationManagerSupplier) { + Assert.notNull(expectationManagerSupplier, "ExpectationManagerSupplier must not be null"); + this.expectationManagerSupplier = expectationManagerSupplier; + } + + /** + * Set if the {@link BufferingClientHttpRequestFactory} wrapper should be used to + * buffer the input and output streams, and for example, allow multiple reads of the + * response body. + * @param bufferContent if request and response content should be buffered + * @since 3.1.0 + */ + public void setBufferContent(boolean bufferContent) { + this.bufferContent = bufferContent; + } + + @Override + public void customize(RestClient.Builder restClientBuilder) { + RequestExpectationManager expectationManager = createExpectationManager(); + MockRestServiceServerBuilder serverBuilder = MockRestServiceServer.bindTo(restClientBuilder); + if (this.bufferContent) { + serverBuilder.bufferContent(); + } + MockRestServiceServer server = serverBuilder.build(expectationManager); + this.expectationManagers.put(restClientBuilder, expectationManager); + this.servers.put(restClientBuilder, server); + } + + protected RequestExpectationManager createExpectationManager() { + return this.expectationManagerSupplier.get(); + } + + public MockRestServiceServer getServer() { + Assert.state(!this.servers.isEmpty(), "Unable to return a single MockRestServiceServer since " + + "MockServerRestClientCustomizer has not been bound to a RestClient"); + Assert.state(this.servers.size() == 1, "Unable to return a single MockRestServiceServer since " + + "MockServerRestClientCustomizer has been bound to more than one RestClient"); + return this.servers.values().iterator().next(); + } + + public Map getExpectationManagers() { + return this.expectationManagers; + } + + public MockRestServiceServer getServer(RestClient.Builder restClientBuilder) { + return this.servers.get(restClientBuilder); + } + + public Map getServers() { + return Collections.unmodifiableMap(this.servers); + } + +} diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/MockServerRestTemplateCustomizer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/MockServerRestTemplateCustomizer.java index aabcd34437ea..ce7ea7d1abe4 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/MockServerRestTemplateCustomizer.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/MockServerRestTemplateCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -73,7 +73,7 @@ public MockServerRestTemplateCustomizer() { } /** - * Crate a new {@link MockServerRestTemplateCustomizer} instance. + * Create a new {@link MockServerRestTemplateCustomizer} instance. * @param expectationManager the expectation manager class to use */ public MockServerRestTemplateCustomizer(Class expectationManager) { @@ -82,7 +82,7 @@ public MockServerRestTemplateCustomizer(Class testClass, + List configAttributes) { + if (ClassUtils.isPresent(this.REACTOR_RESOURCE_FACTORY_CLASS, testClass.getClassLoader())) { + return new DisableReactorResourceFactoryGlobalResourcesContextCustomizerCustomizer(); + } + return null; + + } + + static final class DisableReactorResourceFactoryGlobalResourcesContextCustomizerCustomizer + implements ContextCustomizer { + + private DisableReactorResourceFactoryGlobalResourcesContextCustomizerCustomizer() { + } + + @Override + public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { + context.getBeanFactory() + .registerSingleton(DisableReactorResourceFactoryGlobalResourcesBeanPostProcessor.class.getName(), + new DisableReactorResourceFactoryGlobalResourcesBeanPostProcessor()); + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof DisableReactorResourceFactoryGlobalResourcesContextCustomizerCustomizer); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactor/netty/package-info.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactor/netty/package-info.java new file mode 100644 index 000000000000..6580806accb2 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactor/netty/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Spring Boot support for testing Reactor Netty. + */ +package org.springframework.boot.test.web.reactor.netty; diff --git a/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories index 57c2b6bda7ff..24e84e5c8c84 100644 --- a/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories @@ -6,7 +6,8 @@ org.springframework.boot.test.graphql.tester.HttpGraphQlTesterContextCustomizerF org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory,\ org.springframework.boot.test.mock.mockito.MockitoContextCustomizerFactory,\ org.springframework.boot.test.web.client.TestRestTemplateContextCustomizerFactory,\ -org.springframework.boot.test.web.reactive.server.WebTestClientContextCustomizerFactory +org.springframework.boot.test.web.reactive.server.WebTestClientContextCustomizerFactory,\ +org.springframework.boot.test.web.reactor.netty.DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactory # Test Execution Listeners org.springframework.test.context.TestExecutionListener=\ diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderMockMvcTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderMockMvcTests.java index 75f7127c8357..467a740bfe58 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderMockMvcTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderMockMvcTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,8 +27,7 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.WebApplicationContext; @@ -36,9 +35,6 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link WebAppConfiguration @WebAppConfiguration} integration. @@ -57,16 +53,16 @@ class SpringBootContextLoaderMockMvcTests { @Autowired private ServletContext servletContext; - private MockMvc mvc; + private MockMvcTester mvc; @BeforeEach void setUp() { - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + this.mvc = MockMvcTester.from(this.context); } @Test - void testMockHttpEndpoint() throws Exception { - this.mvc.perform(get("/")).andExpect(status().isOk()).andExpect(content().string("Hello World")); + void testMockHttpEndpoint() { + assertThat(this.mvc.get().uri("/")).hasStatusOk().hasBodyTextEqualTo("Hello World"); } @Test diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java index e314e15d4389..f9cc7f6e69ca 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java @@ -26,12 +26,14 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.BeanCreationException; +import org.springframework.boot.ApplicationContextFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest.UseMainMethod; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; @@ -246,6 +248,13 @@ void whenUseMainMethodWithContextHierarchyThrowsException() { .withMessage("UseMainMethod.ALWAYS cannot be used with @ContextHierarchy tests"); } + @Test + void whenSubclassProvidesCustomApplicationContextFactory() { + TestContext testContext = new ExposedTestContextManager(CustomApplicationContextTest.class) + .getExposedTestContext(); + assertThat(testContext.getApplicationContext()).isInstanceOf(CustomAnnotationConfigApplicationContext.class); + } + private String[] getActiveProfiles(Class testClass) { TestContext testContext = new ExposedTestContextManager(testClass).getExposedTestContext(); ApplicationContext applicationContext = testContext.getApplicationContext(); @@ -255,7 +264,7 @@ private String[] getActiveProfiles(Class testClass) { private Map getMergedContextConfigurationProperties(Class testClass) { TestContext context = new ExposedTestContextManager(testClass).getExposedTestContext(); MergedContextConfiguration config = (MergedContextConfiguration) ReflectionTestUtils.getField(context, - "mergedContextConfiguration"); + "mergedConfig"); return TestPropertySourceUtils.convertInlinedPropertiesToMap(config.getPropertySourceProperties()); } @@ -370,6 +379,25 @@ static class UseMainMethodWithContextHierarchy { } + @SpringBootTest + @ContextConfiguration(classes = Config.class, loader = CustomApplicationContextSpringBootContextLoader.class) + static class CustomApplicationContextTest { + + } + + static class CustomApplicationContextSpringBootContextLoader extends SpringBootContextLoader { + + @Override + protected ApplicationContextFactory getApplicationContextFactory(MergedContextConfiguration mergedConfig) { + return (webApplicationType) -> new CustomAnnotationConfigApplicationContext(); + } + + } + + static class CustomAnnotationConfigApplicationContext extends AnnotationConfigApplicationContext { + + } + @Configuration(proxyBeanMethods = false) static class Config { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperIntegrationTests.java index 38edac15537b..69f87d827ff0 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,8 +45,6 @@ class SpringBootTestContextBootstrapperIntegrationTests { @Autowired private SpringBootTestContextBootstrapperExampleConfig config; - boolean defaultTestExecutionListenersPostProcessorCalled = false; - @Test void findConfigAutomatically() { assertThat(this.config).isNotNull(); @@ -62,11 +60,6 @@ void testConfigurationWasApplied() { assertThat(this.context.getBean(ExampleBean.class)).isNotNull(); } - @Test - void defaultTestExecutionListenersPostProcessorShouldBeCalled() { - assertThat(this.defaultTestExecutionListenersPostProcessorCalled).isTrue(); - } - @TestConfiguration(proxyBeanMethods = false) static class TestConfig { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperTests.java index db724a374a1e..c38c81fc8121 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperTests.java @@ -110,7 +110,7 @@ private TestContext buildTestContext(Class testClass) { } private MergedContextConfiguration getMergedContextConfiguration(TestContext context) { - return (MergedContextConfiguration) ReflectionTestUtils.getField(context, "mergedContextConfiguration"); + return (MergedContextConfiguration) ReflectionTestUtils.getField(context, "mergedConfig"); } @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/TestDefaultTestExecutionListenersPostProcessor.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/TestDefaultTestExecutionListenersPostProcessor.java deleted file mode 100644 index 7587c23f93f5..000000000000 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/TestDefaultTestExecutionListenersPostProcessor.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.test.context.bootstrap; - -import java.util.List; - -import org.springframework.boot.test.context.DefaultTestExecutionListenersPostProcessor; -import org.springframework.test.context.TestContext; -import org.springframework.test.context.TestExecutionListener; -import org.springframework.test.context.support.AbstractTestExecutionListener; - -/** - * Test {@link DefaultTestExecutionListenersPostProcessor}. - * - * @author Phillip Webb - */ -@SuppressWarnings("removal") -public class TestDefaultTestExecutionListenersPostProcessor implements DefaultTestExecutionListenersPostProcessor { - - @Override - public List postProcessDefaultTestExecutionListeners(List listeners) { - listeners.add(new ExampleTestExecutionListener()); - return listeners; - } - - static class ExampleTestExecutionListener extends AbstractTestExecutionListener { - - @Override - public void prepareTestInstance(TestContext testContext) throws Exception { - Object testInstance = testContext.getTestInstance(); - if (testInstance instanceof SpringBootTestContextBootstrapperIntegrationTests test) { - test.defaultTestExecutionListenersPostProcessorCalled = true; - } - } - - } - -} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/filter/ExcludeFilterApplicationContextInitializerTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/filter/ExcludeFilterApplicationContextInitializerTests.java index d221eb8c5cb3..2efd6044980c 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/filter/ExcludeFilterApplicationContextInitializerTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/filter/ExcludeFilterApplicationContextInitializerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ class ExcludeFilterApplicationContextInitializerTests { void testConfigurationIsExcluded() { SpringApplication application = new SpringApplication(TestApplication.class); application.setWebApplicationType(WebApplicationType.NONE); - AssertableApplicationContext applicationContext = AssertableApplicationContext.get(() -> application.run()); + AssertableApplicationContext applicationContext = AssertableApplicationContext.get(application::run); assertThat(applicationContext).hasSingleBean(TestApplication.class); assertThat(applicationContext).doesNotHaveBean(ExcludedTestConfiguration.class); } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/nestedtests/InheritedNestedTestConfigurationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/nestedtests/InheritedNestedTestConfigurationTests.java index c898c6cbfd71..1cc5c4449227 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/nestedtests/InheritedNestedTestConfigurationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/nestedtests/InheritedNestedTestConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,9 +24,9 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.nestedtests.InheritedNestedTestConfigurationTests.ActionPerformer; import org.springframework.boot.test.context.nestedtests.InheritedNestedTestConfigurationTests.AppConfiguration; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import org.springframework.stereotype.Component; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.times; @@ -41,7 +41,7 @@ @Import(ActionPerformer.class) class InheritedNestedTestConfigurationTests { - @MockBean + @MockitoBean Action action; @Autowired diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/AbstractJsonMarshalTesterTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/AbstractJsonMarshalTesterTests.java index 406ff1cc1c98..f680b5f78a33 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/AbstractJsonMarshalTesterTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/AbstractJsonMarshalTesterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -152,7 +153,7 @@ void readReaderShouldReturnObject() throws Exception { void parseListShouldReturnContent() throws Exception { ResolvableType type = ResolvableTypes.get("listOfExampleObject"); AbstractJsonMarshalTester tester = createTester(type); - assertThat(tester.parse(ARRAY_JSON)).asList().containsOnly(OBJECT); + assertThat(tester.parse(ARRAY_JSON)).asInstanceOf(InstanceOfAssertFactories.LIST).containsOnly(OBJECT); } @Test diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/GsonTesterIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/GsonTesterIntegrationTests.java index ed1aca17d886..b2c02051d59b 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/GsonTesterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/GsonTesterIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import java.util.Map; import com.google.gson.Gson; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -61,7 +62,7 @@ void typicalTest() throws Exception { @Test void typicalListTest() throws Exception { String example = "[" + JSON + "]"; - assertThat(this.listJson.parse(example)).asList().hasSize(1); + assertThat(this.listJson.parse(example)).asInstanceOf(InstanceOfAssertFactories.LIST).hasSize(1); assertThat(this.listJson.parse(example).getObject().get(0).getName()).isEqualTo("Spring"); } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterIntegrationTests.java index 5a97f9f7c056..7d33b235b7ea 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.springframework.core.io.ByteArrayResource; @@ -63,7 +64,7 @@ void typicalTest() throws Exception { void typicalListTest() throws Exception { JacksonTester.initFields(this, new ObjectMapper()); String example = "[" + JSON + "]"; - assertThat(this.listJson.parse(example)).asList().hasSize(1); + assertThat(this.listJson.parse(example)).asInstanceOf(InstanceOfAssertFactories.LIST).hasSize(1); assertThat(this.listJson.parse(example).getObject().get(0).getName()).isEqualTo("Spring"); } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/AbstractMockBeanOnGenericExtensionTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/AbstractMockBeanOnGenericExtensionTests.java index 8481e999bd58..d8349fe4238d 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/AbstractMockBeanOnGenericExtensionTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/AbstractMockBeanOnGenericExtensionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ * * @author Madhura Bhave */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class AbstractMockBeanOnGenericExtensionTests extends AbstractMockBeanOnGenericTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/AbstractMockBeanOnGenericTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/AbstractMockBeanOnGenericTests.java index ab7d34ba659c..9277b4295010 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/AbstractMockBeanOnGenericTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/AbstractMockBeanOnGenericTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,8 @@ * @param type of something * @author Madhura Bhave */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @SpringBootTest(classes = AbstractMockBeanOnGenericTests.TestConfiguration.class) abstract class AbstractMockBeanOnGenericTests, U extends AbstractMockBeanOnGenericTests.Something> { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/DefinitionsParserTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/DefinitionsParserTests.java index a2874788f76b..cf4001e96c94 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/DefinitionsParserTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/DefinitionsParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class DefinitionsParserTests { private final DefinitionsParser parser = new DefinitionsParser(); @@ -190,22 +192,26 @@ private List getDefinitions() { return new ArrayList<>(this.parser.getDefinitions()); } + @SuppressWarnings("removal") @MockBean(ExampleService.class) static class SingleMockBean { } + @SuppressWarnings("removal") @MockBeans({ @MockBean(ExampleService.class), @MockBean(ExampleServiceCaller.class) }) static class RepeatMockBean { } + @SuppressWarnings("removal") @MockBean(name = "Name", classes = ExampleService.class, extraInterfaces = ExampleExtraInterface.class, answer = Answers.RETURNS_SMART_NULLS, serializable = true, reset = MockReset.NONE) static class MockBeanAttributes { } + @SuppressWarnings("removal") @MockBean(ExampleService.class) static class MockBeanOnClassAndField { @@ -215,11 +221,13 @@ static class MockBeanOnClassAndField { } + @SuppressWarnings("removal") @MockBean({ ExampleService.class, ExampleServiceCaller.class }) static class MockBeanMultipleClasses { } + @SuppressWarnings("removal") @MockBean(name = "name", classes = { ExampleService.class, ExampleServiceCaller.class }) static class MockBeanMultipleClassesWithName { @@ -232,26 +240,31 @@ static class MockBeanInferClassToMock { } + @SuppressWarnings("removal") @MockBean static class MockBeanMissingClassToMock { } + @SuppressWarnings("removal") @SpyBean(RealExampleService.class) static class SingleSpyBean { } + @SuppressWarnings("removal") @SpyBeans({ @SpyBean(RealExampleService.class), @SpyBean(ExampleServiceCaller.class) }) static class RepeatSpyBean { } + @SuppressWarnings("removal") @SpyBean(name = "Name", classes = RealExampleService.class, reset = MockReset.NONE) static class SpyBeanAttributes { } + @SuppressWarnings("removal") @SpyBean(RealExampleService.class) static class SpyBeanOnClassAndField { @@ -261,11 +274,13 @@ static class SpyBeanOnClassAndField { } + @SuppressWarnings("removal") @SpyBean({ RealExampleService.class, ExampleServiceCaller.class }) static class SpyBeanMultipleClasses { } + @SuppressWarnings("removal") @SpyBean(name = "name", classes = { RealExampleService.class, ExampleServiceCaller.class }) static class SpyBeanMultipleClassesWithName { @@ -278,6 +293,7 @@ static class SpyBeanInferClassToMock { } + @SuppressWarnings("removal") @SpyBean static class SpyBeanMissingClassToMock { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanContextCachingTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanContextCachingTests.java index d209b93ff50e..78ed37a9b8ca 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanContextCachingTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanContextCachingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,6 +43,8 @@ * * @author Andy Wilkinson */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class MockBeanContextCachingTests { private final DefaultContextCache contextCache = new DefaultContextCache(2); diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanForBeanFactoryIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanForBeanFactoryIntegrationTests.java index 6e9ce012872d..1e00655c3273 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanForBeanFactoryIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanForBeanFactoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class MockBeanForBeanFactoryIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnConfigurationClassForExistingBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnConfigurationClassForExistingBeanIntegrationTests.java index a4f0c22649c9..eb14904ff193 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnConfigurationClassForExistingBeanIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnConfigurationClassForExistingBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class MockBeanOnConfigurationClassForExistingBeanIntegrationTests { @@ -48,6 +50,7 @@ void testMocking() { assertThat(this.caller.sayGreeting()).isEqualTo("I say Boot"); } + @SuppressWarnings("removal") @Configuration(proxyBeanMethods = false) @MockBean(ExampleService.class) @Import({ ExampleServiceCaller.class, FailingExampleService.class }) diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnConfigurationClassForNewBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnConfigurationClassForNewBeanIntegrationTests.java index 4a30ac7aabc6..9590989a761a 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnConfigurationClassForNewBeanIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnConfigurationClassForNewBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,9 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") @ExtendWith(SpringExtension.class) +@Deprecated(since = "3.4.0", forRemoval = true) class MockBeanOnConfigurationClassForNewBeanIntegrationTests { @Autowired @@ -47,6 +49,7 @@ void testMocking() { assertThat(this.caller.sayGreeting()).isEqualTo("I say Boot"); } + @SuppressWarnings("removal") @Configuration(proxyBeanMethods = false) @MockBean(ExampleService.class) @Import(ExampleServiceCaller.class) diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnConfigurationFieldForExistingBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnConfigurationFieldForExistingBeanIntegrationTests.java index 3d05a2d86563..170a9dc86648 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnConfigurationFieldForExistingBeanIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnConfigurationFieldForExistingBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,9 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") @ExtendWith(SpringExtension.class) +@Deprecated(since = "3.4.0", forRemoval = true) class MockBeanOnConfigurationFieldForExistingBeanIntegrationTests { @Autowired diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnConfigurationFieldForNewBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnConfigurationFieldForNewBeanIntegrationTests.java index 2ce964fa9230..922b57f4aeb4 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnConfigurationFieldForNewBeanIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnConfigurationFieldForNewBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,9 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") @ExtendWith(SpringExtension.class) +@Deprecated(since = "3.4.0", forRemoval = true) class MockBeanOnConfigurationFieldForNewBeanIntegrationTests { @Autowired diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnContextHierarchyIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnContextHierarchyIntegrationTests.java index da0d3b475471..cb7a65d0aced 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnContextHierarchyIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnContextHierarchyIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) @ContextHierarchy({ @ContextConfiguration(classes = ParentConfig.class), @ContextConfiguration(classes = ChildConfig.class) }) diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnScopedProxyTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnScopedProxyTests.java index 15994a3fc30a..e7103451b67b 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnScopedProxyTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnScopedProxyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,8 @@ * @author Phillip Webb * @see gh-5724 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class MockBeanOnScopedProxyTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestClassForExistingBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestClassForExistingBeanIntegrationTests.java index 792ef94185da..1127cbbb7490 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestClassForExistingBeanIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestClassForExistingBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) @MockBean(ExampleService.class) class MockBeanOnTestClassForExistingBeanIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestClassForNewBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestClassForNewBeanIntegrationTests.java index b0fd27d908eb..bd4e2899dd3a 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestClassForNewBeanIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestClassForNewBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) @MockBean(ExampleService.class) class MockBeanOnTestClassForNewBeanIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanCacheIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanCacheIntegrationTests.java index f603a86aa71b..3caa26a85a57 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanCacheIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanCacheIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,8 @@ * @author Phillip Webb * @see MockBeanOnTestFieldForExistingBeanIntegrationTests */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = MockBeanOnTestFieldForExistingBeanConfig.class) class MockBeanOnTestFieldForExistingBeanCacheIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanConfig.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanConfig.java index 6b33f229d8eb..4232599fc0f3 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanConfig.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @Configuration(proxyBeanMethods = false) @Import({ ExampleServiceCaller.class, FailingExampleService.class }) public class MockBeanOnTestFieldForExistingBeanConfig { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanIntegrationTests.java index 2168ac1f3018..c30b5e7b400b 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ * @author Phillip Webb * @see MockBeanOnTestFieldForExistingBeanCacheIntegrationTests */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = MockBeanOnTestFieldForExistingBeanConfig.class) class MockBeanOnTestFieldForExistingBeanIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanWithQualifierIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanWithQualifierIntegrationTests.java index 30a54cab0ec6..9a4e5702dc6b 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanWithQualifierIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForExistingBeanWithQualifierIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,6 +40,8 @@ * @author Stephane Nicoll * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class MockBeanOnTestFieldForExistingBeanWithQualifierIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForNewBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForNewBeanIntegrationTests.java index 9d0d08e3cca4..e704b6da0992 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForNewBeanIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanOnTestFieldForNewBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class MockBeanOnTestFieldForNewBeanIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithAopProxyTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithAopProxyTests.java index 853641b22325..21516a6a9e8d 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithAopProxyTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithAopProxyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,6 +46,8 @@ * @author Phillip Webb * @see 5837 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class MockBeanWithAopProxyTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithAsyncInterfaceMethodIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithAsyncInterfaceMethodIntegrationTests.java index 66cba73ee829..d5c0b5e6f9c2 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithAsyncInterfaceMethodIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithAsyncInterfaceMethodIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,8 @@ * * @author Andy Wilkinson */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class MockBeanWithAsyncInterfaceMethodIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithDirtiesContextClassModeBeforeMethodIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithDirtiesContextClassModeBeforeMethodIntegrationTests.java index 1e0ded9aeb85..212d05826119 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithDirtiesContextClassModeBeforeMethodIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithDirtiesContextClassModeBeforeMethodIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,8 @@ * * @author Andy Wilkinson */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) @DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD) class MockBeanWithDirtiesContextClassModeBeforeMethodIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithGenericsOnTestFieldForNewBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithGenericsOnTestFieldForNewBeanIntegrationTests.java index 877aa4e4076e..3d20f3e42f82 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithGenericsOnTestFieldForNewBeanIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithGenericsOnTestFieldForNewBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class MockBeanWithGenericsOnTestFieldForNewBeanIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithInjectedFieldIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithInjectedFieldIntegrationTests.java index 61864f13b1a2..65dd47799d05 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithInjectedFieldIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithInjectedFieldIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,8 @@ * * @author Andy Wilkinson */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class MockBeanWithInjectedFieldIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithSpringMethodRuleRepeatJUnit4IntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithSpringMethodRuleRepeatJUnit4IntegrationTests.java index 0c0e04cf7b3f..5f736ccff970 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithSpringMethodRuleRepeatJUnit4IntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockBeanWithSpringMethodRuleRepeatJUnit4IntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,8 @@ * @author Andy Wilkinson * @see gh-27693 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) public class MockBeanWithSpringMethodRuleRepeatJUnit4IntegrationTests { @Rule diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockDefinitionTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockDefinitionTests.java index 51afa721e6a9..2273cbd77bea 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockDefinitionTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockDefinitionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class MockDefinitionTests { private static final ResolvableType EXAMPLE_SERVICE_TYPE = ResolvableType.forClass(ExampleService.class); diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockResetTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockResetTests.java index 1ac65776a392..50b27ea053f2 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockResetTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockResetTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class MockResetTests { @Test diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerFactoryTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerFactoryTests.java index e70a5e2f1ec6..8e4a0e283c5e 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerFactoryTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class MockitoContextCustomizerFactoryTests { private final MockitoContextCustomizerFactory factory = new MockitoContextCustomizerFactory(); @@ -60,16 +62,19 @@ static class NoMockBeanAnnotation { } + @SuppressWarnings("removal") @MockBean({ Service1.class, Service2.class }) static class WithMockBeanAnnotation { } + @SuppressWarnings("removal") @MockBean({ Service2.class, Service1.class }) static class WithSameMockBeanAnnotation { } + @SuppressWarnings("removal") @MockBean({ Service1.class }) static class WithDifferentMockBeanAnnotation { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerTests.java index 417a03f20db2..d85833213883 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class MockitoContextCustomizerTests { private static final Set NO_DEFINITIONS = Collections.emptySet(); diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessorTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessorTests.java index 99969dadd9c9..ea2c90efe6f8 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessorTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,6 +51,8 @@ * @author Andreas Neiser * @author Madhura Bhave */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class MockitoPostProcessorTests { @Test @@ -73,18 +75,6 @@ void cannotMockMultipleQualifiedBeans() { + " expected a single matching bean to replace but found [example1, example3]"); } - @Test - void canMockBeanProducedByFactoryBeanWithStringObjectTypeAttribute() { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - MockitoPostProcessor.register(context); - RootBeanDefinition factoryBeanDefinition = new RootBeanDefinition(TestFactoryBean.class); - factoryBeanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, SomeInterface.class.getName()); - context.registerBeanDefinition("beanToBeMocked", factoryBeanDefinition); - context.register(MockedFactoryBean.class); - context.refresh(); - assertThat(Mockito.mockingDetails(context.getBean("beanToBeMocked")).isMock()).isTrue(); - } - @Test void canMockBeanProducedByFactoryBeanWithClassObjectTypeAttribute() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListenerIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListenerIntegrationTests.java index 1357629a616d..deb85d05e69b 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListenerIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListenerIntegrationTests.java @@ -50,6 +50,8 @@ * * @author Moritz Halbritter */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class MockitoTestExecutionListenerIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListenerTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListenerTests.java index 3ce7d272a2eb..2a00553e3558 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListenerTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(MockitoExtension.class) class MockitoTestExecutionListenerTests { @@ -53,14 +55,6 @@ class MockitoTestExecutionListenerTests { @Mock private MockitoPostProcessor postProcessor; - @Test - void prepareTestInstanceShouldInitMockitoAnnotations() throws Exception { - WithMockitoAnnotations instance = new WithMockitoAnnotations(); - this.listener.prepareTestInstance(mockTestContext(instance)); - assertThat(instance.mock).isNotNull(); - assertThat(instance.captor).isNotNull(); - } - @Test void prepareTestInstanceShouldInjectMockBean() throws Exception { given(this.applicationContext.getBean(MockitoPostProcessor.class)).willReturn(this.postProcessor); diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/QualifierDefinitionTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/QualifierDefinitionTests.java index a30921c2f403..5992f47278e5 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/QualifierDefinitionTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/QualifierDefinitionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(MockitoExtension.class) class QualifierDefinitionTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/ResetMocksTestExecutionListenerTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/ResetMocksTestExecutionListenerTests.java index 5735f4a3ffb5..ea0cdfa6c561 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/ResetMocksTestExecutionListenerTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/ResetMocksTestExecutionListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,6 +40,8 @@ * @author Phillip Webb * @author Andy Wilkinson */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) @TestMethodOrder(MethodOrderer.MethodName.class) class ResetMocksTestExecutionListenerTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolverIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolverIntegrationTests.java index 0a1ce91cbc86..44eabef46fb5 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolverIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolverIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,8 @@ * * @author Andy Wilkinson */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class SpringBootMockResolverIntegrationTests { @Test diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolverTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolverTests.java index fd9b30ebcd1c..656c522cf46a 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolverTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolverTests.java @@ -30,6 +30,8 @@ * * @author Moritz Halbritter */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class SpringBootMockResolverTests { @Test diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnConfigurationClassForExistingBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnConfigurationClassForExistingBeanIntegrationTests.java index e39877a32222..c9ec81c330cc 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnConfigurationClassForExistingBeanIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnConfigurationClassForExistingBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class SpyBeanOnConfigurationClassForExistingBeanIntegrationTests { @@ -47,6 +49,7 @@ void testSpying() { then(this.caller.getService()).should().greeting(); } + @SuppressWarnings("removal") @Configuration(proxyBeanMethods = false) @SpyBean(SimpleExampleService.class) @Import({ ExampleServiceCaller.class, SimpleExampleService.class }) diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnConfigurationClassForNewBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnConfigurationClassForNewBeanIntegrationTests.java index 3bccd8ead14e..fd87c96de259 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnConfigurationClassForNewBeanIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnConfigurationClassForNewBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class SpyBeanOnConfigurationClassForNewBeanIntegrationTests { @@ -47,6 +49,7 @@ void testSpying() { then(this.caller.getService()).should().greeting(); } + @SuppressWarnings("removal") @Configuration(proxyBeanMethods = false) @SpyBean(SimpleExampleService.class) @Import(ExampleServiceCaller.class) diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnConfigurationFieldForExistingBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnConfigurationFieldForExistingBeanIntegrationTests.java index 3b8a2163b15d..fbbc2836abba 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnConfigurationFieldForExistingBeanIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnConfigurationFieldForExistingBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class SpyBeanOnConfigurationFieldForExistingBeanIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnConfigurationFieldForNewBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnConfigurationFieldForNewBeanIntegrationTests.java index 447022fc65ca..34b62aec7557 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnConfigurationFieldForNewBeanIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnConfigurationFieldForNewBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class SpyBeanOnConfigurationFieldForNewBeanIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnContextHierarchyIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnContextHierarchyIntegrationTests.java index 74384c49dbbb..ed45a279fa9a 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnContextHierarchyIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnContextHierarchyIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,6 +40,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) @ContextHierarchy({ @ContextConfiguration(classes = ParentConfig.class), @ContextConfiguration(classes = ChildConfig.class) }) diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestClassForExistingBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestClassForExistingBeanIntegrationTests.java index d35570810bb2..b5788c9fdadd 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestClassForExistingBeanIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestClassForExistingBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) @SpyBean(SimpleExampleService.class) class SpyBeanOnTestClassForExistingBeanIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestClassForNewBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestClassForNewBeanIntegrationTests.java index ddc2fed14857..932e7aca467d 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestClassForNewBeanIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestClassForNewBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) @SpyBean(SimpleExampleService.class) class SpyBeanOnTestClassForNewBeanIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingBeanCacheIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingBeanCacheIntegrationTests.java index 45cd5879baf4..90f1cee1b00b 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingBeanCacheIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingBeanCacheIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,8 @@ * @author Phillip Webb * @see SpyBeanOnTestFieldForExistingBeanIntegrationTests */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = SpyBeanOnTestFieldForExistingBeanConfig.class) class SpyBeanOnTestFieldForExistingBeanCacheIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingBeanConfig.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingBeanConfig.java index 0f7cfa66a33c..e643e5b0ad72 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingBeanConfig.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingBeanConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @Configuration(proxyBeanMethods = false) @Import({ ExampleServiceCaller.class, SimpleExampleService.class }) public class SpyBeanOnTestFieldForExistingBeanConfig { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingBeanIntegrationTests.java index 0043abd19779..24e7203347f5 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingBeanIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ * @author Phillip Webb * @see SpyBeanOnTestFieldForExistingBeanCacheIntegrationTests */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = SpyBeanOnTestFieldForExistingBeanConfig.class) class SpyBeanOnTestFieldForExistingBeanIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingBeanWithQualifierIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingBeanWithQualifierIntegrationTests.java index 15ab29b8d2b0..7085097889c4 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingBeanWithQualifierIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingBeanWithQualifierIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,8 @@ * * @author Andreas Neiser */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class SpyBeanOnTestFieldForExistingBeanWithQualifierIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingCircularBeansIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingCircularBeansIntegrationTests.java index f751e19e3f77..89a0a3587386 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingCircularBeansIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingCircularBeansIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,8 @@ * * @author Andy Wilkinson */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = SpyBeanOnTestFieldForExistingCircularBeansConfig.class) class SpyBeanOnTestFieldForExistingCircularBeansIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingGenericBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingGenericBeanIntegrationTests.java index d3034b61c827..d6beab94be90 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingGenericBeanIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingGenericBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,8 @@ * @author Phillip Webb * @see SpyBeanOnTestFieldForExistingBeanCacheIntegrationTests */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class SpyBeanOnTestFieldForExistingGenericBeanIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingGenericBeanProducedByFactoryBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingGenericBeanProducedByFactoryBeanIntegrationTests.java new file mode 100644 index 000000000000..0a2bfbb1244b --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForExistingGenericBeanProducedByFactoryBeanIntegrationTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.mock.mockito; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.test.mock.mockito.example.ExampleGenericService; +import org.springframework.boot.test.mock.mockito.example.SimpleExampleStringGenericService; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.ResolvableType; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test {@link SpyBean @SpyBean} on a test class field can be used to replace an existing + * bean with generics that's produced by a factory bean. + * + * @author Andy Wilkinson + */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) +@ExtendWith(SpringExtension.class) +class SpyBeanOnTestFieldForExistingGenericBeanProducedByFactoryBeanIntegrationTests { + + // gh-40234 + + @SpyBean(name = "exampleService") + private ExampleGenericService exampleService; + + @Test + void testSpying() { + assertThat(Mockito.mockingDetails(this.exampleService).isSpy()).isTrue(); + assertThat(Mockito.mockingDetails(this.exampleService).getMockCreationSettings().getSpiedInstance()) + .isInstanceOf(SimpleExampleStringGenericService.class); + } + + @Configuration(proxyBeanMethods = false) + @Import(FactoryBeanRegistrar.class) + static class SpyBeanOnTestFieldForExistingBeanConfig { + + } + + static class FactoryBeanRegistrar implements ImportBeanDefinitionRegistrar { + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry) { + RootBeanDefinition definition = new RootBeanDefinition(ExampleGenericServiceFactoryBean.class); + definition.setTargetType(ResolvableType.forClassWithGenerics(ExampleGenericServiceFactoryBean.class, null, + ExampleGenericService.class)); + registry.registerBeanDefinition("exampleService", definition); + } + + } + + static class ExampleGenericServiceFactoryBean> implements FactoryBean { + + @SuppressWarnings("unchecked") + @Override + public U getObject() throws Exception { + return (U) new SimpleExampleStringGenericService(); + } + + @Override + @SuppressWarnings("rawtypes") + public Class getObjectType() { + return ExampleGenericService.class; + } + + } + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForMultipleExistingBeansWithOnePrimaryIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForMultipleExistingBeansWithOnePrimaryIntegrationTests.java index 9df95017fc98..69576871c37d 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForMultipleExistingBeansWithOnePrimaryIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForMultipleExistingBeansWithOnePrimaryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,6 +38,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class SpyBeanOnTestFieldForMultipleExistingBeansWithOnePrimaryIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForNewBeanIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForNewBeanIntegrationTests.java index 2cf35176d8a4..aded79bb3928 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForNewBeanIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanOnTestFieldForNewBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class SpyBeanOnTestFieldForNewBeanIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithAopProxyAndNotProxyTargetAwareTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithAopProxyAndNotProxyTargetAwareTests.java index 38b5439a3925..59cb4853f8a6 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithAopProxyAndNotProxyTargetAwareTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithAopProxyAndNotProxyTargetAwareTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,6 +44,8 @@ * @author Phillip Webb * @see 5837 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class SpyBeanWithAopProxyAndNotProxyTargetAwareTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithAopProxyTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithAopProxyTests.java index 2c0fd0e2a88b..61a21f11eb99 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithAopProxyTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithAopProxyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,6 +44,8 @@ * @author Phillip Webb * @see 5837 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class SpyBeanWithAopProxyTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithDirtiesContextClassModeBeforeMethodIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithDirtiesContextClassModeBeforeMethodIntegrationTests.java index 57a9ac012832..25338d2c4cf2 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithDirtiesContextClassModeBeforeMethodIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithDirtiesContextClassModeBeforeMethodIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,8 @@ * * @author Andy Wilkinson */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) @DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD) class SpyBeanWithDirtiesContextClassModeBeforeMethodIntegrationTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithJdkProxyTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithJdkProxyTests.java index 506afd6c4152..0a8965284c51 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithJdkProxyTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithJdkProxyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ * * @author Andy Wilkinson */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class SpyBeanWithJdkProxyTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithNameOnTestFieldForMultipleExistingBeansTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithNameOnTestFieldForMultipleExistingBeansTests.java index 764a11e57062..11d4c2fc0946 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithNameOnTestFieldForMultipleExistingBeansTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithNameOnTestFieldForMultipleExistingBeansTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ * @author Phillip Webb * @author Andy Wilkinson */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class SpyBeanWithNameOnTestFieldForMultipleExistingBeansTests { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyDefinitionTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyDefinitionTests.java index 335d3f104b08..ed6d37fe01e6 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyDefinitionTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyDefinitionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class SpyDefinitionTests { private static final ResolvableType REAL_SERVICE_TYPE = ResolvableType.forClass(RealExampleService.class); diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/CustomQualifier.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/CustomQualifier.java index 702bc527fc38..559edfb33984 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/CustomQualifier.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/CustomQualifier.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ * * @author Stephane Nicoll */ +@Deprecated(since = "3.4.0", forRemoval = true) @Qualifier @Retention(RetentionPolicy.RUNTIME) public @interface CustomQualifier { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/CustomQualifierExampleService.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/CustomQualifierExampleService.java index 389f76fd505a..41bc34e99f27 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/CustomQualifierExampleService.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/CustomQualifierExampleService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ * * @author Andy Wilkinson */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @CustomQualifier public class CustomQualifierExampleService implements ExampleService { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleExtraInterface.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleExtraInterface.java index 86ec7de8529c..d2b79b801c07 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleExtraInterface.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleExtraInterface.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ * * @author Phillip Webb */ +@Deprecated(since = "3.4.0", forRemoval = true) public interface ExampleExtraInterface { String doExtra(); diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleGenericService.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleGenericService.java index 2de32fd2debc..7756de050850 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleGenericService.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleGenericService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ * @param the generic type * @author Phillip Webb */ +@Deprecated(since = "3.4.0", forRemoval = true) public interface ExampleGenericService { T greeting(); diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleGenericServiceCaller.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleGenericServiceCaller.java index 6ef1f381f5e1..0b77f522a075 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleGenericServiceCaller.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleGenericServiceCaller.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) public class ExampleGenericServiceCaller { private final ExampleGenericService integerService; diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleGenericStringServiceCaller.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleGenericStringServiceCaller.java index e4ffa8ed061a..4c24f21d5bad 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleGenericStringServiceCaller.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleGenericStringServiceCaller.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) public class ExampleGenericStringServiceCaller { private final ExampleGenericService stringService; diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleService.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleService.java index 286eaa50dfac..06c653ce391b 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleService.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ * * @author Phillip Webb */ +@Deprecated(since = "3.4.0", forRemoval = true) public interface ExampleService { String greeting(); diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleServiceCaller.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleServiceCaller.java index e5099bae2607..ad3816f240b8 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleServiceCaller.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/ExampleServiceCaller.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) public class ExampleServiceCaller { private final ExampleService service; diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/FailingExampleService.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/FailingExampleService.java index 566870d16ed7..98307be8c203 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/FailingExampleService.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/FailingExampleService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @Service public class FailingExampleService implements ExampleService { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/RealExampleService.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/RealExampleService.java index c96c13fcf6ef..dd5e42da9634 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/RealExampleService.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/RealExampleService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) public class RealExampleService implements ExampleService { private final String greeting; diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/SimpleExampleIntegerGenericService.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/SimpleExampleIntegerGenericService.java index 29b9e2365b07..b7a8c480b76b 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/SimpleExampleIntegerGenericService.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/SimpleExampleIntegerGenericService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) public class SimpleExampleIntegerGenericService implements ExampleGenericService { @Override diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/SimpleExampleService.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/SimpleExampleService.java index 53ebb703ce01..20426aceb83b 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/SimpleExampleService.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/SimpleExampleService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) public class SimpleExampleService extends RealExampleService { public SimpleExampleService() { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/SimpleExampleStringGenericService.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/SimpleExampleStringGenericService.java index 5ef0f24b9add..417f7f020b31 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/SimpleExampleStringGenericService.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/example/SimpleExampleStringGenericService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) public class SimpleExampleStringGenericService implements ExampleGenericService { private final String greeting; diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/SpringBootTestRandomPortEnvironmentPostProcessorTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/SpringBootTestRandomPortEnvironmentPostProcessorTests.java index e3ee27b5e364..3147d19e520b 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/SpringBootTestRandomPortEnvironmentPostProcessorTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/SpringBootTestRandomPortEnvironmentPostProcessorTests.java @@ -27,9 +27,10 @@ import org.springframework.core.env.MutablePropertySources; import org.springframework.mock.env.MockEnvironment; import org.springframework.test.context.support.TestPropertySourceUtils; +import org.springframework.util.PlaceholderResolutionException; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for {@link SpringBootTestRandomPortEnvironmentPostProcessor}. @@ -169,7 +170,7 @@ void postProcessWhenManagementServerPortPlaceholderAbsentShouldFail() { addTestPropertySource("0", null); this.propertySources .addLast(new MapPropertySource("other", Collections.singletonMap("management.server.port", "${port}"))); - assertThatIllegalArgumentException() + assertThatExceptionOfType(PlaceholderResolutionException.class) .isThrownBy(() -> this.postProcessor.postProcessEnvironment(this.environment, null)) .withMessage("Could not resolve placeholder 'port' in value \"${port}\""); } @@ -196,7 +197,7 @@ void postProcessWhenServerPortPlaceholderAbsentShouldFail() { source.put("server.port", "${port}"); source.put("management.server.port", "9090"); this.propertySources.addLast(new MapPropertySource("other", source)); - assertThatIllegalArgumentException() + assertThatExceptionOfType(PlaceholderResolutionException.class) .isThrownBy(() -> this.postProcessor.postProcessEnvironment(this.environment, null)) .withMessage("Could not resolve placeholder 'port' in value \"${port}\""); } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/MockServerRestClientCustomizerTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/MockServerRestClientCustomizerTests.java new file mode 100644 index 000000000000..0dffb76514a8 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/MockServerRestClientCustomizerTests.java @@ -0,0 +1,163 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.web.client; + +import java.util.function.Supplier; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.test.web.client.RequestExpectationManager; +import org.springframework.test.web.client.SimpleRequestExpectationManager; +import org.springframework.test.web.client.UnorderedRequestExpectationManager; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClient.Builder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for {@link MockServerRestClientCustomizer}. + * + * @author Scott Frederick + */ +class MockServerRestClientCustomizerTests { + + private MockServerRestClientCustomizer customizer; + + @BeforeEach + void setup() { + this.customizer = new MockServerRestClientCustomizer(); + } + + @Test + void createShouldUseSimpleRequestExpectationManager() { + MockServerRestClientCustomizer customizer = new MockServerRestClientCustomizer(); + customizer.customize(RestClient.builder()); + assertThat(customizer.getServer()).extracting("expectationManager") + .isInstanceOf(SimpleRequestExpectationManager.class); + } + + @Test + void createWhenExpectationManagerClassIsNullShouldThrowException() { + Class expectationManager = null; + assertThatIllegalArgumentException().isThrownBy(() -> new MockServerRestClientCustomizer(expectationManager)) + .withMessageContaining("ExpectationManager must not be null"); + } + + @Test + void createWhenExpectationManagerSupplierIsNullShouldThrowException() { + Supplier expectationManagerSupplier = null; + assertThatIllegalArgumentException() + .isThrownBy(() -> new MockServerRestClientCustomizer(expectationManagerSupplier)) + .withMessageContaining("ExpectationManagerSupplier must not be null"); + } + + @Test + void createShouldUseExpectationManagerClass() { + MockServerRestClientCustomizer customizer = new MockServerRestClientCustomizer( + UnorderedRequestExpectationManager.class); + customizer.customize(RestClient.builder()); + assertThat(customizer.getServer()).extracting("expectationManager") + .isInstanceOf(UnorderedRequestExpectationManager.class); + } + + @Test + void createShouldUseSupplier() { + MockServerRestClientCustomizer customizer = new MockServerRestClientCustomizer( + UnorderedRequestExpectationManager::new); + customizer.customize(RestClient.builder()); + assertThat(customizer.getServer()).extracting("expectationManager") + .isInstanceOf(UnorderedRequestExpectationManager.class); + } + + @Test + void customizeShouldBindServer() { + Builder builder = RestClient.builder(); + this.customizer.customize(builder); + this.customizer.getServer().expect(requestTo("/test")).andRespond(withSuccess()); + builder.build().get().uri("/test").retrieve().toEntity(String.class); + this.customizer.getServer().verify(); + } + + @Test + void getServerWhenNoServersAreBoundShouldThrowException() { + assertThatIllegalStateException().isThrownBy(this.customizer::getServer) + .withMessageContaining("Unable to return a single MockRestServiceServer since " + + "MockServerRestClientCustomizer has not been bound to a RestClient"); + } + + @Test + void getServerWhenMultipleServersAreBoundShouldThrowException() { + this.customizer.customize(RestClient.builder()); + this.customizer.customize(RestClient.builder()); + assertThatIllegalStateException().isThrownBy(this.customizer::getServer) + .withMessageContaining("Unable to return a single MockRestServiceServer since " + + "MockServerRestClientCustomizer has been bound to more than one RestClient"); + } + + @Test + void getServerWhenSingleServerIsBoundShouldReturnServer() { + Builder builder = RestClient.builder(); + this.customizer.customize(builder); + assertThat(this.customizer.getServer()).isEqualTo(this.customizer.getServer(builder)); + } + + @Test + void getServerWhenRestClientBuilderIsFoundShouldReturnServer() { + Builder builder1 = RestClient.builder(); + Builder builder2 = RestClient.builder(); + this.customizer.customize(builder1); + this.customizer.customize(builder2); + assertThat(this.customizer.getServer(builder1)).isNotNull(); + assertThat(this.customizer.getServer(builder2)).isNotNull().isNotSameAs(this.customizer.getServer(builder1)); + } + + @Test + void getServerWhenRestClientBuilderIsNotFoundShouldReturnNull() { + Builder builder1 = RestClient.builder(); + Builder builder2 = RestClient.builder(); + this.customizer.customize(builder1); + assertThat(this.customizer.getServer(builder1)).isNotNull(); + assertThat(this.customizer.getServer(builder2)).isNull(); + } + + @Test + void getServersShouldReturnServers() { + Builder builder1 = RestClient.builder(); + Builder builder2 = RestClient.builder(); + this.customizer.customize(builder1); + this.customizer.customize(builder2); + assertThat(this.customizer.getServers()).containsOnlyKeys(builder1, builder2); + } + + @Test + void getExpectationManagersShouldReturnExpectationManagers() { + Builder builder1 = RestClient.builder(); + Builder builder2 = RestClient.builder(); + this.customizer.customize(builder1); + this.customizer.customize(builder2); + RequestExpectationManager manager1 = this.customizer.getExpectationManagers().get(builder1); + RequestExpectationManager manager2 = this.customizer.getExpectationManagers().get(builder2); + assertThat(this.customizer.getServer(builder1)).extracting("expectationManager").isEqualTo(manager1); + assertThat(this.customizer.getServer(builder2)).extracting("expectationManager").isEqualTo(manager2); + } + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTests.java index 8a1af79ce160..022000e0be36 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTests.java @@ -45,6 +45,7 @@ import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.MethodCallback; +import org.springframework.web.client.NoOpResponseErrorHandler; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; @@ -223,13 +224,12 @@ private Stream> getConverterClasses(TestRestTemplate testRestTemplate) } @Test - void withBasicAuthShouldUseNoOpErrorHandler() throws Exception { + void withBasicAuthShouldUseNoOpErrorHandler() { TestRestTemplate originalTemplate = new TestRestTemplate("foo", "bar"); ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class); originalTemplate.getRestTemplate().setErrorHandler(errorHandler); TestRestTemplate basicAuthTemplate = originalTemplate.withBasicAuth("user", "password"); - assertThat(basicAuthTemplate.getRestTemplate().getErrorHandler()).isInstanceOf( - Class.forName("org.springframework.boot.test.web.client.TestRestTemplate$NoOpResponseErrorHandler")); + assertThat(basicAuthTemplate.getRestTemplate().getErrorHandler()).isInstanceOf(NoOpResponseErrorHandler.class); } @Test diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/htmlunit/LocalHostWebClientTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/htmlunit/LocalHostWebClientTests.java index 938e551df855..c3299a417515 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/htmlunit/LocalHostWebClientTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/htmlunit/LocalHostWebClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,10 @@ import java.io.IOException; import java.net.URL; -import com.gargoylesoftware.htmlunit.StringWebResponse; -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.WebConnection; -import com.gargoylesoftware.htmlunit.WebResponse; +import org.htmlunit.StringWebResponse; +import org.htmlunit.WebClient; +import org.htmlunit.WebConnection; +import org.htmlunit.WebResponse; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/htmlunit/webdriver/LocalHostWebConnectionHtmlUnitDriverTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/htmlunit/webdriver/LocalHostWebConnectionHtmlUnitDriverTests.java index b9634ddc6058..9b159132054f 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/htmlunit/webdriver/LocalHostWebConnectionHtmlUnitDriverTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/htmlunit/webdriver/LocalHostWebConnectionHtmlUnitDriverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,13 +18,13 @@ import java.net.URL; -import com.gargoylesoftware.htmlunit.BrowserVersion; -import com.gargoylesoftware.htmlunit.TopLevelWindow; -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.WebClientOptions; -import com.gargoylesoftware.htmlunit.WebConsole; -import com.gargoylesoftware.htmlunit.WebRequest; -import com.gargoylesoftware.htmlunit.WebWindow; +import org.htmlunit.BrowserVersion; +import org.htmlunit.TopLevelWindow; +import org.htmlunit.WebClient; +import org.htmlunit.WebClientOptions; +import org.htmlunit.WebConsole; +import org.htmlunit.WebRequest; +import org.htmlunit.WebWindow; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentMatcher; diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactor/netty/DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactoryTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactor/netty/DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactoryTests.java new file mode 100644 index 000000000000..fab2b7f6d961 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactor/netty/DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactoryTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.web.reactor.netty; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ReactorResourceFactory; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactory}. + * + * @author Phillip Webb + */ +@SpringJUnitConfig +class DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactoryTests { + + @Autowired + private ReactorResourceFactory reactorResourceFactory; + + @Test + void disablesUseGlobalResources() { + assertThat(this.reactorResourceFactory.isUseGlobalResources()).isFalse(); + } + + @Configuration + static class Config { + + @Bean + ReactorResourceFactory reactorResourceFactory() { + return new ReactorResourceFactory(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/build.gradle b/spring-boot-project/spring-boot-testcontainers/build.gradle index 357deae60644..c78397114e96 100644 --- a/spring-boot-project/spring-boot-testcontainers/build.gradle +++ b/spring-boot-project/spring-boot-testcontainers/build.gradle @@ -1,8 +1,10 @@ plugins { id "java-library" id "org.springframework.boot.auto-configuration" + id "org.springframework.boot.configuration-properties" id "org.springframework.boot.conventions" id "org.springframework.boot.deployed" + id "org.springframework.boot.docker-test" id "org.springframework.boot.optional-dependencies" } @@ -12,14 +14,58 @@ dependencies { api(project(":spring-boot-project:spring-boot-autoconfigure")) api("org.testcontainers:testcontainers") + dockerTestImplementation(project(":spring-boot-project:spring-boot-test")) + dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker")) + dockerTestImplementation("ch.qos.logback:logback-classic") + dockerTestImplementation("co.elastic.clients:elasticsearch-java") { + exclude group: "commons-logging", module: "commons-logging" + } + dockerTestImplementation("com.couchbase.client:java-client") + dockerTestImplementation("io.micrometer:micrometer-registry-otlp") + dockerTestImplementation("io.rest-assured:rest-assured") { + exclude group: "commons-logging", module: "commons-logging" + } + dockerTestImplementation("org.apache.activemq:activemq-client") + dockerTestImplementation("org.apache.activemq:artemis-jakarta-client") { + exclude group: "commons-logging", module: "commons-logging" + } + dockerTestImplementation("org.apache.cassandra:java-driver-core") { + exclude group: "org.slf4j", module: "jcl-over-slf4j" + } + dockerTestImplementation("org.assertj:assertj-core") + dockerTestImplementation("org.awaitility:awaitility") + dockerTestImplementation("org.flywaydb:flyway-core") + dockerTestImplementation("org.junit.jupiter:junit-jupiter") + dockerTestImplementation("org.junit.platform:junit-platform-launcher") + dockerTestImplementation("org.liquibase:liquibase-core") { + exclude(group: "javax.xml.bind", module: "jaxb-api") + } + dockerTestImplementation("org.mockito:mockito-core") + dockerTestImplementation("org.springframework:spring-core-test") + dockerTestImplementation("org.springframework:spring-jdbc") + dockerTestImplementation("org.springframework:spring-jms") + dockerTestImplementation("org.springframework:spring-r2dbc") + dockerTestImplementation("org.springframework.amqp:spring-rabbit") + dockerTestImplementation("org.springframework.data:spring-data-redis") + dockerTestImplementation("org.springframework.kafka:spring-kafka") + dockerTestImplementation("org.springframework.ldap:spring-ldap-core") + dockerTestImplementation("org.springframework.pulsar:spring-pulsar") + dockerTestImplementation("org.testcontainers:junit-jupiter") + + dockerTestRuntimeOnly("com.oracle.database.r2dbc:oracle-r2dbc") + dockerTestRuntimeOnly("com.zaxxer:HikariCP") + dockerTestRuntimeOnly("io.lettuce:lettuce-core") + dockerTestRuntimeOnly("org.flywaydb:flyway-database-postgresql") + dockerTestRuntimeOnly("org.postgresql:postgresql") + optional(project(":spring-boot-project:spring-boot-actuator-autoconfigure")) optional("org.springframework:spring-test") optional("org.springframework.data:spring-data-mongodb") optional("org.springframework.data:spring-data-neo4j") + optional("org.testcontainers:activemq") optional("org.testcontainers:cassandra") optional("org.testcontainers:couchbase") optional("org.testcontainers:elasticsearch") - optional("org.testcontainers:influxdb") optional("org.testcontainers:jdbc") optional("org.testcontainers:kafka") optional("org.testcontainers:mariadb") @@ -28,42 +74,26 @@ dependencies { optional("org.testcontainers:mysql") optional("org.testcontainers:neo4j") optional("org.testcontainers:oracle-xe") + optional("org.testcontainers:oracle-free") optional("org.testcontainers:postgresql") + optional("org.testcontainers:pulsar") optional("org.testcontainers:rabbitmq") optional("org.testcontainers:redpanda") optional("org.testcontainers:r2dbc") - testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation(project(":spring-boot-project:spring-boot-test")) - testImplementation("ch.qos.logback:logback-classic") - testImplementation("co.elastic.clients:elasticsearch-java") { - exclude group: "commons-logging", module: "commons-logging" - } - testImplementation("com.couchbase.client:java-client") - testImplementation("com.datastax.oss:java-driver-core") + testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation("org.assertj:assertj-core") - testImplementation("org.awaitility:awaitility") - testImplementation("org.flywaydb:flyway-core") - testImplementation("org.influxdb:influxdb-java") testImplementation("org.junit.jupiter:junit-jupiter") - testImplementation("org.junit.platform:junit-platform-engine") - testImplementation("org.junit.platform:junit-platform-launcher") - testImplementation("org.liquibase:liquibase-core") { - exclude(group: "javax.xml.bind", module: "jaxb-api") - } testImplementation("org.mockito:mockito-core") testImplementation("org.mockito:mockito-junit-jupiter") testImplementation("org.springframework:spring-core-test") testImplementation("org.springframework:spring-jdbc") + testImplementation("org.springframework:spring-jms") testImplementation("org.springframework:spring-r2dbc") testImplementation("org.springframework.amqp:spring-rabbit") testImplementation("org.springframework.data:spring-data-redis") testImplementation("org.springframework.kafka:spring-kafka") + testImplementation("org.springframework.pulsar:spring-pulsar") testImplementation("org.testcontainers:junit-jupiter") - - testRuntimeOnly("com.oracle.database.r2dbc:oracle-r2dbc") - testRuntimeOnly("com.zaxxer:HikariCP") - testRuntimeOnly("io.lettuce:lettuce-core") - testRuntimeOnly("org.postgresql:postgresql") } - diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/ImportTestcontainersTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/ImportTestcontainersTests.java similarity index 90% rename from spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/ImportTestcontainersTests.java rename to spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/ImportTestcontainersTests.java index 6b3ef4722fd5..c3d0bd43703b 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/ImportTestcontainersTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/ImportTestcontainersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,8 +26,8 @@ import org.springframework.boot.testcontainers.beans.TestcontainerBeanDefinition; import org.springframework.boot.testcontainers.context.ImportTestcontainers; -import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; @@ -126,7 +126,7 @@ void importWhenHasBadArgsDynamicPropertySourceMethod() { static class ImportWithoutValue { @ContainerAnnotation - static PostgreSQLContainer container = new PostgreSQLContainer<>(DockerImageNames.postgresql()); + static PostgreSQLContainer container = TestImage.container(PostgreSQLContainer.class); } @@ -150,14 +150,14 @@ static class NullContainer { @ImportTestcontainers static class NonStaticContainer { - PostgreSQLContainer container = new PostgreSQLContainer<>(DockerImageNames.postgresql()); + PostgreSQLContainer container = TestImage.container(PostgreSQLContainer.class); } interface ContainerDefinitions { @ContainerAnnotation - PostgreSQLContainer container = new PostgreSQLContainer<>(DockerImageNames.postgresql()); + PostgreSQLContainer container = TestImage.container(PostgreSQLContainer.class); } @@ -169,7 +169,7 @@ interface ContainerDefinitions { @ImportTestcontainers static class ContainerDefinitionsWithDynamicPropertySource { - static PostgreSQLContainer container = new PostgreSQLContainer<>(DockerImageNames.postgresql()); + static PostgreSQLContainer container = TestImage.container(PostgreSQLContainer.class); @DynamicPropertySource static void containerProperties(DynamicPropertyRegistry registry) { diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/LoadTimeWeaverAwareConsumerContainers.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/LoadTimeWeaverAwareConsumerContainers.java new file mode 100644 index 000000000000..da5f6c8a7874 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/LoadTimeWeaverAwareConsumerContainers.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers; + +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; + +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +/** + * Container definitions for {@link LoadTimeWeaverAwareConsumerImportTestcontainersTests}. + * + * @author Andy Wilkinson + */ +interface LoadTimeWeaverAwareConsumerContainers { + + @Container + @ServiceConnection + PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer<>("postgres:16.1"); + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/LoadTimeWeaverAwareConsumerImportTestcontainersTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/LoadTimeWeaverAwareConsumerImportTestcontainersTests.java new file mode 100644 index 000000000000..a41ad8aea17b --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/LoadTimeWeaverAwareConsumerImportTestcontainersTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.context.ImportTestcontainers; +import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.weaving.LoadTimeWeaverAware; +import org.springframework.instrument.classloading.LoadTimeWeaver; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@DisabledIfDockerUnavailable +@ImportTestcontainers(LoadTimeWeaverAwareConsumerContainers.class) +public class LoadTimeWeaverAwareConsumerImportTestcontainersTests implements LoadTimeWeaverAwareConsumerContainers { + + @Autowired + private LoadTimeWeaverAwareConsumer consumer; + + @Test + void loadTimeWeaverAwareBeanCanUseJdbcUrlFromContainerBasedConnectionDetails() { + assertThat(this.consumer.jdbcUrl).isNotNull(); + } + + @Configuration + @ImportAutoConfiguration(DataSourceAutoConfiguration.class) + static class TestConfiguration { + + @Bean + LoadTimeWeaverAwareConsumer loadTimeWeaverAwareConsumer(JdbcConnectionDetails connectionDetails) { + return new LoadTimeWeaverAwareConsumer(connectionDetails); + } + + } + + static class LoadTimeWeaverAwareConsumer implements LoadTimeWeaverAware { + + private final String jdbcUrl; + + LoadTimeWeaverAwareConsumer(JdbcConnectionDetails connectionDetails) { + this.jdbcUrl = connectionDetails.getJdbcUrl(); + } + + @Override + public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) { + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/lifecycle/TestContainersParallelStartupIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/lifecycle/TestContainersParallelStartupIntegrationTests.java new file mode 100644 index 000000000000..b87412d0cc57 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/lifecycle/TestContainersParallelStartupIntegrationTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.lifecycle; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.testcontainers.containers.PostgreSQLContainer; + +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.boot.testcontainers.lifecycle.TestContainersParallelStartupIntegrationTests.ContainerConfig; +import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration test for parallel startup. + * + * @author Phillip Webb + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = ContainerConfig.class) +@TestPropertySource(properties = "spring.testcontainers.beans.startup=parallel") +@DisabledIfDockerUnavailable +@ExtendWith(OutputCaptureExtension.class) +public class TestContainersParallelStartupIntegrationTests { + + @Test + void startsInParallel(CapturedOutput out) { + assertThat(out).contains("-lifecycle-0").contains("-lifecycle-1").contains("-lifecycle-2"); + } + + @Configuration(proxyBeanMethods = false) + static class ContainerConfig { + + @Bean + static PostgreSQLContainer container1() { + return TestImage.container(PostgreSQLContainer.class); + } + + @Bean + static PostgreSQLContainer container2() { + return TestImage.container(PostgreSQLContainer.class); + } + + @Bean + static PostgreSQLContainer container3() { + return TestImage.container(PostgreSQLContainer.class); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/lifecycle/TestContainersParallelStartupWithImportTestcontainersIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/lifecycle/TestContainersParallelStartupWithImportTestcontainersIntegrationTests.java new file mode 100644 index 000000000000..e3a9cfb57fbb --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/lifecycle/TestContainersParallelStartupWithImportTestcontainersIntegrationTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.lifecycle; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; + +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.boot.testcontainers.context.ImportTestcontainers; +import org.springframework.boot.testcontainers.lifecycle.TestContainersParallelStartupWithImportTestcontainersIntegrationTests.Containers; +import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration test for parallel startup. + * + * @author Phillip Webb + */ +@ExtendWith(SpringExtension.class) +@TestPropertySource(properties = "spring.testcontainers.beans.startup=parallel") +@DisabledIfDockerUnavailable +@ExtendWith(OutputCaptureExtension.class) +@ImportTestcontainers(Containers.class) +class TestContainersParallelStartupWithImportTestcontainersIntegrationTests { + + @Test + void startsInParallel(CapturedOutput out) { + assertThat(out).contains("-lifecycle-0").contains("-lifecycle-1").contains("-lifecycle-2"); + } + + static class Containers { + + @Container + static PostgreSQLContainer container1 = TestImage.container(PostgreSQLContainer.class); + + @Container + static PostgreSQLContainer container2 = TestImage.container(PostgreSQLContainer.class); + + @Container + static PostgreSQLContainer container3 = TestImage.container(PostgreSQLContainer.class); + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersImportWithPropertiesInjectedIntoLoadTimeWeaverAwareBeanIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersImportWithPropertiesInjectedIntoLoadTimeWeaverAwareBeanIntegrationTests.java new file mode 100644 index 000000000000..192752927b17 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersImportWithPropertiesInjectedIntoLoadTimeWeaverAwareBeanIntegrationTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.lifecycle; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.testcontainers.context.ImportTestcontainers; +import org.springframework.boot.testcontainers.lifecycle.TestcontainersImportWithPropertiesInjectedIntoLoadTimeWeaverAwareBeanIntegrationTests.Containers; +import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Bean; +import org.springframework.context.weaving.LoadTimeWeaverAware; +import org.springframework.instrument.classloading.LoadTimeWeaver; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * Tests for {@link ImportTestcontainers} when properties are being injected into a + * {@link LoadTimeWeaverAware} bean. + * + * @author Phillip Webb + */ +@ExtendWith(SpringExtension.class) +@DisabledIfDockerUnavailable +@ImportTestcontainers(Containers.class) +class TestcontainersImportWithPropertiesInjectedIntoLoadTimeWeaverAwareBeanIntegrationTests { + + // gh-38913 + + @Test + void starts() { + } + + @TestConfiguration + @EnableConfigurationProperties(MockDataSourceProperties.class) + static class Config { + + @Bean + MockEntityManager mockEntityManager(MockDataSourceProperties properties) { + return new MockEntityManager(); + } + + } + + static class MockEntityManager implements LoadTimeWeaverAware { + + @Override + public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) { + } + + } + + @ConfigurationProperties("spring.datasource") + public static class MockDataSourceProperties { + + private String url; + + public String getUrl() { + return this.url; + } + + public void setUrl(String url) { + this.url = url; + } + + } + + static class Containers { + + @Container + static PostgreSQLContainer container = TestImage.container(PostgreSQLContainer.class); + + @DynamicPropertySource + static void setConnectionProperties(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.url", container::getJdbcUrl); + registry.add("spring.datasource.password", container::getPassword); + registry.add("spring.datasource.username", container::getUsername); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleOrderIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleOrderIntegrationTests.java similarity index 80% rename from spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleOrderIntegrationTests.java rename to spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleOrderIntegrationTests.java index 1fda208130f9..f4f912f84c24 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleOrderIntegrationTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleOrderIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,13 +23,15 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; +import org.testcontainers.utility.DockerImageName; import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleOrderIntegrationTests.AssertingSpringExtension; import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleOrderIntegrationTests.ContainerConfig; import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleOrderIntegrationTests.TestConfig; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable; -import org.springframework.boot.testsupport.testcontainers.RedisContainer; +import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; +import org.springframework.boot.testsupport.container.RedisContainer; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; @@ -63,21 +65,7 @@ static class ContainerConfig { @Bean @ServiceConnection("redis") RedisContainer redisContainer() { - return new RedisContainer() { - - @Override - public void start() { - events.add("start-container"); - super.start(); - } - - @Override - public void stop() { - events.add("stop-container"); - super.stop(); - } - - }; + return TestImage.container(EventRecordingRedisContainer.class); } } @@ -112,4 +100,24 @@ public void afterAll(ExtensionContext context) throws Exception { } + static class EventRecordingRedisContainer extends RedisContainer { + + EventRecordingRedisContainer(DockerImageName dockerImageName) { + super(dockerImageName); + } + + @Override + public void start() { + events.add("start-container"); + super.start(); + } + + @Override + public void stop() { + events.add("stop-container"); + super.stop(); + } + + } + } diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleOrderWithScopeIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleOrderWithScopeIntegrationTests.java new file mode 100644 index 000000000000..48614af9f472 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleOrderWithScopeIntegrationTests.java @@ -0,0 +1,200 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.lifecycle; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.testcontainers.utility.DockerImageName; + +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleOrderWithScopeIntegrationTests.AssertingSpringExtension; +import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleOrderWithScopeIntegrationTests.ContainerConfig; +import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleOrderWithScopeIntegrationTests.ScopedContextLoader; +import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleOrderWithScopeIntegrationTests.TestConfig; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; +import org.springframework.boot.testsupport.container.RedisContainer; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.support.AnnotationConfigContextLoader; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link TestcontainersLifecycleBeanPostProcessor} to ensure create + * and destroy events happen in the correct order. + * + * @author Phillip Webb + */ +@ExtendWith(AssertingSpringExtension.class) +@ContextConfiguration(loader = ScopedContextLoader.class, classes = { TestConfig.class, ContainerConfig.class }) +@DirtiesContext +@DisabledIfDockerUnavailable +class TestcontainersLifecycleOrderWithScopeIntegrationTests { + + static List events = Collections.synchronizedList(new ArrayList<>()); + + @Test + void eventsAreOrderedCorrectlyAfterStartup() { + assertThat(events).containsExactly("start-container", "create-bean"); + } + + @Configuration(proxyBeanMethods = false) + static class ContainerConfig { + + @Bean + @Scope("custom") + @ServiceConnection("redis") + RedisContainer redisContainer() { + return TestImage.container(EventRecordingRedisContainer.class); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TestConfig { + + @Bean + TestBean testBean() { + events.add("create-bean"); + return new TestBean(); + } + + } + + static class TestBean implements AutoCloseable { + + @Override + public void close() throws Exception { + events.add("destroy-bean"); + } + + } + + static class AssertingSpringExtension extends SpringExtension { + + @Override + public void afterAll(ExtensionContext context) throws Exception { + super.afterAll(context); + assertThat(events).containsExactly("start-container", "create-bean", "destroy-bean", "stop-container"); + } + + } + + static class EventRecordingRedisContainer extends RedisContainer { + + EventRecordingRedisContainer(DockerImageName dockerImageName) { + super(dockerImageName); + } + + @Override + public void start() { + events.add("start-container"); + super.start(); + } + + @Override + public void stop() { + events.add("stop-container"); + super.stop(); + } + + } + + static class ScopedContextLoader extends AnnotationConfigContextLoader { + + @Override + protected GenericApplicationContext createContext() { + CustomScope customScope = new CustomScope(); + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext() { + + @Override + protected void onClose() { + customScope.destroy(); + super.onClose(); + } + + }; + context.getBeanFactory().registerScope("custom", customScope); + return context; + } + + } + + static class CustomScope implements org.springframework.beans.factory.config.Scope { + + private Map instances = new HashMap<>(); + + private MultiValueMap destructors = new LinkedMultiValueMap<>(); + + @Override + public Object get(String name, ObjectFactory objectFactory) { + return this.instances.computeIfAbsent(name, (key) -> objectFactory.getObject()); + } + + @Override + public Object remove(String name) { + synchronized (this) { + Object removed = this.instances.remove(name); + this.destructors.get(name).forEach(Runnable::run); + this.destructors.remove(name); + return removed; + } + } + + @Override + public void registerDestructionCallback(String name, Runnable callback) { + this.destructors.add(name, callback); + } + + @Override + public Object resolveContextualObject(String key) { + return null; + } + + @Override + public String getConversationId() { + return null; + } + + void destroy() { + synchronized (this) { + this.destructors.forEach((name, actions) -> actions.forEach(Runnable::run)); + this.destructors.clear(); + this.instances.clear(); + } + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationTests.java new file mode 100644 index 000000000000..310c0758f0fa --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.properties; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.testcontainers.lifecycle.BeforeTestcontainerUsedEvent; +import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleApplicationContextInitializer; +import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; +import org.springframework.boot.testsupport.container.RedisContainer; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.DynamicPropertyRegistry; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link TestcontainersPropertySourceAutoConfiguration}. + * + * @author Phillip Webb + */ +@DisabledIfDockerUnavailable +class TestcontainersPropertySourceAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withInitializer(new TestcontainersLifecycleApplicationContextInitializer()) + .withConfiguration(AutoConfigurations.of(TestcontainersPropertySourceAutoConfiguration.class)); + + @Test + void containerBeanMethodContributesProperties() { + List events = new ArrayList<>(); + this.contextRunner.withUserConfiguration(ContainerAndPropertiesConfiguration.class) + .withInitializer((context) -> context.addApplicationListener(events::add)) + .run((context) -> { + TestBean testBean = context.getBean(TestBean.class); + RedisContainer redisContainer = context.getBean(RedisContainer.class); + assertThat(testBean.getUsingPort()).isEqualTo(redisContainer.getFirstMappedPort()); + assertThat(events.stream().filter(BeforeTestcontainerUsedEvent.class::isInstance)).hasSize(1); + }); + } + + @Configuration(proxyBeanMethods = false) + @EnableConfigurationProperties(ContainerProperties.class) + @Import(TestBean.class) + static class ContainerAndPropertiesConfiguration { + + @Bean + RedisContainer redisContainer(DynamicPropertyRegistry properties) { + RedisContainer container = TestImage.container(RedisContainer.class); + properties.add("container.port", container::getFirstMappedPort); + return container; + } + + } + + @ConfigurationProperties("container") + record ContainerProperties(int port) { + } + + static class TestBean { + + private int usingPort; + + TestBean(ContainerProperties containerProperties) { + this.usingPort = containerProperties.port(); + } + + int getUsingPort() { + return this.usingPort; + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionAutoConfigurationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionAutoConfigurationTests.java similarity index 94% rename from spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionAutoConfigurationTests.java rename to spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionAutoConfigurationTests.java index 009092cebb50..85a958359fb9 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,8 +31,9 @@ import org.springframework.boot.testcontainers.beans.TestcontainerBeanDefinition; import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleApplicationContextInitializer; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; -import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable; -import org.springframework.boot.testsupport.testcontainers.RedisContainer; +import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; +import org.springframework.boot.testsupport.container.RedisContainer; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -135,7 +136,7 @@ static class ContainerConfiguration { @Bean @ServiceConnection("redis") RedisContainer redisContainer() { - return new RedisContainer(); + return TestImage.container(RedisContainer.class); } } @@ -168,7 +169,7 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B static class TestcontainersRootBeanDefinition extends RootBeanDefinition implements TestcontainerBeanDefinition { - private final RedisContainer container = new RedisContainer(); + private final RedisContainer container = TestImage.container(RedisContainer.class); TestcontainersRootBeanDefinition() { setBeanClass(RedisContainer.class); diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQClassicContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQClassicContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..daa37e14f143 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQClassicContainerConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.activemq; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; +import org.testcontainers.activemq.ActiveMQContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration; +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jms.annotation.JmsListener; +import org.springframework.jms.core.JmsMessagingTemplate; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ActiveMQClassicContainerConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class ActiveMQClassicContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final ActiveMQContainer activemq = TestImage.container(ActiveMQContainer.class); + + @Autowired + private JmsMessagingTemplate jmsTemplate; + + @Autowired + private TestListener listener; + + @Test + void connectionCanBeMadeToActiveMQContainer() { + this.jmsTemplate.convertAndSend("sample.queue", "message"); + Awaitility.waitAtMost(Duration.ofMinutes(1)) + .untilAsserted(() -> assertThat(this.listener.messages).containsExactly("message")); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration({ ActiveMQAutoConfiguration.class, JmsAutoConfiguration.class }) + static class TestConfiguration { + + @Bean + TestListener testListener() { + return new TestListener(); + } + + } + + static class TestListener { + + private final List messages = new ArrayList<>(); + + @JmsListener(destination = "sample.queue") + void processMessage(String message) { + this.messages.add(message); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..c53c3e3ad988 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.activemq; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration; +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.SymptomaActiveMQContainer; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jms.annotation.JmsListener; +import org.springframework.jms.core.JmsMessagingTemplate; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ActiveMQContainerConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class ActiveMQContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final SymptomaActiveMQContainer activemq = TestImage.container(SymptomaActiveMQContainer.class); + + @Autowired + private JmsMessagingTemplate jmsTemplate; + + @Autowired + private TestListener listener; + + @Test + void connectionCanBeMadeToActiveMQContainer() { + this.jmsTemplate.convertAndSend("sample.queue", "message"); + Awaitility.waitAtMost(Duration.ofMinutes(1)) + .untilAsserted(() -> assertThat(this.listener.messages).containsExactly("message")); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration({ ActiveMQAutoConfiguration.class, JmsAutoConfiguration.class }) + static class TestConfiguration { + + @Bean + TestListener testListener() { + return new TestListener(); + } + + } + + static class TestListener { + + private final List messages = new ArrayList<>(); + + @JmsListener(destination = "sample.queue") + void processMessage(String message) { + this.messages.add(message); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/activemq/ArtemisContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/activemq/ArtemisContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..e1b8c7ae5f41 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/activemq/ArtemisContainerConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.activemq; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; +import org.testcontainers.activemq.ArtemisContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration; +import org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jms.annotation.JmsListener; +import org.springframework.jms.core.JmsMessagingTemplate; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ArtemisContainerConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class ArtemisContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final ArtemisContainer artemis = TestImage.container(ArtemisContainer.class); + + @Autowired + private JmsMessagingTemplate jmsTemplate; + + @Autowired + private TestListener listener; + + @Test + void connectionCanBeMadeToActiveMQContainer() { + this.jmsTemplate.convertAndSend("sample.queue", "message"); + Awaitility.waitAtMost(Duration.ofMinutes(1)) + .untilAsserted(() -> assertThat(this.listener.messages).containsExactly("message")); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration({ ArtemisAutoConfiguration.class, JmsAutoConfiguration.class }) + static class TestConfiguration { + + @Bean + TestListener testListener() { + return new TestListener(); + } + + } + + static class TestListener { + + private final List messages = new ArrayList<>(); + + @JmsListener(destination = "sample.queue") + void processMessage(String message) { + this.messages.add(message); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitContainerConnectionDetailsFactoryIntegrationTests.java similarity index 91% rename from spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitContainerConnectionDetailsFactoryIntegrationTests.java rename to spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitContainerConnectionDetailsFactoryIntegrationTests.java index 8567d2f03b61..11fe6629a395 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitContainerConnectionDetailsFactoryIntegrationTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitContainerConnectionDetailsFactoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -54,8 +54,7 @@ class RabbitContainerConnectionDetailsFactoryIntegrationTests { @Container @ServiceConnection - static final RabbitMQContainer rabbit = new RabbitMQContainer(DockerImageNames.rabbit()) - .withStartupTimeout(Duration.ofMinutes(4)); + static final RabbitMQContainer rabbit = TestImage.container(RabbitMQContainer.class); @Autowired(required = false) private RabbitConnectionDetails connectionDetails; diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/cassandra/CassandraContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/cassandra/CassandraContainerConnectionDetailsFactoryTests.java similarity index 90% rename from spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/cassandra/CassandraContainerConnectionDetailsFactoryTests.java rename to spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/cassandra/CassandraContainerConnectionDetailsFactoryTests.java index a2e7ecc550cd..19535dc537f7 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/cassandra/CassandraContainerConnectionDetailsFactoryTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/cassandra/CassandraContainerConnectionDetailsFactoryTests.java @@ -18,6 +18,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import org.junit.jupiter.api.Test; +import org.testcontainers.containers.CassandraContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -26,7 +27,7 @@ import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; import org.springframework.boot.autoconfigure.cassandra.CassandraConnectionDetails; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.CassandraContainer; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -43,7 +44,7 @@ class CassandraContainerConnectionDetailsFactoryTests { @Container @ServiceConnection - static final CassandraContainer cassandra = new CassandraContainer(); + static final CassandraContainer cassandra = TestImage.container(CassandraContainer.class); @Autowired(required = false) private CassandraConnectionDetails connectionDetails; diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/couchbase/CouchbaseContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/couchbase/CouchbaseContainerConnectionDetailsFactoryTests.java similarity index 90% rename from spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/couchbase/CouchbaseContainerConnectionDetailsFactoryTests.java rename to spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/couchbase/CouchbaseContainerConnectionDetailsFactoryTests.java index debefc398067..d31beee3ad24 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/couchbase/CouchbaseContainerConnectionDetailsFactoryTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/couchbase/CouchbaseContainerConnectionDetailsFactoryTests.java @@ -16,8 +16,6 @@ package org.springframework.boot.testcontainers.service.connection.couchbase; -import java.time.Duration; - import com.couchbase.client.java.Cluster; import org.junit.jupiter.api.Test; import org.testcontainers.couchbase.BucketDefinition; @@ -31,7 +29,7 @@ import org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration; import org.springframework.boot.autoconfigure.couchbase.CouchbaseConnectionDetails; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -48,10 +46,8 @@ class CouchbaseContainerConnectionDetailsFactoryTests { @Container @ServiceConnection - static final CouchbaseContainer couchbase = new CouchbaseContainer(DockerImageNames.couchbase()) + static final CouchbaseContainer couchbase = TestImage.container(CouchbaseContainer.class) .withEnabledServices(CouchbaseService.KV, CouchbaseService.INDEX, CouchbaseService.QUERY) - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)) .withBucket(new BucketDefinition("cbbucket")); @Autowired(required = false) diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/elasticsearch/ElasticsearchContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/elasticsearch/ElasticsearchContainerConnectionDetailsFactoryTests.java similarity index 88% rename from spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/elasticsearch/ElasticsearchContainerConnectionDetailsFactoryTests.java rename to spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/elasticsearch/ElasticsearchContainerConnectionDetailsFactoryTests.java index 64f693f46e3a..be32783368e2 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/elasticsearch/ElasticsearchContainerConnectionDetailsFactoryTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/elasticsearch/ElasticsearchContainerConnectionDetailsFactoryTests.java @@ -17,7 +17,6 @@ package org.springframework.boot.testcontainers.service.connection.elasticsearch; import java.io.IOException; -import java.time.Duration; import co.elastic.clients.elasticsearch.ElasticsearchClient; import org.junit.jupiter.api.Test; @@ -31,7 +30,7 @@ import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails; import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -48,10 +47,7 @@ class ElasticsearchContainerConnectionDetailsFactoryTests { @Container @ServiceConnection - static final ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) - .withEnv("ES_JAVA_OPTS", "-Xms32m -Xmx512m") - .withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(10)); + static final ElasticsearchContainer elasticsearch = TestImage.container(ElasticsearchContainer.class); @Autowired(required = false) private ElasticsearchConnectionDetails connectionDetails; diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/flyway/FlywayContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/flyway/FlywayContainerConnectionDetailsFactoryTests.java similarity index 92% rename from spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/flyway/FlywayContainerConnectionDetailsFactoryTests.java rename to spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/flyway/FlywayContainerConnectionDetailsFactoryTests.java index c6ce96899a47..cc83fb0e6fc4 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/flyway/FlywayContainerConnectionDetailsFactoryTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/flyway/FlywayContainerConnectionDetailsFactoryTests.java @@ -27,7 +27,7 @@ import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -46,7 +46,7 @@ class FlywayContainerConnectionDetailsFactoryTests { @Container @ServiceConnection - static final PostgreSQLContainer postgres = new PostgreSQLContainer<>(DockerImageNames.postgresql()); + static final PostgreSQLContainer postgres = TestImage.container(PostgreSQLContainer.class); @Autowired(required = false) private JdbcConnectionDetails connectionDetails; diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/jdbc/JdbcContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/jdbc/JdbcContainerConnectionDetailsFactoryTests.java similarity index 93% rename from spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/jdbc/JdbcContainerConnectionDetailsFactoryTests.java rename to spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/jdbc/JdbcContainerConnectionDetailsFactoryTests.java index feff30939285..e2b0d92caacd 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/jdbc/JdbcContainerConnectionDetailsFactoryTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/jdbc/JdbcContainerConnectionDetailsFactoryTests.java @@ -29,7 +29,7 @@ import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -48,7 +48,7 @@ class JdbcContainerConnectionDetailsFactoryTests { @Container @ServiceConnection - static final PostgreSQLContainer postgres = new PostgreSQLContainer<>(DockerImageNames.postgresql()); + static final PostgreSQLContainer postgres = TestImage.container(PostgreSQLContainer.class); @Autowired(required = false) private JdbcConnectionDetails connectionDetails; diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/kafka/ApacheKafkaContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/kafka/ApacheKafkaContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..516b854d7d8b --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/kafka/ApacheKafkaContainerConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.kafka; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.kafka.KafkaContainer; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConfluentKafkaContainerConnectionDetailsFactory}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + * @author Eddú Meléndez + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +@TestPropertySource(properties = { "spring.kafka.consumer.group-id=test-group", + "spring.kafka.consumer.auto-offset-reset=earliest" }) +class ApacheKafkaContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final KafkaContainer kafka = TestImage.container(KafkaContainer.class); + + @Autowired + private KafkaTemplate kafkaTemplate; + + @Autowired + private TestListener listener; + + @Test + void connectionCanBeMadeToKafkaContainer() { + this.kafkaTemplate.send("test-topic", "test-data"); + Awaitility.waitAtMost(Duration.ofMinutes(4)) + .untilAsserted(() -> assertThat(this.listener.messages).containsExactly("test-data")); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(KafkaAutoConfiguration.class) + static class TestConfiguration { + + @Bean + TestListener testListener() { + return new TestListener(); + } + + } + + static class TestListener { + + private final List messages = new ArrayList<>(); + + @KafkaListener(topics = "test-topic") + void processMessage(String message) { + this.messages.add(message); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/kafka/ConfluentKafkaContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/kafka/ConfluentKafkaContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..d02f9b93e3ab --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/kafka/ConfluentKafkaContainerConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.kafka; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.KafkaContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConfluentKafkaContainerConnectionDetailsFactory}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +@TestPropertySource(properties = { "spring.kafka.consumer.group-id=test-group", + "spring.kafka.consumer.auto-offset-reset=earliest" }) +class ConfluentKafkaContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final KafkaContainer kafka = TestImage.container(KafkaContainer.class); + + @Autowired + private KafkaTemplate kafkaTemplate; + + @Autowired + private TestListener listener; + + @Test + void connectionCanBeMadeToKafkaContainer() { + this.kafkaTemplate.send("test-topic", "test-data"); + Awaitility.waitAtMost(Duration.ofMinutes(4)) + .untilAsserted(() -> assertThat(this.listener.messages).containsExactly("test-data")); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(KafkaAutoConfiguration.class) + static class TestConfiguration { + + @Bean + TestListener testListener() { + return new TestListener(); + } + + } + + static class TestListener { + + private final List messages = new ArrayList<>(); + + @KafkaListener(topics = "test-topic") + void processMessage(String message) { + this.messages.add(message); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/ldap/OpenLdapContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/ldap/OpenLdapContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..d269c3cb43d3 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/ldap/OpenLdapContainerConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.ldap; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.OpenLdapContainer; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Configuration; +import org.springframework.ldap.core.AttributesMapper; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.query.LdapQueryBuilder; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OpenLdapContainerConnectionDetailsFactory}. + * + * @author Philipp Kessler + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class OpenLdapContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final OpenLdapContainer openLdap = TestImage.container(OpenLdapContainer.class).withEnv("LDAP_TLS", "false"); + + @Autowired + private LdapTemplate ldapTemplate; + + @Test + void connectionCanBeMadeToLdapContainer() { + List cn = this.ldapTemplate.search(LdapQueryBuilder.query().where("objectclass").is("dcObject"), + (AttributesMapper) (attributes) -> attributes.get("dc").get().toString()); + assertThat(cn).hasSize(1); + assertThat(cn.get(0)).isEqualTo("example"); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration({ LdapAutoConfiguration.class }) + static class TestConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/liquibase/LiquibaseContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/liquibase/LiquibaseContainerConnectionDetailsFactoryTests.java similarity index 93% rename from spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/liquibase/LiquibaseContainerConnectionDetailsFactoryTests.java rename to spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/liquibase/LiquibaseContainerConnectionDetailsFactoryTests.java index a37595667f78..f9a585872ff0 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/liquibase/LiquibaseContainerConnectionDetailsFactoryTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/liquibase/LiquibaseContainerConnectionDetailsFactoryTests.java @@ -27,7 +27,7 @@ import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -46,7 +46,7 @@ class LiquibaseContainerConnectionDetailsFactoryTests { @Container @ServiceConnection - static final PostgreSQLContainer postgres = new PostgreSQLContainer<>(DockerImageNames.postgresql()); + static final PostgreSQLContainer postgres = TestImage.container(PostgreSQLContainer.class); @Autowired(required = false) private JdbcConnectionDetails connectionDetails; diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryMetricsContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryMetricsContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..c48d6a2135ce --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryMetricsContainerConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,118 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.otlp; + +import java.time.Duration; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.DistributionSummary; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Timer; +import io.restassured.RestAssured; +import io.restassured.response.Response; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.MountableFile; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsExportAutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.matchesPattern; + +/** + * Tests for {@link OpenTelemetryMetricsContainerConnectionDetailsFactory}. + * + * @author Eddú Meléndez + * @author Jonatan Ivanov + */ +@SpringJUnitConfig +@TestPropertySource(properties = { "management.otlp.metrics.export.resource-attributes.service.name=test", + "management.otlp.metrics.export.step=1s" }) +@Testcontainers(disabledWithoutDocker = true) +class OpenTelemetryMetricsContainerConnectionDetailsFactoryIntegrationTests { + + private static final String OPENMETRICS_001 = "application/openmetrics-text; version=0.0.1; charset=utf-8"; + + private static final String CONFIG_FILE_NAME = "collector-config.yml"; + + @Container + @ServiceConnection + static final GenericContainer container = TestImage.OPENTELEMETRY.genericContainer() + .withCommand("--config=/etc/" + CONFIG_FILE_NAME) + .withCopyToContainer(MountableFile.forClasspathResource(CONFIG_FILE_NAME), "/etc/" + CONFIG_FILE_NAME) + .withExposedPorts(4318, 9090); + + @Autowired + private MeterRegistry meterRegistry; + + @Test + void connectionCanBeMadeToOpenTelemetryCollectorContainer() { + Counter.builder("test.counter").register(this.meterRegistry).increment(42); + Gauge.builder("test.gauge", () -> 12).register(this.meterRegistry); + Timer.builder("test.timer").register(this.meterRegistry).record(Duration.ofMillis(123)); + DistributionSummary.builder("test.distributionsummary").register(this.meterRegistry).record(24); + Awaitility.await() + .atMost(Duration.ofSeconds(5)) + .pollDelay(Duration.ofMillis(100)) + .pollInterval(Duration.ofMillis(100)) + .untilAsserted(() -> whenPrometheusScraped().then() + .statusCode(200) + .contentType(OPENMETRICS_001) + .body(endsWith("# EOF\n"), containsString("service_name"))); + whenPrometheusScraped().then() + .body(containsString( + "{job=\"test\",service_name=\"test\",telemetry_sdk_language=\"java\",telemetry_sdk_name=\"io.micrometer\""), + matchesPattern("(?s)^.*test_counter\\{.+} 42\\.0\\n.*$"), + matchesPattern("(?s)^.*test_gauge\\{.+} 12\\.0\\n.*$"), + matchesPattern("(?s)^.*test_timer_count\\{.+} 1\\n.*$"), + matchesPattern("(?s)^.*test_timer_sum\\{.+} 123\\.0\\n.*$"), + matchesPattern("(?s)^.*test_timer_bucket\\{.+,le=\"\\+Inf\"} 1\\n.*$"), + matchesPattern("(?s)^.*test_distributionsummary_count\\{.+} 1\\n.*$"), + matchesPattern("(?s)^.*test_distributionsummary_sum\\{.+} 24\\.0\\n.*$"), + matchesPattern("(?s)^.*test_distributionsummary_bucket\\{.+,le=\"\\+Inf\"} 1\\n.*$")); + } + + private Response whenPrometheusScraped() { + return RestAssured.given().port(container.getMappedPort(9090)).accept(OPENMETRICS_001).when().get("/metrics"); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(OtlpMetricsExportAutoConfiguration.class) + static class TestConfiguration { + + @Bean + Clock customClock() { + return Clock.SYSTEM; + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryTracingContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryTracingContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..6d8760f1faaf --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryTracingContainerConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.otlp; + +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingConnectionDetails; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OpenTelemetryTracingContainerConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class OpenTelemetryTracingContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final GenericContainer container = TestImage.OPENTELEMETRY.genericContainer().withExposedPorts(4318); + + @Autowired + private OtlpTracingConnectionDetails connectionDetails; + + @Test + void connectionCanBeMadeToOpenTelemetryContainer() { + assertThat(this.connectionDetails.getUrl()) + .isEqualTo("http://" + container.getHost() + ":" + container.getMappedPort(4318) + "/v1/traces"); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(OtlpAutoConfiguration.class) + static class TestConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/pulsar/PulsarContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/pulsar/PulsarContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..6ebe0b92c2bd --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/pulsar/PulsarContainerConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.pulsar; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import org.apache.pulsar.client.api.PulsarClientException; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.PulsarContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.pulsar.PulsarAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.pulsar.annotation.PulsarListener; +import org.springframework.pulsar.core.PulsarTemplate; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link PulsarContainerConnectionDetailsFactory}. + * + * @author Chris Bono + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +@TestPropertySource(properties = { "spring.pulsar.consumer.subscription.initial-position=earliest" }) +class PulsarContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + @SuppressWarnings("unused") + static final PulsarContainer pulsar = TestImage.container(PulsarContainer.class); + + @Autowired + private PulsarTemplate pulsarTemplate; + + @Autowired + private TestListener listener; + + @Test + void connectionCanBeMadeToPulsarContainer() throws PulsarClientException { + this.pulsarTemplate.send("test-topic", "test-data"); + Awaitility.waitAtMost(Duration.ofSeconds(30)) + .untilAsserted(() -> assertThat(this.listener.messages).containsExactly("test-data")); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(PulsarAutoConfiguration.class) + static class TestConfiguration { + + @Bean + TestListener testListener() { + return new TestListener(); + } + + } + + static class TestListener { + + private final List messages = new ArrayList<>(); + + @PulsarListener(topics = "test-topic") + void processMessage(String message) { + this.messages.add(message); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleFreeR2dbcContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleFreeR2dbcContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..2294e0877973 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleFreeR2dbcContainerConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.r2dbc; + +import java.time.Duration; + +import io.r2dbc.spi.ConnectionFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.OS; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.oracle.OracleContainer; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration; +import org.springframework.boot.jdbc.DatabaseDriver; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.boot.testsupport.junit.DisabledOnOs; +import org.springframework.context.annotation.Configuration; +import org.springframework.r2dbc.core.DatabaseClient; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OracleFreeR2dbcContainerConnectionDetailsFactory}. + * + * @author Andy Wilkinson + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "The Oracle image has no ARM support") +class OracleFreeR2dbcContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final OracleContainer oracle = TestImage.container(OracleContainer.class); + + @Autowired + ConnectionFactory connectionFactory; + + @Test + void connectionCanBeMadeToOracleContainer() { + Object result = DatabaseClient.create(this.connectionFactory) + .sql(DatabaseDriver.ORACLE.getValidationQuery()) + .map((row, metadata) -> row.get(0)) + .first() + .block(Duration.ofSeconds(30)); + assertThat(result).isEqualTo("Hello"); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(R2dbcAutoConfiguration.class) + static class TestConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleXeR2dbcContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleXeR2dbcContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..5f94aa689c0f --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleXeR2dbcContainerConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.r2dbc; + +import java.time.Duration; + +import io.r2dbc.spi.ConnectionFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.OS; +import org.testcontainers.containers.OracleContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration; +import org.springframework.boot.jdbc.DatabaseDriver; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.boot.testsupport.junit.DisabledOnOs; +import org.springframework.context.annotation.Configuration; +import org.springframework.r2dbc.core.DatabaseClient; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OracleXeR2dbcContainerConnectionDetailsFactory}. + * + * @author Andy Wilkinson + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "The Oracle image has no ARM support") +class OracleXeR2dbcContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final OracleContainer oracle = TestImage.container(OracleContainer.class); + + @Autowired + ConnectionFactory connectionFactory; + + @Test + void connectionCanBeMadeToOracleContainer() { + Object result = DatabaseClient.create(this.connectionFactory) + .sql(DatabaseDriver.ORACLE.getValidationQuery()) + .map((row, metadata) -> row.get(0)) + .first() + .block(Duration.ofSeconds(30)); + assertThat(result).isEqualTo("Hello"); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(R2dbcAutoConfiguration.class) + static class TestConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactoryTests.java similarity index 91% rename from spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactoryTests.java rename to spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactoryTests.java index 6bc97667ec00..29dfbbfb84fe 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactoryTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactoryTests.java @@ -25,7 +25,8 @@ import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.RedisContainer; +import org.springframework.boot.testsupport.container.RedisContainer; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; @@ -44,7 +45,7 @@ class RedisContainerConnectionDetailsFactoryTests { @Container @ServiceConnection - static final RedisContainer redis = new RedisContainer(); + static final RedisContainer redis = TestImage.container(RedisContainer.class); @Autowired(required = false) private RedisConnectionDetails connectionDetails; diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redis/RedisStackContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redis/RedisStackContainerConnectionDetailsFactoryTests.java new file mode 100644 index 000000000000..03bcabb7e5ed --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redis/RedisStackContainerConnectionDetailsFactoryTests.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.redis; + +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.RedisStackContainer; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link RedisContainerConnectionDetailsFactory}. + * + * @author Andy Wilkinson + * @author Eddú Meléndez + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class RedisStackContainerConnectionDetailsFactoryTests { + + @Container + @ServiceConnection + static final RedisStackContainer redis = TestImage.container(RedisStackContainer.class); + + @Autowired(required = false) + private RedisConnectionDetails connectionDetails; + + @Autowired + private RedisConnectionFactory connectionFactory; + + @Test + void connectionCanBeMadeToRedisContainer() { + assertThat(this.connectionDetails).isNotNull(); + try (RedisConnection connection = this.connectionFactory.getConnection()) { + assertThat(connection.commands().echo("Hello, World".getBytes())).isEqualTo("Hello, World".getBytes()); + } + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(RedisAutoConfiguration.class) + static class TestConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redis/RedisStackServerContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redis/RedisStackServerContainerConnectionDetailsFactoryTests.java new file mode 100644 index 000000000000..a3ebf8d7d8ce --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redis/RedisStackServerContainerConnectionDetailsFactoryTests.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.redis; + +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.RedisStackServerContainer; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link RedisContainerConnectionDetailsFactory}. + * + * @author Andy Wilkinson + * @author Eddú Meléndez + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class RedisStackServerContainerConnectionDetailsFactoryTests { + + @Container + @ServiceConnection + static final RedisStackServerContainer redis = TestImage.container(RedisStackServerContainer.class); + + @Autowired(required = false) + private RedisConnectionDetails connectionDetails; + + @Autowired + private RedisConnectionFactory connectionFactory; + + @Test + void connectionCanBeMadeToRedisContainer() { + assertThat(this.connectionDetails).isNotNull(); + try (RedisConnection connection = this.connectionFactory.getConnection()) { + assertThat(connection.commands().echo("Hello, World".getBytes())).isEqualTo("Hello, World".getBytes()); + } + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(RedisAutoConfiguration.class) + static class TestConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/redpanda/RedpandaContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redpanda/RedpandaContainerConnectionDetailsFactoryIntegrationTests.java similarity index 91% rename from spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/redpanda/RedpandaContainerConnectionDetailsFactoryIntegrationTests.java rename to spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redpanda/RedpandaContainerConnectionDetailsFactoryIntegrationTests.java index 292358da3e2b..1c26bc0295cd 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/redpanda/RedpandaContainerConnectionDetailsFactoryIntegrationTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redpanda/RedpandaContainerConnectionDetailsFactoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.kafka.annotation.KafkaListener; @@ -53,8 +53,7 @@ class RedpandaContainerConnectionDetailsFactoryIntegrationTests { @Container @ServiceConnection - static final RedpandaContainer redpanda = new RedpandaContainer(DockerImageNames.redpanda()) - .withStartupTimeout(Duration.ofMinutes(5)); + static final RedpandaContainer redpanda = TestImage.container(RedpandaContainer.class); @Autowired KafkaTemplate kafkaTemplate; diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/zipkin/ZipkinContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/zipkin/ZipkinContainerConnectionDetailsFactoryIntegrationTests.java similarity index 88% rename from spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/zipkin/ZipkinContainerConnectionDetailsFactoryIntegrationTests.java rename to spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/zipkin/ZipkinContainerConnectionDetailsFactoryIntegrationTests.java index 0c9a9d1abe75..62b6e12897ec 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/zipkin/ZipkinContainerConnectionDetailsFactoryIntegrationTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/zipkin/ZipkinContainerConnectionDetailsFactoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,8 @@ import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConnectionDetails; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.boot.testsupport.container.ZipkinContainer; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -44,7 +45,7 @@ class ZipkinContainerConnectionDetailsFactoryIntegrationTests { @Container @ServiceConnection - static final GenericContainer zipkin = new GenericContainer<>(DockerImageNames.zipkin()).withExposedPorts(9411); + static final GenericContainer zipkin = TestImage.container(ZipkinContainer.class); @Autowired(required = false) private ZipkinConnectionDetails connectionDetails; diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/resources/collector-config.yml b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/resources/collector-config.yml new file mode 100644 index 000000000000..c17a371d66c2 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/resources/collector-config.yml @@ -0,0 +1,20 @@ +receivers: + otlp: + protocols: + grpc: + http: + +exporters: + # https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/prometheusexporter + prometheus: + endpoint: '0.0.0.0:9090' + metric_expiration: 1m + enable_open_metrics: true + resource_to_telemetry_conversion: + enabled: true + +service: + pipelines: + metrics: + receivers: [otlp] + exporters: [prometheus] \ No newline at end of file diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/resources/db/changelog/db.changelog-master.yaml b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/resources/db/changelog/db.changelog-master.yaml similarity index 100% rename from spring-boot-project/spring-boot-testcontainers/src/test/resources/db/changelog/db.changelog-master.yaml rename to spring-boot-project/spring-boot-testcontainers/src/dockerTest/resources/db/changelog/db.changelog-master.yaml diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-cache/src/redisTest/resources/logback-test.xml b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/resources/logback-test.xml similarity index 100% rename from spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-cache/src/redisTest/resources/logback-test.xml rename to spring-boot-project/spring-boot-testcontainers/src/dockerTest/resources/logback-test.xml diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/resources/spring.properties b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/resources/spring.properties new file mode 100644 index 000000000000..47dff33f0bb5 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/resources/spring.properties @@ -0,0 +1 @@ +spring.test.context.cache.maxSize=1 \ No newline at end of file diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/ContainerFieldsImporter.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/ContainerFieldsImporter.java index ed12fd8de10b..95a8fce48ff3 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/ContainerFieldsImporter.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/ContainerFieldsImporter.java @@ -22,7 +22,6 @@ import java.util.List; import org.testcontainers.containers.Container; -import org.testcontainers.lifecycle.Startable; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.util.Assert; @@ -39,9 +38,6 @@ void registerBeanDefinitions(BeanDefinitionRegistry registry, Class definitio for (Field field : getContainerFields(definitionClass)) { assertValid(field); Container container = getContainer(field); - if (container instanceof Startable startable) { - startable.start(); - } registerBeanDefinition(registry, field, container); } } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/DynamicPropertySourceMethodsImporter.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/DynamicPropertySourceMethodsImporter.java index 35d72d7c7206..d680f7504c81 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/DynamicPropertySourceMethodsImporter.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/DynamicPropertySourceMethodsImporter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.lang.reflect.Modifier; import java.util.Set; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.boot.testcontainers.properties.TestcontainersPropertySource; import org.springframework.core.MethodIntrospector; import org.springframework.core.annotation.MergedAnnotations; @@ -43,16 +44,17 @@ class DynamicPropertySourceMethodsImporter { this.environment = environment; } - void registerDynamicPropertySources(Class definitionClass) { + void registerDynamicPropertySources(BeanDefinitionRegistry beanDefinitionRegistry, Class definitionClass) { Set methods = MethodIntrospector.selectMethods(definitionClass, this::isAnnotated); if (methods.isEmpty()) { return; } - DynamicPropertyRegistry registry = TestcontainersPropertySource.attach(this.environment); + DynamicPropertyRegistry dynamicPropertyRegistry = TestcontainersPropertySource.attach(this.environment, + beanDefinitionRegistry); methods.forEach((method) -> { assertValid(method); ReflectionUtils.makeAccessible(method); - ReflectionUtils.invokeMethod(method, null, registry); + ReflectionUtils.invokeMethod(method, null, dynamicPropertyRegistry); }); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/ImportTestcontainersRegistrar.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/ImportTestcontainersRegistrar.java index 9e4dc4c97dfb..1c2cf49725c3 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/ImportTestcontainersRegistrar.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/ImportTestcontainersRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,7 +62,7 @@ private void registerBeanDefinitions(BeanDefinitionRegistry registry, Class[] for (Class definitionClass : definitionClasses) { this.containerFieldsImporter.registerBeanDefinitions(registry, definitionClass); if (this.dynamicPropertySourceMethodsImporter != null) { - this.dynamicPropertySourceMethodsImporter.registerDynamicPropertySources(definitionClass); + this.dynamicPropertySourceMethodsImporter.registerDynamicPropertySources(registry, definitionClass); } } } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/BeforeTestcontainerUsedEvent.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/BeforeTestcontainerUsedEvent.java new file mode 100644 index 000000000000..c31963302fc1 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/BeforeTestcontainerUsedEvent.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.lifecycle; + +import org.testcontainers.containers.Container; + +import org.springframework.context.ApplicationEvent; + +/** + * Event published just before a Testcontainers {@link Container} is used. + * + * @author Andy Wilkinson + * @since 3.2.6 + */ +public class BeforeTestcontainerUsedEvent extends ApplicationEvent { + + public BeforeTestcontainerUsedEvent(Object source) { + super(source); + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializer.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializer.java index 087c4d71e605..662be02a7f47 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializer.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,11 @@ public void initialize(ConfigurableApplicationContext applicationContext) { } ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory(); applicationContext.addBeanFactoryPostProcessor(new TestcontainersLifecycleBeanFactoryPostProcessor()); - beanFactory.addBeanPostProcessor(new TestcontainersLifecycleBeanPostProcessor(beanFactory)); + TestcontainersStartup startup = TestcontainersStartup.get(applicationContext.getEnvironment()); + TestcontainersLifecycleBeanPostProcessor beanPostProcessor = new TestcontainersLifecycleBeanPostProcessor( + beanFactory, startup); + beanFactory.addBeanPostProcessor(beanPostProcessor); + applicationContext.addApplicationListener(beanPostProcessor); } } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleBeanPostProcessor.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleBeanPostProcessor.java index 42fbc68e7a29..e870be4cdb61 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleBeanPostProcessor.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleBeanPostProcessor.java @@ -16,9 +16,13 @@ package org.springframework.boot.testcontainers.lifecycle; +import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -35,6 +39,7 @@ import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; +import org.springframework.context.ApplicationListener; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.log.LogMessage; @@ -54,52 +59,108 @@ * @see TestcontainersLifecycleApplicationContextInitializer */ @Order(Ordered.LOWEST_PRECEDENCE) -class TestcontainersLifecycleBeanPostProcessor implements DestructionAwareBeanPostProcessor { +class TestcontainersLifecycleBeanPostProcessor + implements DestructionAwareBeanPostProcessor, ApplicationListener { private static final Log logger = LogFactory.getLog(TestcontainersLifecycleBeanPostProcessor.class); private final ConfigurableListableBeanFactory beanFactory; - private volatile boolean containersInitialized = false; + private final TestcontainersStartup startup; - TestcontainersLifecycleBeanPostProcessor(ConfigurableListableBeanFactory beanFactory) { + private final AtomicReference startables = new AtomicReference<>(Startables.UNSTARTED); + + private final AtomicBoolean containersInitialized = new AtomicBoolean(); + + TestcontainersLifecycleBeanPostProcessor(ConfigurableListableBeanFactory beanFactory, + TestcontainersStartup startup) { this.beanFactory = beanFactory; + this.startup = startup; + } + + @Override + public void onApplicationEvent(BeforeTestcontainerUsedEvent event) { + initializeContainers(); } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof Startable startable) { - startable.start(); - } if (this.beanFactory.isConfigurationFrozen()) { initializeContainers(); } + if (bean instanceof Startable startableBean) { + if (this.startables.compareAndExchange(Startables.UNSTARTED, Startables.STARTING) == Startables.UNSTARTED) { + initializeStartables(startableBean, beanName); + } + else if (this.startables.get() == Startables.STARTED) { + logger.trace(LogMessage.format("Starting container %s", beanName)); + startableBean.start(); + } + } return bean; } - private void initializeContainers() { - if (this.containersInitialized) { + private void initializeStartables(Startable startableBean, String startableBeanName) { + logger.trace(LogMessage.format("Initializing startables")); + List beanNames = new ArrayList<>(getBeanNames(Startable.class)); + beanNames.remove(startableBeanName); + List beans = getBeans(beanNames); + if (beans == null) { + logger.trace(LogMessage.format("Failed to obtain startables %s", beanNames)); + this.startables.set(Startables.UNSTARTED); return; } - this.containersInitialized = true; - Set beanNames = new LinkedHashSet<>(); - beanNames.addAll(List.of(this.beanFactory.getBeanNamesForType(ContainerState.class, false, false))); - beanNames.addAll(List.of(this.beanFactory.getBeanNamesForType(Startable.class, false, false))); + beanNames.add(startableBeanName); + beans.add(startableBean); + logger.trace(LogMessage.format("Starting startables %s", beanNames)); + start(beans); + this.startables.set(Startables.STARTED); + if (!beanNames.isEmpty()) { + logger.debug(LogMessage.format("Initialized and started startable beans '%s'", beanNames)); + } + } + + private void start(List beans) { + Set startables = beans.stream() + .filter(Startable.class::isInstance) + .map(Startable.class::cast) + .collect(Collectors.toCollection(LinkedHashSet::new)); + this.startup.start(startables); + } + + private void initializeContainers() { + if (this.containersInitialized.compareAndSet(false, true)) { + logger.trace("Initializing containers"); + List beanNames = getBeanNames(ContainerState.class); + List beans = getBeans(beanNames); + if (beans != null) { + logger.trace(LogMessage.format("Initialized containers %s", beanNames)); + } + else { + logger.trace(LogMessage.format("Failed to initialize containers %s", beanNames)); + this.containersInitialized.set(false); + } + } + } + + private List getBeanNames(Class type) { + return List.of(this.beanFactory.getBeanNamesForType(type, true, false)); + } + + private List getBeans(List beanNames) { + List beans = new ArrayList<>(beanNames.size()); for (String beanName : beanNames) { try { - this.beanFactory.getBean(beanName); + beans.add(this.beanFactory.getBean(beanName)); } catch (BeanCreationException ex) { if (ex.contains(BeanCurrentlyInCreationException.class)) { - this.containersInitialized = false; - return; + return null; } throw ex; } } - if (!beanNames.isEmpty()) { - logger.debug(LogMessage.format("Initialized container beans '%s'", beanNames)); - } + return beans; } @Override @@ -130,4 +191,10 @@ private boolean isReusedContainer(Object bean) { && TestcontainersConfiguration.getInstance().environmentSupportsReuse(); } + enum Startables { + + UNSTARTED, STARTING, STARTED + + } + } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersStartup.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersStartup.java new file mode 100644 index 000000000000..00009a07fa6c --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersStartup.java @@ -0,0 +1,94 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.lifecycle; + +import java.util.Collection; + +import org.testcontainers.lifecycle.Startable; +import org.testcontainers.lifecycle.Startables; + +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; + +/** + * Testcontainers startup strategies. The strategy to use can be configured in the Spring + * {@link Environment} with a {@value #PROPERTY} property. + * + * @author Phillip Webb + * @since 3.2.0 + */ +public enum TestcontainersStartup { + + /** + * Startup containers sequentially. + */ + SEQUENTIAL { + + @Override + void start(Collection startables) { + startables.forEach(Startable::start); + } + + }, + + /** + * Startup containers in parallel. + */ + PARALLEL { + + @Override + void start(Collection startables) { + Startables.deepStart(startables).join(); + } + + }; + + /** + * The {@link Environment} property used to change the {@link TestcontainersStartup} + * strategy. + */ + public static final String PROPERTY = "spring.testcontainers.beans.startup"; + + abstract void start(Collection startables); + + static TestcontainersStartup get(ConfigurableEnvironment environment) { + return get((environment != null) ? environment.getProperty(PROPERTY) : null); + } + + private static TestcontainersStartup get(String value) { + if (value == null) { + return SEQUENTIAL; + } + String canonicalName = getCanonicalName(value); + for (TestcontainersStartup candidate : values()) { + if (candidate.name().equalsIgnoreCase(canonicalName)) { + return candidate; + } + } + throw new IllegalArgumentException("Unknown '%s' property value '%s'".formatted(PROPERTY, value)); + } + + private static String getCanonicalName(String name) { + StringBuilder canonicalName = new StringBuilder(name.length()); + name.chars() + .filter(Character::isLetterOrDigit) + .map(Character::toLowerCase) + .forEach((c) -> canonicalName.append((char) c)); + return canonicalName.toString(); + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySource.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySource.java index 57a3aa83547c..ee2ae41359bd 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySource.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySource.java @@ -19,17 +19,29 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.Supplier; import org.testcontainers.containers.Container; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.testcontainers.lifecycle.BeforeTestcontainerUsedEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.Environment; +import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; +import org.springframework.util.function.SupplierUtils; /** * {@link EnumerablePropertySource} backed by a map with values supplied from one or more @@ -38,12 +50,14 @@ * @author Phillip Webb * @since 3.1.0 */ -public class TestcontainersPropertySource extends EnumerablePropertySource>> { +public class TestcontainersPropertySource extends MapPropertySource { static final String NAME = "testcontainersPropertySource"; private final DynamicPropertyRegistry registry; + private final Set eventPublishers = new CopyOnWriteArraySet<>(); + TestcontainersPropertySource() { this(Collections.synchronizedMap(new LinkedHashMap<>())); } @@ -57,37 +71,90 @@ private TestcontainersPropertySource(Map> valueSupplier }; } - @Override - public Object getProperty(String name) { - Supplier valueSupplier = this.source.get(name); - return (valueSupplier != null) ? valueSupplier.get() : null; + private void addEventPublisher(ApplicationEventPublisher eventPublisher) { + this.eventPublishers.add(eventPublisher); } @Override - public boolean containsProperty(String name) { - return this.source.containsKey(name); + public Object getProperty(String name) { + Object valueSupplier = this.source.get(name); + return (valueSupplier != null) ? getProperty(name, valueSupplier) : null; } - @Override - public String[] getPropertyNames() { - return StringUtils.toStringArray(this.source.keySet()); + private Object getProperty(String name, Object valueSupplier) { + BeforeTestcontainerUsedEvent event = new BeforeTestcontainerUsedEvent(this); + this.eventPublishers.forEach((eventPublisher) -> eventPublisher.publishEvent(event)); + return SupplierUtils.resolve(valueSupplier); } public static DynamicPropertyRegistry attach(Environment environment) { + return attach(environment, null); + } + + static DynamicPropertyRegistry attach(ConfigurableApplicationContext applicationContext) { + return attach(applicationContext.getEnvironment(), applicationContext, null); + } + + public static DynamicPropertyRegistry attach(Environment environment, BeanDefinitionRegistry registry) { + return attach(environment, null, registry); + } + + private static DynamicPropertyRegistry attach(Environment environment, ApplicationEventPublisher eventPublisher, + BeanDefinitionRegistry registry) { Assert.state(environment instanceof ConfigurableEnvironment, "TestcontainersPropertySource can only be attached to a ConfigurableEnvironment"); - return attach((ConfigurableEnvironment) environment); + TestcontainersPropertySource propertySource = getOrAdd((ConfigurableEnvironment) environment); + if (eventPublisher != null) { + propertySource.addEventPublisher(eventPublisher); + } + else if (registry != null && !registry.containsBeanDefinition(EventPublisherRegistrar.NAME)) { + registry.registerBeanDefinition(EventPublisherRegistrar.NAME, new RootBeanDefinition( + EventPublisherRegistrar.class, () -> new EventPublisherRegistrar(environment))); + } + return propertySource.registry; } - private static DynamicPropertyRegistry attach(ConfigurableEnvironment environment) { + static TestcontainersPropertySource getOrAdd(ConfigurableEnvironment environment) { PropertySource propertySource = environment.getPropertySources().get(NAME); if (propertySource == null) { environment.getPropertySources().addFirst(new TestcontainersPropertySource()); - return attach(environment); + return getOrAdd(environment); } Assert.state(propertySource instanceof TestcontainersPropertySource, "Incorrect TestcontainersPropertySource type registered"); - return ((TestcontainersPropertySource) propertySource).registry; + return ((TestcontainersPropertySource) propertySource); + } + + /** + * {@link BeanFactoryPostProcessor} to register the {@link ApplicationEventPublisher} + * to the {@link TestcontainersPropertySource}. This class is a + * {@link BeanFactoryPostProcessor} so that it is initialized as early as possible. + */ + static class EventPublisherRegistrar implements BeanFactoryPostProcessor, ApplicationEventPublisherAware { + + static final String NAME = EventPublisherRegistrar.class.getName(); + + private final Environment environment; + + private ApplicationEventPublisher eventPublisher; + + EventPublisherRegistrar(Environment environment) { + this.environment = environment; + } + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) { + this.eventPublisher = eventPublisher; + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + if (this.eventPublisher != null) { + TestcontainersPropertySource.getOrAdd((ConfigurableEnvironment) this.environment) + .addEventPublisher(this.eventPublisher); + } + } + } } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfiguration.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfiguration.java index 5c016c964e5f..e6219f2d2d4e 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfiguration.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,12 @@ package org.springframework.boot.testcontainers.properties; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; -import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.test.context.DynamicPropertyRegistry; /** @@ -28,6 +31,8 @@ * @author Phillip Webb * @since 3.1.0 */ +@AutoConfiguration +@Order(Ordered.HIGHEST_PRECEDENCE) @ConditionalOnClass(DynamicPropertyRegistry.class) public class TestcontainersPropertySourceAutoConfiguration { @@ -35,8 +40,8 @@ public class TestcontainersPropertySourceAutoConfiguration { } @Bean - DynamicPropertyRegistry dynamicPropertyRegistry(ConfigurableEnvironment environment) { - return TestcontainersPropertySource.attach(environment); + static DynamicPropertyRegistry dynamicPropertyRegistry(ConfigurableApplicationContext applicationContext) { + return TestcontainersPropertySource.attach(applicationContext); } } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactory.java index d2edbd9db575..1e8e0d24ac77 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactory.java @@ -17,6 +17,7 @@ package org.springframework.boot.testcontainers.service.connection; import java.util.Arrays; +import java.util.List; import java.util.stream.Stream; import org.apache.commons.logging.Log; @@ -25,11 +26,16 @@ import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory; import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.OriginProvider; +import org.springframework.boot.testcontainers.lifecycle.BeforeTestcontainerUsedEvent; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.core.ResolvableType; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader.FailureHandler; @@ -56,7 +62,7 @@ public abstract class ContainerConnectionDetailsFactory, */ protected static final String ANY_CONNECTION_NAME = null; - private final String connectionName; + private final List connectionNames; private final String[] requiredClassNames; @@ -75,7 +81,19 @@ protected ContainerConnectionDetailsFactory() { * @param requiredClassNames the names of classes that must be present */ protected ContainerConnectionDetailsFactory(String connectionName, String... requiredClassNames) { - this.connectionName = connectionName; + this(Arrays.asList(connectionName), requiredClassNames); + } + + /** + * Create a new {@link ContainerConnectionDetailsFactory} instance with the given + * supported connection names. + * @param connectionNames the supported connection names + * @param requiredClassNames the names of classes that must be present + * @since 3.4.0 + */ + protected ContainerConnectionDetailsFactory(List connectionNames, String... requiredClassNames) { + Assert.notEmpty(connectionNames, "ConnectionNames must contain at least one name"); + this.connectionNames = connectionNames; this.requiredClassNames = requiredClassNames; } @@ -88,8 +106,10 @@ public final D getConnectionDetails(ContainerConnectionSource source) { Class[] generics = resolveGenerics(); Class containerType = generics[0]; Class connectionDetailsType = generics[1]; - if (source.accepts(this.connectionName, containerType, connectionDetailsType)) { - return getContainerConnectionDetails(source); + for (String connectionName : this.connectionNames) { + if (source.accepts(connectionName, containerType, connectionDetailsType)) { + return getContainerConnectionDetails(source); + } } } catch (NoClassDefFoundError ex) { @@ -123,10 +143,12 @@ private Class[] resolveGenerics() { * @param the container type */ protected static class ContainerConnectionDetails> - implements ConnectionDetails, OriginProvider, InitializingBean { + implements ConnectionDetails, OriginProvider, InitializingBean, ApplicationContextAware { private final ContainerConnectionSource source; + private volatile ApplicationEventPublisher eventPublisher; + private volatile C container; /** @@ -151,6 +173,7 @@ public void afterPropertiesSet() throws Exception { protected final C getContainer() { Assert.state(this.container != null, "Container cannot be obtained before the connection details bean has been initialized"); + this.eventPublisher.publishEvent(new BeforeTestcontainerUsedEvent(this)); return this.container; } @@ -159,6 +182,11 @@ public Origin getOrigin() { return this.source.getOrigin(); } + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.eventPublisher = applicationContext; + } + } static class ContainerConnectionDetailsFactoriesRuntimeHints implements RuntimeHintsRegistrar { diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionAutoConfigurationRegistrar.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionAutoConfigurationRegistrar.java index 8e9be4c32239..ae6c9b10ea43 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionAutoConfigurationRegistrar.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionAutoConfigurationRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,8 +68,8 @@ private void registerBeanDefinitions(ConfigurableListableBeanFactory beanFactory private Set getAnnotations(ConfigurableListableBeanFactory beanFactory, String beanName, BeanDefinition beanDefinition) { - Set annotations = new LinkedHashSet<>(); - annotations.addAll(beanFactory.findAllAnnotationsOnBean(beanName, ServiceConnection.class, false)); + Set annotations = new LinkedHashSet<>( + beanFactory.findAllAnnotationsOnBean(beanName, ServiceConnection.class, false)); if (beanDefinition instanceof TestcontainerBeanDefinition testcontainerBeanDefinition) { testcontainerBeanDefinition.getAnnotations() .stream(ServiceConnection.class) diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQClassicContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQClassicContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..8c9904bc5d09 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQClassicContainerConnectionDetailsFactory.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.activemq; + +import org.testcontainers.activemq.ActiveMQContainer; + +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +/** + * {@link ContainerConnectionDetailsFactory} to create {@link ActiveMQConnectionDetails} * + * from a {@link ServiceConnection @ServiceConnection}-annotated + * {@link ActiveMQContainer}. + * + * @author Eddú Meléndez + */ +class ActiveMQClassicContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + @Override + protected ActiveMQConnectionDetails getContainerConnectionDetails( + ContainerConnectionSource source) { + return new ActiveMQContainerConnectionDetails(source); + } + + private static final class ActiveMQContainerConnectionDetails extends ContainerConnectionDetails + implements ActiveMQConnectionDetails { + + private ActiveMQContainerConnectionDetails(ContainerConnectionSource source) { + super(source); + } + + @Override + public String getBrokerUrl() { + return getContainer().getBrokerUrl(); + } + + @Override + public String getUser() { + return getContainer().getUser(); + } + + @Override + public String getPassword() { + return getContainer().getPassword(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..82324da487ee --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactory.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.activemq; + +import org.testcontainers.containers.Container; +import org.testcontainers.containers.GenericContainer; + +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +/** + * {@link ContainerConnectionDetailsFactory} to create {@link ActiveMQConnectionDetails} + * from a {@link ServiceConnection @ServiceConnection}-annotated {@link GenericContainer} + * using the {@code "symptoma/activemq"} image. + * + * @author Eddú Meléndez + */ +class ActiveMQContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory, ActiveMQConnectionDetails> { + + ActiveMQContainerConnectionDetailsFactory() { + super("symptoma/activemq"); + } + + @Override + protected ActiveMQConnectionDetails getContainerConnectionDetails(ContainerConnectionSource> source) { + return new ActiveMQContainerConnectionDetails(source); + } + + private static final class ActiveMQContainerConnectionDetails extends ContainerConnectionDetails> + implements ActiveMQConnectionDetails { + + private ActiveMQContainerConnectionDetails(ContainerConnectionSource> source) { + super(source); + } + + @Override + public String getBrokerUrl() { + return "tcp://" + getContainer().getHost() + ":" + getContainer().getFirstMappedPort(); + } + + @Override + public String getUser() { + return getContainer().getEnvMap().get("ACTIVEMQ_USERNAME"); + } + + @Override + public String getPassword() { + return getContainer().getEnvMap().get("ACTIVEMQ_PASSWORD"); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ArtemisContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ArtemisContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..798ff7d97f85 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ArtemisContainerConnectionDetailsFactory.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.activemq; + +import org.testcontainers.activemq.ArtemisContainer; + +import org.springframework.boot.autoconfigure.jms.artemis.ArtemisConnectionDetails; +import org.springframework.boot.autoconfigure.jms.artemis.ArtemisMode; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +/** + * {@link ContainerConnectionDetailsFactory} to create {@link ArtemisConnectionDetails} + * from a {@link ServiceConnection @ServiceConnection}-annotated {@link ArtemisContainer}. + * + * @author Eddú Meléndez + */ +class ArtemisContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + @Override + protected ArtemisConnectionDetails getContainerConnectionDetails( + ContainerConnectionSource source) { + return new ArtemisContainerConnectionDetails(source); + } + + private static final class ArtemisContainerConnectionDetails extends ContainerConnectionDetails + implements ArtemisConnectionDetails { + + private ArtemisContainerConnectionDetails(ContainerConnectionSource source) { + super(source); + } + + @Override + public ArtemisMode getMode() { + return ArtemisMode.NATIVE; + } + + @Override + public String getBrokerUrl() { + return getContainer().getBrokerUrl(); + } + + @Override + public String getUser() { + return getContainer().getUser(); + } + + @Override + public String getPassword() { + return getContainer().getPassword(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/package-info.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/package-info.java new file mode 100644 index 000000000000..0981f1bf9b21 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Support for testcontainers ActiveMQ service connections. + */ +package org.springframework.boot.testcontainers.service.connection.activemq; diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/ApacheKafkaContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/ApacheKafkaContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..5815e75ac82a --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/ApacheKafkaContainerConnectionDetailsFactory.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.kafka; + +import java.util.List; + +import org.testcontainers.kafka.KafkaContainer; + +import org.springframework.boot.autoconfigure.kafka.KafkaConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +/** + * {@link ContainerConnectionDetailsFactory} to create {@link KafkaConnectionDetails} from + * a {@link ServiceConnection @ServiceConnection}-annotated {@link KafkaContainer}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + * @author Eddú Meléndez + */ +class ApacheKafkaContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + @Override + protected KafkaConnectionDetails getContainerConnectionDetails(ContainerConnectionSource source) { + return new ApacheKafkaContainerConnectionDetails(source); + } + + /** + * {@link KafkaConnectionDetails} backed by a {@link ContainerConnectionSource}. + */ + private static final class ApacheKafkaContainerConnectionDetails extends ContainerConnectionDetails + implements KafkaConnectionDetails { + + private ApacheKafkaContainerConnectionDetails(ContainerConnectionSource source) { + super(source); + } + + @Override + public List getBootstrapServers() { + return List.of(getContainer().getBootstrapServers()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/ConfluentKafkaContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/ConfluentKafkaContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..33bf8be95e7e --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/ConfluentKafkaContainerConnectionDetailsFactory.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.kafka; + +import java.util.List; + +import org.testcontainers.containers.KafkaContainer; + +import org.springframework.boot.autoconfigure.kafka.KafkaConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +/** + * {@link ContainerConnectionDetailsFactory} to create {@link KafkaConnectionDetails} from + * a {@link ServiceConnection @ServiceConnection}-annotated {@link KafkaContainer}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + */ +class ConfluentKafkaContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + @Override + protected KafkaConnectionDetails getContainerConnectionDetails(ContainerConnectionSource source) { + return new ConfluentKafkaContainerConnectionDetails(source); + } + + /** + * {@link KafkaConnectionDetails} backed by a {@link ContainerConnectionSource}. + */ + private static final class ConfluentKafkaContainerConnectionDetails + extends ContainerConnectionDetails implements KafkaConnectionDetails { + + private ConfluentKafkaContainerConnectionDetails(ContainerConnectionSource source) { + super(source); + } + + @Override + public List getBootstrapServers() { + return List.of(getContainer().getBootstrapServers()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaContainerConnectionDetailsFactory.java deleted file mode 100644 index 1f7b7f89a222..000000000000 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaContainerConnectionDetailsFactory.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.testcontainers.service.connection.kafka; - -import java.util.List; - -import org.testcontainers.containers.KafkaContainer; - -import org.springframework.boot.autoconfigure.kafka.KafkaConnectionDetails; -import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; -import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; - -/** - * {@link ContainerConnectionDetailsFactory} to create {@link KafkaConnectionDetails} from - * a {@link ServiceConnection @ServiceConnection}-annotated {@link KafkaContainer}. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - */ -class KafkaContainerConnectionDetailsFactory - extends ContainerConnectionDetailsFactory { - - @Override - protected KafkaConnectionDetails getContainerConnectionDetails(ContainerConnectionSource source) { - return new KafkaContainerConnectionDetails(source); - } - - /** - * {@link KafkaConnectionDetails} backed by a {@link ContainerConnectionSource}. - */ - private static final class KafkaContainerConnectionDetails extends ContainerConnectionDetails - implements KafkaConnectionDetails { - - private KafkaContainerConnectionDetails(ContainerConnectionSource source) { - super(source); - } - - @Override - public List getBootstrapServers() { - return List.of(getContainer().getBootstrapServers()); - } - - } - -} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ldap/OpenLdapContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ldap/OpenLdapContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..fd752811cdde --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ldap/OpenLdapContainerConnectionDetailsFactory.java @@ -0,0 +1,89 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.ldap; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +import org.testcontainers.containers.Container; +import org.testcontainers.containers.GenericContainer; + +import org.springframework.boot.autoconfigure.ldap.LdapConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +/** + * {@link ContainerConnectionDetailsFactory} to create {@link LdapConnectionDetails} from + * a {@link ServiceConnection @ServiceConnection}-annotated {@link GenericContainer} using + * the {@code "osixia/openldap"} image. + * + * @author Philipp Kessler + */ +class OpenLdapContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory, LdapConnectionDetails> { + + OpenLdapContainerConnectionDetailsFactory() { + super("osixia/openldap"); + } + + @Override + protected LdapConnectionDetails getContainerConnectionDetails(ContainerConnectionSource> source) { + return new OpenLdapContainerConnectionDetails(source); + } + + private static final class OpenLdapContainerConnectionDetails extends ContainerConnectionDetails> + implements LdapConnectionDetails { + + private OpenLdapContainerConnectionDetails(ContainerConnectionSource> source) { + super(source); + } + + @Override + public String[] getUrls() { + Map env = getContainer().getEnvMap(); + boolean usesTls = Boolean.parseBoolean(env.getOrDefault("LDAP_TLS", "true")); + String ldapPort = usesTls ? env.getOrDefault("LDAPS_PORT", "636") : env.getOrDefault("LDAP_PORT", "389"); + return new String[] { "%s://%s:%d".formatted(usesTls ? "ldaps" : "ldap", getContainer().getHost(), + getContainer().getMappedPort(Integer.parseInt(ldapPort))) }; + } + + @Override + public String getBase() { + Map env = getContainer().getEnvMap(); + if (env.containsKey("LDAP_BASE_DN")) { + return env.get("LDAP_BASE_DN"); + } + return Arrays.stream(env.getOrDefault("LDAP_DOMAIN", "example.org").split("\\.")) + .map("dc=%s"::formatted) + .collect(Collectors.joining(",")); + } + + @Override + public String getUsername() { + return "cn=admin,%s".formatted(getBase()); + } + + @Override + public String getPassword() { + return getContainer().getEnvMap().getOrDefault("LDAP_ADMIN_PASSWORD", "admin"); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ldap/package-info.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ldap/package-info.java new file mode 100644 index 000000000000..0de36b546985 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ldap/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Support for testcontainers Ldap service connections. + */ +package org.springframework.boot.testcontainers.service.connection.ldap; diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryMetricsContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryMetricsContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..8dd417ccd1b7 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryMetricsContainerConnectionDetailsFactory.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.otlp; + +import org.testcontainers.containers.Container; +import org.testcontainers.containers.GenericContainer; + +import org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +/** + * {@link ContainerConnectionDetailsFactory} to create + * {@link OtlpMetricsConnectionDetails} from a + * {@link ServiceConnection @ServiceConnection}-annotated {@link GenericContainer} using + * the {@code "otel/opentelemetry-collector-contrib"} image. + * + * @author Eddú Meléndez + */ +class OpenTelemetryMetricsContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory, OtlpMetricsConnectionDetails> { + + OpenTelemetryMetricsContainerConnectionDetailsFactory() { + super("otel/opentelemetry-collector-contrib", + "org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsExportAutoConfiguration"); + } + + @Override + protected OtlpMetricsConnectionDetails getContainerConnectionDetails( + ContainerConnectionSource> source) { + return new OpenTelemetryMetricsContainerConnectionDetails(source); + } + + private static final class OpenTelemetryMetricsContainerConnectionDetails + extends ContainerConnectionDetails> implements OtlpMetricsConnectionDetails { + + private OpenTelemetryMetricsContainerConnectionDetails(ContainerConnectionSource> source) { + super(source); + } + + @Override + public String getUrl() { + return "http://%s:%d/v1/metrics".formatted(getContainer().getHost(), getContainer().getMappedPort(4318)); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryTracingContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryTracingContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..6c3e72ac797c --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryTracingContainerConnectionDetailsFactory.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.otlp; + +import org.testcontainers.containers.Container; +import org.testcontainers.containers.GenericContainer; + +import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +/** + * {@link ContainerConnectionDetailsFactory} to create + * {@link OtlpTracingConnectionDetails} from a + * {@link ServiceConnection @ServiceConnection}-annotated {@link GenericContainer} using + * the {@code "otel/opentelemetry-collector-contrib"} image. + * + * @author Eddú Meléndez + */ +class OpenTelemetryTracingContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory, OtlpTracingConnectionDetails> { + + OpenTelemetryTracingContainerConnectionDetailsFactory() { + super("otel/opentelemetry-collector-contrib", + "org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpAutoConfiguration"); + } + + @Override + protected OtlpTracingConnectionDetails getContainerConnectionDetails( + ContainerConnectionSource> source) { + return new OpenTelemetryTracingContainerConnectionDetails(source); + } + + private static final class OpenTelemetryTracingContainerConnectionDetails + extends ContainerConnectionDetails> implements OtlpTracingConnectionDetails { + + private OpenTelemetryTracingContainerConnectionDetails(ContainerConnectionSource> source) { + super(source); + } + + @Override + public String getUrl() { + return "http://%s:%d/v1/traces".formatted(getContainer().getHost(), getContainer().getMappedPort(4318)); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/package-info.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/package-info.java new file mode 100644 index 000000000000..59b4a9dce0a2 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Support for testcontainers OpenTelemetry service connections. + */ +package org.springframework.boot.testcontainers.service.connection.otlp; diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/pulsar/PulsarContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/pulsar/PulsarContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..836c1a127d23 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/pulsar/PulsarContainerConnectionDetailsFactory.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.pulsar; + +import org.testcontainers.containers.PulsarContainer; + +import org.springframework.boot.autoconfigure.pulsar.PulsarConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +/** + * {@link ContainerConnectionDetailsFactory} to create {@link PulsarConnectionDetails} + * from a {@link ServiceConnection @ServiceConnection}-annotated {@link PulsarContainer}. + * + * @author Chris Bono + */ +class PulsarContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + @Override + protected PulsarConnectionDetails getContainerConnectionDetails(ContainerConnectionSource source) { + return new PulsarContainerConnectionDetails(source); + } + + /** + * {@link PulsarConnectionDetails} backed by a {@link ContainerConnectionSource}. + */ + private static final class PulsarContainerConnectionDetails extends ContainerConnectionDetails + implements PulsarConnectionDetails { + + private PulsarContainerConnectionDetails(ContainerConnectionSource source) { + super(source); + } + + @Override + public String getBrokerUrl() { + return getContainer().getPulsarBrokerUrl(); + } + + @Override + public String getAdminUrl() { + return getContainer().getHttpServiceUrl(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/pulsar/package-info.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/pulsar/package-info.java new file mode 100644 index 000000000000..4938ad863134 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/pulsar/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Support for testcontainers Pulsar service connections. + */ +package org.springframework.boot.testcontainers.service.connection.pulsar; diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleFreeR2dbcContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleFreeR2dbcContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..09e381faa0fd --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleFreeR2dbcContainerConnectionDetailsFactory.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.r2dbc; + +import io.r2dbc.spi.ConnectionFactoryOptions; +import org.testcontainers.oracle.OracleContainer; +import org.testcontainers.oracle.OracleR2DBCDatabaseContainer; + +import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +/** + * {@link ContainerConnectionDetailsFactory} to create {@link R2dbcConnectionDetails} from + * a {@link ServiceConnection @ServiceConnection}-annotated {@link OracleContainer}. + * + * @author Eddú Meléndez + */ +class OracleFreeR2dbcContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + OracleFreeR2dbcContainerConnectionDetailsFactory() { + super(ANY_CONNECTION_NAME, "io.r2dbc.spi.ConnectionFactoryOptions"); + } + + @Override + public R2dbcConnectionDetails getContainerConnectionDetails(ContainerConnectionSource source) { + return new R2dbcDatabaseContainerConnectionDetails(source); + } + + /** + * {@link R2dbcConnectionDetails} backed by a {@link ContainerConnectionSource}. + */ + private static final class R2dbcDatabaseContainerConnectionDetails + extends ContainerConnectionDetails implements R2dbcConnectionDetails { + + private R2dbcDatabaseContainerConnectionDetails(ContainerConnectionSource source) { + super(source); + } + + @Override + public ConnectionFactoryOptions getConnectionFactoryOptions() { + return OracleR2DBCDatabaseContainer.getOptions(getContainer()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleR2dbcContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleR2dbcContainerConnectionDetailsFactory.java deleted file mode 100644 index 783e83a77115..000000000000 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleR2dbcContainerConnectionDetailsFactory.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.testcontainers.service.connection.r2dbc; - -import io.r2dbc.spi.ConnectionFactoryOptions; -import org.testcontainers.containers.OracleContainer; -import org.testcontainers.containers.OracleR2DBCDatabaseContainer; - -import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; -import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; -import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; - -/** - * {@link ContainerConnectionDetailsFactory} to create {@link R2dbcConnectionDetails} from - * a {@link ServiceConnection @ServiceConnection}-annotated {@link OracleContainer}. - * - * @author Eddú Meléndez - */ -class OracleR2dbcContainerConnectionDetailsFactory - extends ContainerConnectionDetailsFactory { - - OracleR2dbcContainerConnectionDetailsFactory() { - super(ANY_CONNECTION_NAME, "io.r2dbc.spi.ConnectionFactoryOptions"); - } - - @Override - public R2dbcConnectionDetails getContainerConnectionDetails(ContainerConnectionSource source) { - return new R2dbcDatabaseContainerConnectionDetails(source); - } - - /** - * {@link R2dbcConnectionDetails} backed by a {@link ContainerConnectionSource}. - */ - private static final class R2dbcDatabaseContainerConnectionDetails - extends ContainerConnectionDetails implements R2dbcConnectionDetails { - - private R2dbcDatabaseContainerConnectionDetails(ContainerConnectionSource source) { - super(source); - } - - @Override - public ConnectionFactoryOptions getConnectionFactoryOptions() { - return OracleR2DBCDatabaseContainer.getOptions(getContainer()); - } - - } - -} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleXeR2dbcContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleXeR2dbcContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..a5f21c796a46 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleXeR2dbcContainerConnectionDetailsFactory.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.r2dbc; + +import io.r2dbc.spi.ConnectionFactoryOptions; +import org.testcontainers.containers.OracleContainer; +import org.testcontainers.containers.OracleR2DBCDatabaseContainer; + +import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +/** + * {@link ContainerConnectionDetailsFactory} to create {@link R2dbcConnectionDetails} from + * a {@link ServiceConnection @ServiceConnection}-annotated {@link OracleContainer}. + * + * @author Eddú Meléndez + */ +class OracleXeR2dbcContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + OracleXeR2dbcContainerConnectionDetailsFactory() { + super(ANY_CONNECTION_NAME, "io.r2dbc.spi.ConnectionFactoryOptions"); + } + + @Override + public R2dbcConnectionDetails getContainerConnectionDetails(ContainerConnectionSource source) { + return new R2dbcDatabaseContainerConnectionDetails(source); + } + + /** + * {@link R2dbcConnectionDetails} backed by a {@link ContainerConnectionSource}. + */ + private static final class R2dbcDatabaseContainerConnectionDetails + extends ContainerConnectionDetails implements R2dbcConnectionDetails { + + private R2dbcDatabaseContainerConnectionDetails(ContainerConnectionSource source) { + super(source); + } + + @Override + public ConnectionFactoryOptions getConnectionFactoryOptions() { + return OracleR2DBCDatabaseContainer.getOptions(getContainer()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactory.java index 4b13a527de6d..5d10886d6961 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.boot.testcontainers.service.connection.redis; +import java.util.List; + import org.testcontainers.containers.Container; import org.testcontainers.containers.GenericContainer; @@ -32,12 +34,18 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Eddú Meléndez */ class RedisContainerConnectionDetailsFactory extends ContainerConnectionDetailsFactory, RedisConnectionDetails> { + private static final List REDIS_IMAGE_NAMES = List.of("redis", "bitnami/redis", "redis/redis-stack", + "redis/redis-stack-server"); + + private static final int REDIS_PORT = 6379; + RedisContainerConnectionDetailsFactory() { - super("redis"); + super(REDIS_IMAGE_NAMES); } @Override @@ -57,7 +65,7 @@ private RedisContainerConnectionDetails(ContainerConnectionSource> @Override public Standalone getStandalone() { - return Standalone.of(getContainer().getHost(), getContainer().getFirstMappedPort()); + return Standalone.of(getContainer().getHost(), getContainer().getMappedPort(REDIS_PORT)); } } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 000000000000..ca82e22875ba --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,10 @@ +{ + "properties": [ + { + "name": "spring.testcontainers.beans.startup", + "type": "org.springframework.boot.testcontainers.lifecycle.TestcontainersStartup", + "description": "Testcontainers startup modes.", + "defaultValue": "sequential" + } + ] +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories index 2cfe37359c38..813e438b31f6 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories @@ -8,19 +8,28 @@ org.springframework.boot.testcontainers.service.connection.ServiceConnectionCont # Connection Details Factories org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\ +org.springframework.boot.testcontainers.service.connection.activemq.ActiveMQClassicContainerConnectionDetailsFactory,\ +org.springframework.boot.testcontainers.service.connection.activemq.ActiveMQContainerConnectionDetailsFactory,\ +org.springframework.boot.testcontainers.service.connection.activemq.ArtemisContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.amqp.RabbitContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.cassandra.CassandraContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.couchbase.CouchbaseContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.flyway.FlywayContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.elasticsearch.ElasticsearchContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.jdbc.JdbcContainerConnectionDetailsFactory,\ -org.springframework.boot.testcontainers.service.connection.kafka.KafkaContainerConnectionDetailsFactory,\ +org.springframework.boot.testcontainers.service.connection.kafka.ApacheKafkaContainerConnectionDetailsFactory,\ +org.springframework.boot.testcontainers.service.connection.kafka.ConfluentKafkaContainerConnectionDetailsFactory,\ +org.springframework.boot.testcontainers.service.connection.ldap.OpenLdapContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.liquibase.LiquibaseContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.mongo.MongoContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.neo4j.Neo4jContainerConnectionDetailsFactory,\ +org.springframework.boot.testcontainers.service.connection.otlp.OpenTelemetryMetricsContainerConnectionDetailsFactory,\ +org.springframework.boot.testcontainers.service.connection.otlp.OpenTelemetryTracingContainerConnectionDetailsFactory,\ +org.springframework.boot.testcontainers.service.connection.pulsar.PulsarContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.r2dbc.MariaDbR2dbcContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.r2dbc.MySqlR2dbcContainerConnectionDetailsFactory,\ -org.springframework.boot.testcontainers.service.connection.r2dbc.OracleR2dbcContainerConnectionDetailsFactory,\ +org.springframework.boot.testcontainers.service.connection.r2dbc.OracleFreeR2dbcContainerConnectionDetailsFactory,\ +org.springframework.boot.testcontainers.service.connection.r2dbc.OracleXeR2dbcContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.r2dbc.PostgresR2dbcContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.r2dbc.SqlServerR2dbcContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.redis.RedisContainerConnectionDetailsFactory,\ diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializerTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializerTests.java index 3c89be28e94d..68b5c46c415e 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializerTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializerTests.java @@ -16,15 +16,20 @@ package org.springframework.boot.testcontainers.lifecycle; +import java.util.Map; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.testcontainers.containers.GenericContainer; import org.testcontainers.lifecycle.Startable; import org.testcontainers.utility.TestcontainersConfiguration; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.support.AbstractBeanFactory; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.MapPropertySource; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @@ -138,6 +143,22 @@ void dealsWithBeanCurrentlyInCreationException() { applicationContext.refresh(); } + @Test + void setupStartupBasedOnEnvironmentProperty() { + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.getEnvironment() + .getPropertySources() + .addLast(new MapPropertySource("test", Map.of("spring.testcontainers.beans.startup", "parallel"))); + new TestcontainersLifecycleApplicationContextInitializer().initialize(applicationContext); + AbstractBeanFactory beanFactory = (AbstractBeanFactory) applicationContext.getBeanFactory(); + BeanPostProcessor beanPostProcessor = beanFactory.getBeanPostProcessors() + .stream() + .filter(TestcontainersLifecycleBeanPostProcessor.class::isInstance) + .findFirst() + .get(); + assertThat(beanPostProcessor).extracting("startup").isEqualTo(TestcontainersStartup.PARALLEL); + } + private AnnotationConfigApplicationContext createApplicationContext(Startable container) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); new TestcontainersLifecycleApplicationContextInitializer().initialize(applicationContext); diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersStartupTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersStartupTests.java new file mode 100644 index 000000000000..b50e6ef63a58 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersStartupTests.java @@ -0,0 +1,122 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.lifecycle; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; +import org.testcontainers.lifecycle.Startable; + +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link TestcontainersStartup}. + * + * @author Phillip Webb + */ +class TestcontainersStartupTests { + + private static final String PROPERTY = TestcontainersStartup.PROPERTY; + + private final AtomicInteger counter = new AtomicInteger(); + + @Test + void startWhenSquentialStartsSequentially() { + List startables = createTestStartables(100); + TestcontainersStartup.SEQUENTIAL.start(startables); + for (int i = 0; i < startables.size(); i++) { + assertThat(startables.get(i).getIndex()).isEqualTo(i); + assertThat(startables.get(i).getThreadName()).isEqualTo(Thread.currentThread().getName()); + } + } + + @Test + void startWhenParallelStartsInParallel() { + List startables = createTestStartables(100); + TestcontainersStartup.PARALLEL.start(startables); + assertThat(startables.stream().map(TestStartable::getThreadName)).hasSizeGreaterThan(1); + } + + @Test + void getWhenNoPropertyReturnsDefault() { + MockEnvironment environment = new MockEnvironment(); + assertThat(TestcontainersStartup.get(environment)).isEqualTo(TestcontainersStartup.SEQUENTIAL); + } + + @Test + void getWhenPropertyReturnsBasedOnValue() { + MockEnvironment environment = new MockEnvironment(); + assertThat(TestcontainersStartup.get(environment.withProperty(PROPERTY, "SEQUENTIAL"))) + .isEqualTo(TestcontainersStartup.SEQUENTIAL); + assertThat(TestcontainersStartup.get(environment.withProperty(PROPERTY, "sequential"))) + .isEqualTo(TestcontainersStartup.SEQUENTIAL); + assertThat(TestcontainersStartup.get(environment.withProperty(PROPERTY, "SEQuenTIaL"))) + .isEqualTo(TestcontainersStartup.SEQUENTIAL); + assertThat(TestcontainersStartup.get(environment.withProperty(PROPERTY, "S-E-Q-U-E-N-T-I-A-L"))) + .isEqualTo(TestcontainersStartup.SEQUENTIAL); + assertThat(TestcontainersStartup.get(environment.withProperty(PROPERTY, "parallel"))) + .isEqualTo(TestcontainersStartup.PARALLEL); + } + + @Test + void getWhenUnknownPropertyThrowsException() { + MockEnvironment environment = new MockEnvironment(); + assertThatIllegalArgumentException() + .isThrownBy(() -> TestcontainersStartup.get(environment.withProperty(PROPERTY, "bad"))) + .withMessage("Unknown 'spring.testcontainers.beans.startup' property value 'bad'"); + } + + private List createTestStartables(int size) { + List testStartables = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + testStartables.add(new TestStartable()); + } + return testStartables; + } + + private final class TestStartable implements Startable { + + private int index; + + private String threadName; + + @Override + public void start() { + this.index = TestcontainersStartupTests.this.counter.getAndIncrement(); + this.threadName = Thread.currentThread().getName(); + } + + @Override + public void stop() { + } + + int getIndex() { + return this.index; + } + + String getThreadName() { + return this.threadName; + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationTests.java deleted file mode 100644 index 723a4203f0b8..000000000000 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationTests.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.testcontainers.properties; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleApplicationContextInitializer; -import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable; -import org.springframework.boot.testsupport.testcontainers.RedisContainer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.DynamicPropertyRegistry; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link TestcontainersPropertySourceAutoConfiguration}. - * - * @author Phillip Webb - */ -@DisabledIfDockerUnavailable -class TestcontainersPropertySourceAutoConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withInitializer(new TestcontainersLifecycleApplicationContextInitializer()) - .withConfiguration(AutoConfigurations.of(TestcontainersPropertySourceAutoConfiguration.class)); - - @Test - void containerBeanMethodContributesProperties() { - this.contextRunner.withUserConfiguration(ContainerAndPropertiesConfiguration.class).run((context) -> { - TestBean testBean = context.getBean(TestBean.class); - RedisContainer redisContainer = context.getBean(RedisContainer.class); - assertThat(testBean.getUsingPort()).isEqualTo(redisContainer.getFirstMappedPort()); - }); - } - - @Configuration(proxyBeanMethods = false) - @EnableConfigurationProperties(ContainerProperties.class) - @Import(TestBean.class) - static class ContainerAndPropertiesConfiguration { - - @Bean - RedisContainer redisContainer(DynamicPropertyRegistry properties) { - RedisContainer container = new RedisContainer(); - properties.add("container.port", container::getFirstMappedPort); - return container; - } - - } - - @ConfigurationProperties("container") - record ContainerProperties(int port) { - } - - static class TestBean { - - private int usingPort; - - TestBean(ContainerProperties containerProperties) { - this.usingPort = containerProperties.port(); - } - - int getUsingPort() { - return this.usingPort; - } - - } - -} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceTests.java index 7958666992da..86d43e647f27 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,18 @@ package org.springframework.boot.testcontainers.properties; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.boot.testcontainers.lifecycle.BeforeTestcontainerUsedEvent; +import org.springframework.boot.testcontainers.properties.TestcontainersPropertySource.EventPublisherRegistrar; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.PropertySource; import org.springframework.mock.env.MockEnvironment; @@ -37,6 +45,13 @@ class TestcontainersPropertySourceTests { private MockEnvironment environment = new MockEnvironment(); + private GenericApplicationContext context = new GenericApplicationContext(); + + TestcontainersPropertySourceTests() { + ((DefaultListableBeanFactory) this.context.getBeanFactory()).setAllowBeanDefinitionOverriding(false); + this.context.setEnvironment(this.environment); + } + @Test void getPropertyWhenHasValueSupplierReturnsSuppliedValue() { DynamicPropertyRegistry registry = TestcontainersPropertySource.attach(this.environment); @@ -85,14 +100,14 @@ void getSourceReturnsImmutableSource() { } @Test - void attachWhenNotAttachedAttaches() { + void attachToEnvironmentWhenNotAttachedAttaches() { TestcontainersPropertySource.attach(this.environment); PropertySource propertySource = this.environment.getPropertySources().get(TestcontainersPropertySource.NAME); assertThat(propertySource).isNotNull(); } @Test - void attachWhenAlreadyAttachedReturnsExisting() { + void attachToEnvironmentWhenAlreadyAttachedReturnsExisting() { DynamicPropertyRegistry r1 = TestcontainersPropertySource.attach(this.environment); PropertySource p1 = this.environment.getPropertySources().get(TestcontainersPropertySource.NAME); DynamicPropertyRegistry r2 = TestcontainersPropertySource.attach(this.environment); @@ -101,4 +116,38 @@ void attachWhenAlreadyAttachedReturnsExisting() { assertThat(p1).isSameAs(p2); } + @Test + void attachToEnvironmentAndContextWhenNotAttachedAttaches() { + TestcontainersPropertySource.attach(this.environment, this.context); + PropertySource propertySource = this.environment.getPropertySources().get(TestcontainersPropertySource.NAME); + assertThat(propertySource).isNotNull(); + assertThat(this.context.containsBean(EventPublisherRegistrar.NAME)); + } + + @Test + void attachToEnvironmentAndContextWhenAlreadyAttachedReturnsExisting() { + DynamicPropertyRegistry r1 = TestcontainersPropertySource.attach(this.environment, this.context); + PropertySource p1 = this.environment.getPropertySources().get(TestcontainersPropertySource.NAME); + DynamicPropertyRegistry r2 = TestcontainersPropertySource.attach(this.environment, this.context); + PropertySource p2 = this.environment.getPropertySources().get(TestcontainersPropertySource.NAME); + assertThat(r1).isSameAs(r2); + assertThat(p1).isSameAs(p2); + } + + @Test + void getPropertyPublishesEvent() { + try (GenericApplicationContext applicationContext = new GenericApplicationContext()) { + List events = new ArrayList<>(); + applicationContext.addApplicationListener(events::add); + DynamicPropertyRegistry registry = TestcontainersPropertySource.attach(applicationContext.getEnvironment(), + (BeanDefinitionRegistry) applicationContext.getBeanFactory()); + applicationContext.refresh(); + registry.add("test", () -> "spring"); + assertThat(applicationContext.getEnvironment().containsProperty("test")).isTrue(); + assertThat(events.isEmpty()); + assertThat(applicationContext.getEnvironment().getProperty("test")).isEqualTo("spring"); + assertThat(events.stream().filter(BeforeTestcontainerUsedEvent.class::isInstance)).hasSize(1); + } + } + } diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactoryTests.java index bab9bf3488f1..f5cc2b4e2820 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactoryTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.boot.testcontainers.service.connection; +import java.util.Collections; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.BeforeEach; @@ -28,11 +30,16 @@ import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory; import org.springframework.boot.origin.Origin; +import org.springframework.boot.testcontainers.lifecycle.BeforeTestcontainerUsedEvent; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactoryTests.TestContainerConnectionDetailsFactory.TestContainerConnectionDetails; +import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.MergedAnnotation; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; /** @@ -79,6 +86,14 @@ void getConnectionDetailsWhenTypesMatchAndNameRestrictionMatchesReturnsDetails() assertThat(connectionDetails).isNotNull(); } + @Test + void getConnectionDetailsWhenTypesMatchAndNameRestrictionsMatchReturnsDetails() { + TestContainerConnectionDetailsFactory factory = new TestContainerConnectionDetailsFactory( + List.of("notmyname", "myname")); + ConnectionDetails connectionDetails = getConnectionDetails(factory, this.source); + assertThat(connectionDetails).isNotNull(); + } + @Test void getConnectionDetailsWhenTypesMatchAndNameRestrictionDoesNotMatchReturnsNull() { TestContainerConnectionDetailsFactory factory = new TestContainerConnectionDetailsFactory("notmyname"); @@ -86,6 +101,14 @@ void getConnectionDetailsWhenTypesMatchAndNameRestrictionDoesNotMatchReturnsNull assertThat(connectionDetails).isNull(); } + @Test + void getConnectionDetailsWhenTypesMatchAndNameRestrictionsDoNotMatchReturnsNull() { + TestContainerConnectionDetailsFactory factory = new TestContainerConnectionDetailsFactory( + List.of("notmyname", "alsonotmyname")); + ConnectionDetails connectionDetails = getConnectionDetails(factory, this.source); + assertThat(connectionDetails).isNull(); + } + @Test void getConnectionDetailsWhenContainerTypeDoesNotMatchReturnsNull() { ElasticsearchContainer container = mock(ElasticsearchContainer.class); @@ -107,16 +130,31 @@ void getConnectionDetailsHasOrigin() { void getContainerWhenNotInitializedThrowsException() { TestContainerConnectionDetailsFactory factory = new TestContainerConnectionDetailsFactory(); TestContainerConnectionDetails connectionDetails = getConnectionDetails(factory, this.source); - assertThatIllegalStateException().isThrownBy(() -> connectionDetails.callGetContainer()) + assertThatIllegalStateException().isThrownBy(connectionDetails::callGetContainer) .withMessage("Container cannot be obtained before the connection details bean has been initialized"); } @Test - void getContainerWhenInitializedReturnsSuppliedContainer() throws Exception { + void getContainerWhenInitializedPublishesEventAndReturnsSuppliedContainer() throws Exception { TestContainerConnectionDetailsFactory factory = new TestContainerConnectionDetailsFactory(); TestContainerConnectionDetails connectionDetails = getConnectionDetails(factory, this.source); + ApplicationContext context = mock(ApplicationContext.class); + connectionDetails.setApplicationContext(context); connectionDetails.afterPropertiesSet(); assertThat(connectionDetails.callGetContainer()).isSameAs(this.container); + then(context).should().publishEvent(any(BeforeTestcontainerUsedEvent.class)); + } + + @Test + void creatingFactoryWithEmptyNamesThrows() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new TestContainerConnectionDetailsFactory(Collections.emptyList())); + } + + @Test + void creatingFactoryWithNullNamesThrows() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new TestContainerConnectionDetailsFactory((List) null)); } @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -139,6 +177,10 @@ static class TestContainerConnectionDetailsFactory super(connectionName); } + TestContainerConnectionDetailsFactory(List connectionNames) { + super(connectionNames); + } + @Override protected JdbcConnectionDetails getContainerConnectionDetails( ContainerConnectionSource> source) { diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaContainerConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index 713a1402771a..000000000000 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaContainerConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.testcontainers.service.connection.kafka; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; - -import org.awaitility.Awaitility; -import org.junit.jupiter.api.Test; -import org.testcontainers.containers.KafkaContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link KafkaContainerConnectionDetailsFactory}. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - */ -@SpringJUnitConfig -@Testcontainers(disabledWithoutDocker = true) -@TestPropertySource(properties = { "spring.kafka.consumer.group-id=test-group", - "spring.kafka.consumer.auto-offset-reset=earliest" }) -class KafkaContainerConnectionDetailsFactoryIntegrationTests { - - @Container - @ServiceConnection - static final KafkaContainer kafka = new KafkaContainer(DockerImageNames.kafka()); - - @Autowired - private KafkaTemplate kafkaTemplate; - - @Autowired - private TestListener listener; - - @Test - void connectionCanBeMadeToKafkaContainer() { - this.kafkaTemplate.send("test-topic", "test-data"); - Awaitility.waitAtMost(Duration.ofMinutes(4)) - .untilAsserted(() -> assertThat(this.listener.messages).containsExactly("test-data")); - } - - @Configuration(proxyBeanMethods = false) - @ImportAutoConfiguration(KafkaAutoConfiguration.class) - static class TestConfiguration { - - @Bean - TestListener testListener() { - return new TestListener(); - } - - } - - static class TestListener { - - private final List messages = new ArrayList<>(); - - @KafkaListener(topics = "test-topic") - void processMessage(String message) { - this.messages.add(message); - } - - } - -} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleFreeR2dbcContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleFreeR2dbcContainerConnectionDetailsFactoryTests.java new file mode 100644 index 000000000000..5cbcb4c1f7ac --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleFreeR2dbcContainerConnectionDetailsFactoryTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.r2dbc; + +import io.r2dbc.spi.ConnectionFactoryOptions; +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactoryHints; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OracleFreeR2dbcContainerConnectionDetailsFactory}. + * + * @author Andy Wilkinson + */ +class OracleFreeR2dbcContainerConnectionDetailsFactoryTests { + + @Test + void shouldRegisterHints() { + RuntimeHints hints = ContainerConnectionDetailsFactoryHints.getRegisteredHints(getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(ConnectionFactoryOptions.class)).accepts(hints); + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleR2dbcContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleR2dbcContainerConnectionDetailsFactoryTests.java deleted file mode 100644 index 5aff194a1bfd..000000000000 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleR2dbcContainerConnectionDetailsFactoryTests.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.testcontainers.service.connection.r2dbc; - -import java.time.Duration; - -import io.r2dbc.spi.ConnectionFactory; -import io.r2dbc.spi.ConnectionFactoryOptions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.OS; -import org.testcontainers.containers.OracleContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; - -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration; -import org.springframework.boot.jdbc.DatabaseDriver; -import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactoryHints; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.junit.DisabledOnOs; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; -import org.springframework.context.annotation.Configuration; -import org.springframework.r2dbc.core.DatabaseClient; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link OracleR2dbcContainerConnectionDetailsFactory}. - * - * @author Andy Wilkinson - */ -@SpringJUnitConfig -@Testcontainers(disabledWithoutDocker = true) -@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", - disabledReason = "The Oracle image has no ARM support") -class OracleR2dbcContainerConnectionDetailsFactoryTests { - - @Container - @ServiceConnection - static final OracleContainer oracle = new OracleContainer(DockerImageNames.oracleXe()) - .withStartupTimeout(Duration.ofMinutes(2)); - - @Autowired - ConnectionFactory connectionFactory; - - @Test - void connectionCanBeMadeToOracleContainer() { - Object result = DatabaseClient.create(this.connectionFactory) - .sql(DatabaseDriver.ORACLE.getValidationQuery()) - .map((row, metadata) -> row.get(0)) - .first() - .block(Duration.ofSeconds(30)); - assertThat(result).isEqualTo("Hello"); - } - - @Test - void shouldRegisterHints() { - RuntimeHints hints = ContainerConnectionDetailsFactoryHints.getRegisteredHints(getClass().getClassLoader()); - assertThat(RuntimeHintsPredicates.reflection().onType(ConnectionFactoryOptions.class)).accepts(hints); - } - - @Configuration(proxyBeanMethods = false) - @ImportAutoConfiguration(R2dbcAutoConfiguration.class) - static class TestConfiguration { - - } - -} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleXeR2dbcContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleXeR2dbcContainerConnectionDetailsFactoryTests.java new file mode 100644 index 000000000000..392f44aa87b1 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/r2dbc/OracleXeR2dbcContainerConnectionDetailsFactoryTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection.r2dbc; + +import io.r2dbc.spi.ConnectionFactoryOptions; +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactoryHints; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OracleXeR2dbcContainerConnectionDetailsFactory}. + * + * @author Andy Wilkinson + */ +class OracleXeR2dbcContainerConnectionDetailsFactoryTests { + + @Test + void shouldRegisterHints() { + RuntimeHints hints = ContainerConnectionDetailsFactoryHints.getRegisteredHints(getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(ConnectionFactoryOptions.class)).accepts(hints); + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/resources/spring.properties b/spring-boot-project/spring-boot-testcontainers/src/test/resources/spring.properties new file mode 100644 index 000000000000..47dff33f0bb5 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/test/resources/spring.properties @@ -0,0 +1 @@ +spring.test.context.cache.maxSize=1 \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-antlib/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-antlib/build.gradle index e00c34aeaf8e..750604b01944 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-antlib/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-antlib/build.gradle @@ -19,7 +19,7 @@ dependencies { antUnit "org.apache.ant:ant-antunit:1.3" antIvy "org.apache.ivy:ivy:2.5.0" - compileOnly(project(":spring-boot-project:spring-boot-tools:spring-boot-loader")) + compileOnly(project(":spring-boot-project:spring-boot-tools:spring-boot-loader-classic")) compileOnly("org.apache.ant:ant:${antVersion}") implementation(project(":spring-boot-project:spring-boot-tools:spring-boot-loader-tools")) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-antlib/src/main/resources/org/springframework/boot/ant/antlib.xml b/spring-boot-project/spring-boot-tools/spring-boot-antlib/src/main/resources/org/springframework/boot/ant/antlib.xml index bf2f7307866d..980049c0cd2d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-antlib/src/main/resources/org/springframework/boot/ant/antlib.xml +++ b/spring-boot-project/spring-boot-tools/spring-boot-antlib/src/main/resources/org/springframework/boot/ant/antlib.xml @@ -42,7 +42,7 @@ Extracting spring-boot-loader to ${destdir}/dependency - @@ -58,10 +58,10 @@ - + + value="org.springframework.boot.loader.launch.JarLauncher" /> diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/build.gradle index d6b393c6819e..0a0243b6f881 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/build.gradle @@ -2,35 +2,27 @@ plugins { id "java-library" id "org.springframework.boot.conventions" id "org.springframework.boot.deployed" + id "org.springframework.boot.docker-test" } description = "Spring Boot Buildpack Platform" -configurations.all { - resolutionStrategy { - eachDependency { dependency -> - // Downgrade Jackson as Gradle cannot cope with 2.15.0's multi-version - // jar files with bytecode in META-INF/versions/19 - if (dependency.requested.group.startsWith("com.fasterxml.jackson")) { - dependency.useVersion("2.14.2") - } - } - } -} - dependencies { - api("com.fasterxml.jackson.core:jackson-databind") - api("com.fasterxml.jackson.module:jackson-module-parameter-names") - api("net.java.dev.jna:jna-platform") - api("org.apache.commons:commons-compress") - api("org.apache.httpcomponents.client5:httpclient5") - api("org.springframework:spring-core") - api("org.tomlj:tomlj:1.0.0") - + dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker")) + dockerTestImplementation("org.junit.jupiter:junit-jupiter") + dockerTestRuntimeOnly("org.testcontainers:testcontainers") + + implementation("com.fasterxml.jackson.core:jackson-databind") + implementation("com.fasterxml.jackson.module:jackson-module-parameter-names") + implementation("net.java.dev.jna:jna-platform") + implementation("org.apache.commons:commons-compress") + implementation("org.apache.httpcomponents.client5:httpclient5") + implementation("org.springframework:spring-core") + implementation("org.tomlj:tomlj:1.0.0") + testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation("com.jayway.jsonpath:json-path") testImplementation("org.assertj:assertj-core") - testImplementation("org.testcontainers:testcontainers") testImplementation("org.hamcrest:hamcrest") testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.mockito:mockito-core") diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/dockerTest/java/org/springframework/boot/buildpack/platform/docker/DockerApiIntegrationTests.java similarity index 89% rename from spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiIntegrationTests.java rename to spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/dockerTest/java/org/springframework/boot/buildpack/platform/docker/DockerApiIntegrationTests.java index cff7b111eb4e..55f0cfda3bbc 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/dockerTest/java/org/springframework/boot/buildpack/platform/docker/DockerApiIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; -import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable; +import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; /** * Integration tests for {@link DockerApi}. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/AbstractBuildLog.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/AbstractBuildLog.java index 7154d4ad43bf..6c4ed5c71a9f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/AbstractBuildLog.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/AbstractBuildLog.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,6 +68,12 @@ public void executingLifecycle(BuildRequest request, LifecycleVersion version, V log(" > Using build cache volume '" + buildCacheVolume + "'"); } + @Override + public void executingLifecycle(BuildRequest request, LifecycleVersion version, Cache buildCache) { + log(" > Executing lifecycle version " + version); + log(" > Using build cache " + buildCache); + } + @Override public Consumer runningPhase(BuildRequest request, String name) { log(); @@ -96,6 +102,17 @@ public void taggedImage(ImageReference tag) { log(); } + @Override + public void failedCleaningWorkDir(Cache cache, Exception exception) { + StringBuilder message = new StringBuilder("Warning: Working location " + cache + " could not be cleaned"); + if (exception != null) { + message.append(": ").append(exception.getMessage()); + } + log(); + log(message.toString()); + log(); + } + private String getDigest(Image image) { List digests = image.getDigests(); return (digests.isEmpty() ? "" : digests.get(0)); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ApiVersions.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ApiVersions.java index 10ceba196dba..2b1f474c06f7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ApiVersions.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ApiVersions.java @@ -31,7 +31,7 @@ final class ApiVersions { /** * The platform API versions supported by this release. */ - static final ApiVersions SUPPORTED_PLATFORMS = ApiVersions.of(0, IntStream.rangeClosed(3, 11)); + static final ApiVersions SUPPORTED_PLATFORMS = ApiVersions.of(0, IntStream.rangeClosed(3, 12)); private final ApiVersion[] apiVersions; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildLog.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildLog.java index 0acbbabd224f..c753db8bb847 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildLog.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildLog.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,6 +79,14 @@ public interface BuildLog { */ void executingLifecycle(BuildRequest request, LifecycleVersion version, VolumeName buildCacheVolume); + /** + * Log that the lifecycle is executing. + * @param request the build request + * @param version the lifecycle version + * @param buildCache the build cache in use + */ + void executingLifecycle(BuildRequest request, LifecycleVersion version, Cache buildCache); + /** * Log that a specific phase is running. * @param request the build request @@ -106,6 +114,13 @@ public interface BuildLog { */ void taggedImage(ImageReference tag); + /** + * Log that a cache cleanup step was not completed successfully. + * @param cache the cache + * @param exception any exception that caused the failure + */ + void failedCleaningWorkDir(Cache cache, Exception exception); + /** * Factory method that returns a {@link BuildLog} the outputs to {@link System#out}. * @return a build log instance that logs to system out diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildRequest.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildRequest.java index f409fcc875ff..76bedefd106d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildRequest.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,9 +45,20 @@ */ public class BuildRequest { - static final String DEFAULT_BUILDER_IMAGE_NAME = "paketobuildpacks/builder-jammy-base:latest"; + static final String DEFAULT_BUILDER_IMAGE_NAME = "paketobuildpacks/builder-jammy-tiny"; - private static final ImageReference DEFAULT_BUILDER = ImageReference.of(DEFAULT_BUILDER_IMAGE_NAME); + static final String DEFAULT_BUILDER_IMAGE_REF = DEFAULT_BUILDER_IMAGE_NAME + ":latest"; + + static final List KNOWN_TRUSTED_BUILDERS = List.of( + ImageReference.of("paketobuildpacks/builder-jammy-tiny"), + ImageReference.of("paketobuildpacks/builder-jammy-base"), + ImageReference.of("paketobuildpacks/builder-jammy-full"), + ImageReference.of("paketobuildpacks/builder-jammy-buildpackless-tiny"), + ImageReference.of("paketobuildpacks/builder-jammy-buildpackless-base"), + ImageReference.of("paketobuildpacks/builder-jammy-buildpackless-full"), + ImageReference.of("gcr.io/buildpacks/builder"), ImageReference.of("heroku/builder")); + + private static final ImageReference DEFAULT_BUILDER = ImageReference.of(DEFAULT_BUILDER_IMAGE_REF); private final ImageReference name; @@ -55,6 +66,8 @@ public class BuildRequest { private final ImageReference builder; + private final Boolean trustBuilder; + private final ImageReference runImage; private final Creator creator; @@ -77,6 +90,8 @@ public class BuildRequest { private final List tags; + private final Cache buildWorkspace; + private final Cache buildCache; private final Cache launchCache; @@ -85,12 +100,15 @@ public class BuildRequest { private final String applicationDirectory; + private final List securityOptions; + BuildRequest(ImageReference name, Function applicationContent) { Assert.notNull(name, "Name must not be null"); Assert.notNull(applicationContent, "ApplicationContent must not be null"); this.name = name.inTaggedForm(); this.applicationContent = applicationContent; this.builder = DEFAULT_BUILDER; + this.trustBuilder = null; this.runImage = null; this.env = Collections.emptyMap(); this.cleanCache = false; @@ -102,17 +120,20 @@ public class BuildRequest { this.bindings = Collections.emptyList(); this.network = null; this.tags = Collections.emptyList(); + this.buildWorkspace = null; this.buildCache = null; this.launchCache = null; this.createdDate = null; this.applicationDirectory = null; + this.securityOptions = null; } BuildRequest(ImageReference name, Function applicationContent, ImageReference builder, ImageReference runImage, Creator creator, Map env, boolean cleanCache, boolean verboseLogging, PullPolicy pullPolicy, boolean publish, List buildpacks, - List bindings, String network, List tags, Cache buildCache, Cache launchCache, - Instant createdDate, String applicationDirectory) { + List bindings, String network, List tags, Cache buildWorkspace, Cache buildCache, + Cache launchCache, Instant createdDate, String applicationDirectory, List securityOptions, + Boolean trustBuilder) { this.name = name; this.applicationContent = applicationContent; this.builder = builder; @@ -127,10 +148,13 @@ public class BuildRequest { this.bindings = bindings; this.network = network; this.tags = tags; + this.buildWorkspace = buildWorkspace; this.buildCache = buildCache; this.launchCache = launchCache; this.createdDate = createdDate; this.applicationDirectory = applicationDirectory; + this.securityOptions = securityOptions; + this.trustBuilder = trustBuilder; } /** @@ -142,8 +166,21 @@ public BuildRequest withBuilder(ImageReference builder) { Assert.notNull(builder, "Builder must not be null"); return new BuildRequest(this.name, this.applicationContent, builder.inTaggedOrDigestForm(), this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, - this.buildpacks, this.bindings, this.network, this.tags, this.buildCache, this.launchCache, - this.createdDate, this.applicationDirectory); + this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, + this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.trustBuilder); + } + + /** + * Return a new {@link BuildRequest} with an updated trust builder setting. + * @param trustBuilder {@code true} if the builder should be treated as trusted, + * {@code false} otherwise + * @return an updated build request + */ + public BuildRequest withTrustBuilder(boolean trustBuilder) { + return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, + this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, + this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, + this.applicationDirectory, this.securityOptions, trustBuilder); } /** @@ -154,8 +191,8 @@ public BuildRequest withBuilder(ImageReference builder) { public BuildRequest withRunImage(ImageReference runImageName) { return new BuildRequest(this.name, this.applicationContent, this.builder, runImageName.inTaggedOrDigestForm(), this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, - this.buildpacks, this.bindings, this.network, this.tags, this.buildCache, this.launchCache, - this.createdDate, this.applicationDirectory); + this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, + this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.trustBuilder); } /** @@ -167,8 +204,8 @@ public BuildRequest withCreator(Creator creator) { Assert.notNull(creator, "Creator must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, - this.network, this.tags, this.buildCache, this.launchCache, this.createdDate, - this.applicationDirectory); + this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, + this.applicationDirectory, this.securityOptions, this.trustBuilder); } /** @@ -184,8 +221,8 @@ public BuildRequest withEnv(String name, String value) { env.put(name, value); return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, Collections.unmodifiableMap(env), this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, - this.buildpacks, this.bindings, this.network, this.tags, this.buildCache, this.launchCache, - this.createdDate, this.applicationDirectory); + this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, + this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.trustBuilder); } /** @@ -199,8 +236,9 @@ public BuildRequest withEnv(Map env) { updatedEnv.putAll(env); return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, Collections.unmodifiableMap(updatedEnv), this.cleanCache, this.verboseLogging, this.pullPolicy, - this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildCache, - this.launchCache, this.createdDate, this.applicationDirectory); + this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, + this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, + this.trustBuilder); } /** @@ -211,8 +249,8 @@ public BuildRequest withEnv(Map env) { public BuildRequest withCleanCache(boolean cleanCache) { return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, - this.network, this.tags, this.buildCache, this.launchCache, this.createdDate, - this.applicationDirectory); + this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, + this.applicationDirectory, this.securityOptions, this.trustBuilder); } /** @@ -223,8 +261,8 @@ public BuildRequest withCleanCache(boolean cleanCache) { public BuildRequest withVerboseLogging(boolean verboseLogging) { return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, this.cleanCache, verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, - this.network, this.tags, this.buildCache, this.launchCache, this.createdDate, - this.applicationDirectory); + this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, + this.applicationDirectory, this.securityOptions, this.trustBuilder); } /** @@ -235,8 +273,8 @@ public BuildRequest withVerboseLogging(boolean verboseLogging) { public BuildRequest withPullPolicy(PullPolicy pullPolicy) { return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, pullPolicy, this.publish, this.buildpacks, this.bindings, - this.network, this.tags, this.buildCache, this.launchCache, this.createdDate, - this.applicationDirectory); + this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, + this.applicationDirectory, this.securityOptions, this.trustBuilder); } /** @@ -247,8 +285,8 @@ public BuildRequest withPullPolicy(PullPolicy pullPolicy) { public BuildRequest withPublish(boolean publish) { return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, publish, this.buildpacks, this.bindings, - this.network, this.tags, this.buildCache, this.launchCache, this.createdDate, - this.applicationDirectory); + this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, + this.applicationDirectory, this.securityOptions, this.trustBuilder); } /** @@ -272,8 +310,8 @@ public BuildRequest withBuildpacks(List buildpacks) { Assert.notNull(buildpacks, "Buildpacks must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, buildpacks, this.bindings, - this.network, this.tags, this.buildCache, this.launchCache, this.createdDate, - this.applicationDirectory); + this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, + this.applicationDirectory, this.securityOptions, this.trustBuilder); } /** @@ -297,8 +335,8 @@ public BuildRequest withBindings(List bindings) { Assert.notNull(bindings, "Bindings must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, bindings, - this.network, this.tags, this.buildCache, this.launchCache, this.createdDate, - this.applicationDirectory); + this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, + this.applicationDirectory, this.securityOptions, this.trustBuilder); } /** @@ -310,7 +348,8 @@ public BuildRequest withBindings(List bindings) { public BuildRequest withNetwork(String network) { return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, - network, this.tags, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory); + network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, + this.applicationDirectory, this.securityOptions, this.trustBuilder); } /** @@ -332,7 +371,22 @@ public BuildRequest withTags(List tags) { Assert.notNull(tags, "Tags must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, - this.network, tags, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory); + this.network, tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, + this.applicationDirectory, this.securityOptions, this.trustBuilder); + } + + /** + * Return a new {@link BuildRequest} with an updated build workspace. + * @param buildWorkspace the build workspace + * @return an updated build request + * @since 3.2.0 + */ + public BuildRequest withBuildWorkspace(Cache buildWorkspace) { + Assert.notNull(buildWorkspace, "BuildWorkspace must not be null"); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, + this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, + this.network, this.tags, buildWorkspace, this.buildCache, this.launchCache, this.createdDate, + this.applicationDirectory, this.securityOptions, this.trustBuilder); } /** @@ -344,7 +398,8 @@ public BuildRequest withBuildCache(Cache buildCache) { Assert.notNull(buildCache, "BuildCache must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, - this.network, this.tags, buildCache, this.launchCache, this.createdDate, this.applicationDirectory); + this.network, this.tags, this.buildWorkspace, buildCache, this.launchCache, this.createdDate, + this.applicationDirectory, this.securityOptions, this.trustBuilder); } /** @@ -356,7 +411,8 @@ public BuildRequest withLaunchCache(Cache launchCache) { Assert.notNull(launchCache, "LaunchCache must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, - this.network, this.tags, this.buildCache, launchCache, this.createdDate, this.applicationDirectory); + this.network, this.tags, this.buildWorkspace, this.buildCache, launchCache, this.createdDate, + this.applicationDirectory, this.securityOptions, this.trustBuilder); } /** @@ -368,8 +424,8 @@ public BuildRequest withCreatedDate(String createdDate) { Assert.notNull(createdDate, "CreatedDate must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, - this.network, this.tags, this.buildCache, this.launchCache, parseCreatedDate(createdDate), - this.applicationDirectory); + this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, + parseCreatedDate(createdDate), this.applicationDirectory, this.securityOptions, this.trustBuilder); } private Instant parseCreatedDate(String createdDate) { @@ -393,7 +449,22 @@ public BuildRequest withApplicationDirectory(String applicationDirectory) { Assert.notNull(applicationDirectory, "ApplicationDirectory must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, - this.network, this.tags, this.buildCache, this.launchCache, this.createdDate, applicationDirectory); + this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, + applicationDirectory, this.securityOptions, this.trustBuilder); + } + + /** + * Return a new {@link BuildRequest} with an updated security options. + * @param securityOptions the security options + * @return an updated build request + * @since 3.2.0 + */ + public BuildRequest withSecurityOptions(List securityOptions) { + Assert.notNull(securityOptions, "SecurityOption must not be null"); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, + this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, + this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, + this.applicationDirectory, securityOptions, this.trustBuilder); } /** @@ -423,6 +494,19 @@ public ImageReference getBuilder() { return this.builder; } + /** + * Return whether the builder should be treated as trusted. + * @return the trust builder flag + * @since 3.4.0 + */ + public boolean isTrustBuilder() { + return (this.trustBuilder != null) ? this.trustBuilder : isBuilderKnownAndTrusted(); + } + + private boolean isBuilderKnownAndTrusted() { + return KNOWN_TRUSTED_BUILDERS.stream().anyMatch((builder) -> builder.getName().equals(this.builder.getName())); + } + /** * Return the run image that should be used, if provided. * @return the run image @@ -513,6 +597,15 @@ public List getTags() { return this.tags; } + /** + * Return the build workspace that should be used by the lifecycle. + * @return the build workspace or {@code null} + * @since 3.2.0 + */ + public Cache getBuildWorkspace() { + return this.buildWorkspace; + } + /** * Return the custom build cache that should be used by the lifecycle. * @return the build cache @@ -545,6 +638,15 @@ public String getApplicationDirectory() { return this.applicationDirectory; } + /** + * Return the security options that should be used by the lifecycle. + * @return the security options or {@code null} + * @since 3.2.0 + */ + public List getSecurityOptions() { + return this.securityOptions; + } + /** * Factory method to create a new {@link BuildRequest} from a JAR file. * @param jarFile the source jar file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java index 367e52d616d8..2549144662cd 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,11 +17,9 @@ package org.springframework.boot.buildpack.platform.build; import java.io.IOException; -import java.nio.file.Path; import java.util.List; import java.util.function.Consumer; -import org.springframework.boot.buildpack.platform.build.BuilderMetadata.Stack; import org.springframework.boot.buildpack.platform.docker.DockerApi; import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent; import org.springframework.boot.buildpack.platform.docker.TotalProgressPullListener; @@ -33,6 +31,7 @@ import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.io.IOBiConsumer; +import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -103,7 +102,7 @@ public void build(BuildRequest request) throws DockerEngineException, IOExceptio ImageFetcher imageFetcher = new ImageFetcher(domain, getBuilderAuthHeader(), pullPolicy); Image builderImage = imageFetcher.fetchImage(ImageType.BUILDER, request.getBuilder()); BuilderMetadata builderMetadata = BuilderMetadata.fromImage(builderImage); - request = withRunImageIfNeeded(request, builderMetadata.getStack()); + request = withRunImageIfNeeded(request, builderMetadata); Image runImage = imageFetcher.fetchImage(ImageType.RUNNER, request.getRunImage()); assertStackIdsMatch(runImage, builderImage); BuildOwner buildOwner = BuildOwner.fromEnv(builderImage.getConfig().getEnv()); @@ -124,24 +123,30 @@ public void build(BuildRequest request) throws DockerEngineException, IOExceptio } } - private BuildRequest withRunImageIfNeeded(BuildRequest request, Stack builderStack) { + private BuildRequest withRunImageIfNeeded(BuildRequest request, BuilderMetadata metadata) { if (request.getRunImage() != null) { return request; } - return request.withRunImage(getRunImageReferenceForStack(builderStack)); + return request.withRunImage(getRunImageReference(metadata)); } - private ImageReference getRunImageReferenceForStack(Stack stack) { - String name = stack.getRunImage().getImage(); - Assert.state(StringUtils.hasText(name), "Run image must be specified in the builder image stack"); - return ImageReference.of(name).inTaggedOrDigestForm(); + private ImageReference getRunImageReference(BuilderMetadata metadata) { + if (metadata.getRunImages() != null && !metadata.getRunImages().isEmpty()) { + String runImageName = metadata.getRunImages().get(0).getImage(); + return ImageReference.of(runImageName).inTaggedOrDigestForm(); + } + String runImageName = metadata.getStack().getRunImage().getImage(); + Assert.state(StringUtils.hasText(runImageName), "Run image must be specified in the builder image metadata"); + return ImageReference.of(runImageName).inTaggedOrDigestForm(); } private void assertStackIdsMatch(Image runImage, Image builderImage) { StackId runImageStackId = StackId.fromImage(runImage); StackId builderImageStackId = StackId.fromImage(builderImage); - Assert.state(runImageStackId.equals(builderImageStackId), () -> "Run image stack '" + runImageStackId - + "' does not match builder stack '" + builderImageStackId + "'"); + if (runImageStackId.hasId() && builderImageStackId.hasId()) { + Assert.state(runImageStackId.equals(builderImageStackId), () -> "Run image stack '" + runImageStackId + + "' does not match builder stack '" + builderImageStackId + "'"); + } } private Buildpacks getBuildpacks(BuildRequest request, ImageFetcher imageFetcher, BuilderMetadata builderMetadata, @@ -273,8 +278,9 @@ public Image fetchImage(ImageReference reference, ImageType imageType) throws IO } @Override - public void exportImageLayers(ImageReference reference, IOBiConsumer exports) throws IOException { - Builder.this.docker.image().exportLayerFiles(reference, exports); + public void exportImageLayers(ImageReference reference, IOBiConsumer exports) + throws IOException { + Builder.this.docker.image().exportLayers(reference, exports); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderException.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderException.java index 1ac0664a2b19..4158c9769032 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderException.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderException.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderMetadata.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderMetadata.java index d08c11bfa04c..831b524a3c83 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderMetadata.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,6 +49,8 @@ class BuilderMetadata extends MappedObject { private final Stack stack; + private final List runImages; + private final Lifecycle lifecycle; private final CreatedBy createdBy; @@ -58,6 +60,7 @@ class BuilderMetadata extends MappedObject { BuilderMetadata(JsonNode node) { super(node, MethodHandles.lookup()); this.stack = valueAt("/stack", Stack.class); + this.runImages = childrenAt("/images", RunImage::new); this.lifecycle = valueAt("/lifecycle", Lifecycle.class); this.createdBy = valueAt("/createdBy", CreatedBy.class); this.buildpacks = extractBuildpacks(getNode().at("/buildpacks")); @@ -80,6 +83,14 @@ Stack getStack() { return this.stack; } + /** + * Return run images metadata. + * @return the run images metadata + */ + List getRunImages() { + return this.runImages; + } + /** * Return lifecycle metadata. * @return the lifecycle metadata @@ -196,6 +207,32 @@ default String[] getMirrors() { } + static class RunImage extends MappedObject { + + private final String image; + + private final List mirrors; + + /** + * Create a new {@link MappedObject} instance. + * @param node the source node + */ + RunImage(JsonNode node) { + super(node, MethodHandles.lookup()); + this.image = valueAt("/image", String.class); + this.mirrors = childrenAt("/mirrors", JsonNode::asText); + } + + String getImage() { + return this.image; + } + + List getMirrors() { + return this.mirrors; + } + + } + /** * Lifecycle metadata. */ diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackResolverContext.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackResolverContext.java index fd8e59feffd5..4357ca8b382e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackResolverContext.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackResolverContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,12 @@ package org.springframework.boot.buildpack.platform.build; import java.io.IOException; -import java.nio.file.Path; import java.util.List; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.io.IOBiConsumer; +import org.springframework.boot.buildpack.platform.io.TarArchive; /** * Context passed to a {@link BuildpackResolver}. @@ -52,6 +52,6 @@ interface BuildpackResolverContext { * during the callback) * @throws IOException on IO error */ - void exportImageLayers(ImageReference reference, IOBiConsumer exports) throws IOException; + void exportImageLayers(ImageReference reference, IOBiConsumer exports) throws IOException; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Cache.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Cache.java index 9f3087f94c31..704a3418d397 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Cache.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Cache.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import java.util.Objects; +import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -37,7 +38,22 @@ public enum Format { /** * A cache stored as a volume in the Docker daemon. */ - VOLUME; + VOLUME("volume"), + + /** + * A cache stored as a bind mount. + */ + BIND("bind mount"); + + private final String description; + + Format(String description) { + this.description = description; + } + + public String getDescription() { + return this.description; + } } @@ -55,16 +71,44 @@ public Volume getVolume() { return (this.format.equals(Format.VOLUME)) ? (Volume) this : null; } + /** + * Return the details of the cache if it is a bind cache. + * @return the cache, or {@code null} if it is not a bind cache + */ + public Bind getBind() { + return (this.format.equals(Format.BIND)) ? (Bind) this : null; + } + /** * Create a new {@code Cache} that uses a volume with the provided name. * @param name the cache volume name * @return a new cache instance */ public static Cache volume(String name) { + Assert.notNull(name, "Name must not be null"); + return new Volume(VolumeName.of(name)); + } + + /** + * Create a new {@code Cache} that uses a volume with the provided name. + * @param name the cache volume name + * @return a new cache instance + */ + public static Cache volume(VolumeName name) { Assert.notNull(name, "Name must not be null"); return new Volume(name); } + /** + * Create a new {@code Cache} that uses a bind mount with the provided source. + * @param source the cache bind mount source + * @return a new cache instance + */ + public static Cache bind(String source) { + Assert.notNull(source, "Source must not be null"); + return new Bind(source); + } + @Override public boolean equals(Object obj) { if (this == obj) { @@ -87,14 +131,18 @@ public int hashCode() { */ public static class Volume extends Cache { - private final String name; + private final VolumeName name; - Volume(String name) { + Volume(VolumeName name) { super(Format.VOLUME); this.name = name; } public String getName() { + return this.name.toString(); + } + + public VolumeName getVolumeName() { return this.name; } @@ -120,6 +168,56 @@ public int hashCode() { return result; } + @Override + public String toString() { + return this.format.getDescription() + " '" + this.name + "'"; + } + + } + + /** + * Details of a cache stored in a bind mount. + */ + public static class Bind extends Cache { + + private final String source; + + Bind(String source) { + super(Format.BIND); + this.source = source; + } + + public String getSource() { + return this.source; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + if (!super.equals(obj)) { + return false; + } + Bind other = (Bind) obj; + return Objects.equals(this.source, other.source); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + ObjectUtils.nullSafeHashCode(this.source); + return result; + } + + @Override + public String toString() { + return this.format.getDescription() + " '" + this.source + "'"; + } + } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/EphemeralBuilder.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/EphemeralBuilder.java index 7a1ab1b77981..3ac1ee7a7857 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/EphemeralBuilder.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/EphemeralBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ImageBuildpack.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ImageBuildpack.java index 31c84cefc10e..7bb824edafe5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ImageBuildpack.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ImageBuildpack.java @@ -36,6 +36,7 @@ import org.springframework.boot.buildpack.platform.docker.type.Layer; import org.springframework.boot.buildpack.platform.docker.type.LayerId; import org.springframework.boot.buildpack.platform.io.IOConsumer; +import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.util.StreamUtils; /** @@ -115,31 +116,31 @@ private static class ExportedLayers { ExportedLayers(BuildpackResolverContext context, ImageReference imageReference) throws IOException { List layerFiles = new ArrayList<>(); - context.exportImageLayers(imageReference, (name, path) -> layerFiles.add(copyToTemp(path))); + context.exportImageLayers(imageReference, + (name, tarArchive) -> layerFiles.add(createLayerFile(tarArchive))); this.layerFiles = Collections.unmodifiableList(layerFiles); } - private Path copyToTemp(Path path) throws IOException { - Path outputPath = Files.createTempFile("create-builder-scratch-", null); - try (OutputStream out = Files.newOutputStream(outputPath)) { - copyLayerTar(path, out); + private Path createLayerFile(TarArchive tarArchive) throws IOException { + Path sourceTarFile = Files.createTempFile("create-builder-scratch-source-", null); + try (OutputStream out = Files.newOutputStream(sourceTarFile)) { + tarArchive.writeTo(out); } - return outputPath; - } - - private void copyLayerTar(Path path, OutputStream out) throws IOException { - try (TarArchiveInputStream tarIn = new TarArchiveInputStream(Files.newInputStream(path)); - TarArchiveOutputStream tarOut = new TarArchiveOutputStream(out)) { - tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); - TarArchiveEntry entry = tarIn.getNextTarEntry(); - while (entry != null) { - tarOut.putArchiveEntry(entry); - StreamUtils.copy(tarIn, tarOut); - tarOut.closeArchiveEntry(); - entry = tarIn.getNextTarEntry(); + Path layerFile = Files.createTempFile("create-builder-scratch-", null); + try (TarArchiveOutputStream out = new TarArchiveOutputStream(Files.newOutputStream(layerFile))) { + try (TarArchiveInputStream in = new TarArchiveInputStream(Files.newInputStream(sourceTarFile))) { + out.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); + TarArchiveEntry entry = in.getNextEntry(); + while (entry != null) { + out.putArchiveEntry(entry); + StreamUtils.copy(in, out); + out.closeArchiveEntry(); + entry = in.getNextEntry(); + } + out.finish(); } - tarOut.finish(); } + return layerFile; } void apply(IOConsumer layers) throws IOException { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java index 133f8e765b18..98c58ae984b7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java @@ -18,6 +18,10 @@ import java.io.Closeable; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; import java.util.function.Consumer; import com.sun.jna.Platform; @@ -34,6 +38,7 @@ import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.util.Assert; +import org.springframework.util.FileSystemUtils; /** * A buildpack lifecycle used to run the build {@link Phase phases} needed to package an @@ -54,6 +59,8 @@ class Lifecycle implements Closeable { private static final String DOMAIN_SOCKET_PATH = "/var/run/docker.sock"; + private static final List DEFAULT_SECURITY_OPTIONS = List.of("label=disable"); + private final BuildLog log; private final DockerApi docker; @@ -68,16 +75,18 @@ class Lifecycle implements Closeable { private final ApiVersion platformVersion; - private final VolumeName layersVolume; + private final Cache layers; - private final VolumeName applicationVolume; + private final Cache application; - private final VolumeName buildCacheVolume; + private final Cache buildCache; - private final VolumeName launchCacheVolume; + private final Cache launchCache; private final String applicationDirectory; + private final List securityOptions; + private boolean executed; private boolean applicationVolumePopulated; @@ -99,44 +108,37 @@ class Lifecycle implements Closeable { this.builder = builder; this.lifecycleVersion = LifecycleVersion.parse(builder.getBuilderMetadata().getLifecycle().getVersion()); this.platformVersion = getPlatformVersion(builder.getBuilderMetadata().getLifecycle()); - this.layersVolume = createRandomVolumeName("pack-layers-"); - this.applicationVolume = createRandomVolumeName("pack-app-"); - this.buildCacheVolume = getBuildCacheVolumeName(request); - this.launchCacheVolume = getLaunchCacheVolumeName(request); + this.layers = getLayersBindingSource(request); + this.application = getApplicationBindingSource(request); + this.buildCache = getBuildCache(request); + this.launchCache = getLaunchCache(request); this.applicationDirectory = getApplicationDirectory(request); + this.securityOptions = getSecurityOptions(request); } - protected VolumeName createRandomVolumeName(String prefix) { - return VolumeName.random(prefix); - } - - private VolumeName getBuildCacheVolumeName(BuildRequest request) { + private Cache getBuildCache(BuildRequest request) { if (request.getBuildCache() != null) { - return getVolumeName(request.getBuildCache()); + return request.getBuildCache(); } - return createCacheVolumeName(request, "build"); + return createVolumeCache(request, "build"); } - private VolumeName getLaunchCacheVolumeName(BuildRequest request) { + private Cache getLaunchCache(BuildRequest request) { if (request.getLaunchCache() != null) { - return getVolumeName(request.getLaunchCache()); + return request.getLaunchCache(); } - return createCacheVolumeName(request, "launch"); - } - - private VolumeName getVolumeName(Cache cache) { - if (cache.getVolume() != null) { - return VolumeName.of(cache.getVolume().getName()); - } - return null; + return createVolumeCache(request, "launch"); } private String getApplicationDirectory(BuildRequest request) { return (request.getApplicationDirectory() != null) ? request.getApplicationDirectory() : Directory.APPLICATION; } - private VolumeName createCacheVolumeName(BuildRequest request, String suffix) { - return VolumeName.basedOn(request.getName(), ImageReference::toLegacyString, "pack-cache-", "." + suffix, 6); + private List getSecurityOptions(BuildRequest request) { + if (request.getSecurityOptions() != null) { + return request.getSecurityOptions(); + } + return (Platform.isWindows()) ? Collections.emptyList() : DEFAULT_SECURITY_OPTIONS; } private ApiVersion getPlatformVersion(BuilderMetadata.Lifecycle lifecycle) { @@ -155,51 +157,149 @@ private ApiVersion getPlatformVersion(BuilderMetadata.Lifecycle lifecycle) { void execute() throws IOException { Assert.state(!this.executed, "Lifecycle has already been executed"); this.executed = true; - this.log.executingLifecycle(this.request, this.lifecycleVersion, this.buildCacheVolume); + this.log.executingLifecycle(this.request, this.lifecycleVersion, this.buildCache); if (this.request.isCleanCache()) { - deleteVolume(this.buildCacheVolume); + deleteCache(this.buildCache); + } + if (this.request.isTrustBuilder()) { + run(createPhase()); + } + else { + run(analyzePhase()); + run(detectPhase()); + if (!this.request.isCleanCache()) { + run(restorePhase()); + } + else { + this.log.skippingPhase("restorer", "because 'cleanCache' is enabled"); + } + run(buildPhase()); + run(exportPhase()); } - run(createPhase()); this.log.executedLifecycle(this.request); } private Phase createPhase() { Phase phase = new Phase("creator", isVerboseLogging()); - phase.withDaemonAccess(); + phase.withApp(this.applicationDirectory, + Binding.from(getCacheBindingSource(this.application), this.applicationDirectory)); + phase.withPlatform(Directory.PLATFORM); + phase.withRunImage(this.request.getRunImage()); + phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS)); + phase.withBuildCache(Directory.CACHE, Binding.from(getCacheBindingSource(this.buildCache), Directory.CACHE)); + phase.withLaunchCache(Directory.LAUNCH_CACHE, + Binding.from(getCacheBindingSource(this.launchCache), Directory.LAUNCH_CACHE)); configureDaemonAccess(phase); - phase.withLogLevelArg(); - phase.withArgs("-app", this.applicationDirectory); - phase.withArgs("-platform", Directory.PLATFORM); - phase.withArgs("-run-image", this.request.getRunImage()); - phase.withArgs("-layers", Directory.LAYERS); - phase.withArgs("-cache-dir", Directory.CACHE); - phase.withArgs("-launch-cache", Directory.LAUNCH_CACHE); - phase.withArgs("-daemon"); if (this.request.isCleanCache()) { - phase.withArgs("-skip-restore"); + phase.withSkipRestore(); } if (requiresProcessTypeDefault()) { - phase.withArgs("-process-type=web"); + phase.withProcessType("web"); } - phase.withArgs(this.request.getName()); - phase.withBinding(Binding.from(this.layersVolume, Directory.LAYERS)); - phase.withBinding(Binding.from(this.applicationVolume, this.applicationDirectory)); - phase.withBinding(Binding.from(this.buildCacheVolume, Directory.CACHE)); - phase.withBinding(Binding.from(this.launchCacheVolume, Directory.LAUNCH_CACHE)); - if (this.request.getBindings() != null) { - this.request.getBindings().forEach(phase::withBinding); + phase.withImageName(this.request.getName()); + configureOptions(phase); + configureCreatedDate(phase); + return phase; + + } + + private Phase analyzePhase() { + Phase phase = new Phase("analyzer", isVerboseLogging()); + configureDaemonAccess(phase); + phase.withLaunchCache(Directory.LAUNCH_CACHE, + Binding.from(getCacheBindingSource(this.launchCache), Directory.LAUNCH_CACHE)); + phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS)); + phase.withRunImage(this.request.getRunImage()); + phase.withImageName(this.request.getName()); + configureOptions(phase); + return phase; + } + + private Phase detectPhase() { + Phase phase = new Phase("detector", isVerboseLogging()); + phase.withApp(this.applicationDirectory, + Binding.from(getCacheBindingSource(this.application), this.applicationDirectory)); + phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS)); + phase.withPlatform(Directory.PLATFORM); + configureOptions(phase); + return phase; + } + + private Phase restorePhase() { + Phase phase = new Phase("restorer", isVerboseLogging()); + configureDaemonAccess(phase); + phase.withBuildCache(Directory.CACHE, Binding.from(getCacheBindingSource(this.buildCache), Directory.CACHE)); + phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS)); + configureOptions(phase); + return phase; + } + + private Phase buildPhase() { + Phase phase = new Phase("builder", isVerboseLogging()); + phase.withApp(this.applicationDirectory, + Binding.from(getCacheBindingSource(this.application), this.applicationDirectory)); + phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS)); + phase.withPlatform(Directory.PLATFORM); + configureOptions(phase); + return phase; + } + + private Phase exportPhase() { + Phase phase = new Phase("exporter", isVerboseLogging()); + configureDaemonAccess(phase); + phase.withApp(this.applicationDirectory, + Binding.from(getCacheBindingSource(this.application), this.applicationDirectory)); + phase.withBuildCache(Directory.CACHE, Binding.from(getCacheBindingSource(this.buildCache), Directory.CACHE)); + phase.withLaunchCache(Directory.LAUNCH_CACHE, + Binding.from(getCacheBindingSource(this.launchCache), Directory.LAUNCH_CACHE)); + phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS)); + if (requiresProcessTypeDefault()) { + phase.withProcessType("web"); } - phase.withEnv(PLATFORM_API_VERSION_KEY, this.platformVersion.toString()); - if (this.request.getNetwork() != null) { - phase.withNetworkMode(this.request.getNetwork()); + phase.withImageName(this.request.getName()); + configureOptions(phase); + configureCreatedDate(phase); + return phase; + } + + private Cache getLayersBindingSource(BuildRequest request) { + if (request.getBuildWorkspace() != null) { + return getBuildWorkspaceBindingSource(request.getBuildWorkspace(), "layers"); } - if (this.request.getCreatedDate() != null) { - phase.withEnv(SOURCE_DATE_EPOCH_KEY, Long.toString(this.request.getCreatedDate().getEpochSecond())); + return createVolumeCache("pack-layers-"); + } + + private Cache getApplicationBindingSource(BuildRequest request) { + if (request.getBuildWorkspace() != null) { + return getBuildWorkspaceBindingSource(request.getBuildWorkspace(), "app"); } - return phase; + return createVolumeCache("pack-app-"); + } + + private Cache getBuildWorkspaceBindingSource(Cache buildWorkspace, String suffix) { + return (buildWorkspace.getVolume() != null) ? Cache.volume(buildWorkspace.getVolume().getName() + "-" + suffix) + : Cache.bind(buildWorkspace.getBind().getSource() + "-" + suffix); + } + + private String getCacheBindingSource(Cache cache) { + return (cache.getVolume() != null) ? cache.getVolume().getName() : cache.getBind().getSource(); + } + + private Cache createVolumeCache(String prefix) { + return Cache.volume(createRandomVolumeName(prefix)); + } + + private Cache createVolumeCache(BuildRequest request, String suffix) { + return Cache.volume( + VolumeName.basedOn(request.getName(), ImageReference::toLegacyString, "pack-cache-", "." + suffix, 6)); + } + + protected VolumeName createRandomVolumeName(String prefix) { + return VolumeName.random(prefix); } private void configureDaemonAccess(Phase phase) { + phase.withDaemonAccess(); if (this.dockerHost != null) { if (this.dockerHost.isRemote()) { phase.withEnv("DOCKER_HOST", this.dockerHost.getAddress()); @@ -215,9 +315,25 @@ private void configureDaemonAccess(Phase phase) { else { phase.withBinding(Binding.from(DOMAIN_SOCKET_PATH, DOMAIN_SOCKET_PATH)); } - if (!Platform.isWindows()) { - phase.withSecurityOption("label=disable"); + if (this.securityOptions != null) { + this.securityOptions.forEach(phase::withSecurityOption); + } + } + + private void configureCreatedDate(Phase phase) { + if (this.request.getCreatedDate() != null) { + phase.withEnv(SOURCE_DATE_EPOCH_KEY, Long.toString(this.request.getCreatedDate().getEpochSecond())); + } + } + + private void configureOptions(Phase phase) { + if (this.request.getBindings() != null) { + this.request.getBindings().forEach(phase::withBinding); } + if (this.request.getNetwork() != null) { + phase.withNetworkMode(this.request.getNetwork()); + } + phase.withEnv(PLATFORM_API_VERSION_KEY, this.platformVersion.toString()); } private boolean isVerboseLogging() { @@ -231,7 +347,7 @@ private boolean requiresProcessTypeDefault() { private void run(Phase phase) throws IOException { Consumer logConsumer = this.log.runningPhase(this.request, phase.getName()); ContainerConfig containerConfig = ContainerConfig.of(this.builder.getName(), phase::apply); - ContainerReference reference = createContainer(containerConfig); + ContainerReference reference = createContainer(containerConfig, phase.requiresApp()); try { this.docker.container().start(reference); this.docker.container().logs(reference, logConsumer::accept); @@ -245,11 +361,14 @@ private void run(Phase phase) throws IOException { } } - private ContainerReference createContainer(ContainerConfig config) throws IOException { - if (this.applicationVolumePopulated) { + private ContainerReference createContainer(ContainerConfig config, boolean requiresAppUpload) throws IOException { + if (!requiresAppUpload || this.applicationVolumePopulated) { return this.docker.container().create(config); } try { + if (this.application.getBind() != null) { + Files.createDirectories(Path.of(this.application.getBind().getSource())); + } TarArchive applicationContent = this.request.getApplicationContent(this.builder.getBuildOwner()); return this.docker.container() .create(config, ContainerContent.of(applicationContent, this.applicationDirectory)); @@ -261,14 +380,32 @@ private ContainerReference createContainer(ContainerConfig config) throws IOExce @Override public void close() throws IOException { - deleteVolume(this.layersVolume); - deleteVolume(this.applicationVolume); + deleteCache(this.layers); + deleteCache(this.application); + } + + private void deleteCache(Cache cache) throws IOException { + if (cache.getVolume() != null) { + deleteVolume(cache.getVolume().getVolumeName()); + } + if (cache.getBind() != null) { + deleteBind(cache.getBind()); + } } private void deleteVolume(VolumeName name) throws IOException { this.docker.volume().delete(name, true); } + private void deleteBind(Cache.Bind bind) { + try { + FileSystemUtils.deleteRecursively(Path.of(bind.getSource())); + } + catch (Exception ex) { + this.log.failedCleaningWorkDir(bind, ex); + } + } + /** * Common directories used by the various phases. */ @@ -280,8 +417,7 @@ private static final class Directory { *

* Maps to the {@code } concept in the * buildpack - * specification and the {@code -layers} argument from the reference lifecycle - * implementation. + * specification and the {@code -layers} argument to lifecycle phases. */ static final String LAYERS = "/layers"; @@ -308,8 +444,7 @@ private static final class Directory { *

* Maps to the {@code /env} and {@code /#} concepts in the * buildpack - * specification and the {@code -platform} argument from the reference - * lifecycle implementation. + * specification and the {@code -platform} argument to lifecycle phases. */ static final String PLATFORM = "/platform"; @@ -318,8 +453,7 @@ private static final class Directory { * image {@link BuildRequest#getName() name} being built, and is persistent across * invocations even if the application content has changed. *

- * Maps to the {@code -path} argument from the reference lifecycle implementation - * cache and restore phases + * Maps to the {@code -path} argument to lifecycle phases. */ static final String CACHE = "/cache"; @@ -328,8 +462,7 @@ private static final class Directory { * based on the image {@link BuildRequest#getName() name} being built, and is * persistent across invocations even if the application content has changed. *

- * Maps to the {@code -launch-cache} argument from the reference lifecycle - * implementation export phase + * Maps to the {@code -launch-cache} argument to lifecycle phases. */ static final String LAUNCH_CACHE = "/launch-cache"; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Phase.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Phase.java index 80bacfad48a5..439511409d97 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Phase.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Phase.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig; +import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.util.StringUtils; /** @@ -37,8 +38,6 @@ class Phase { private final String name; - private final boolean verboseLogging; - private boolean daemonAccess = false; private final List args = new ArrayList<>(); @@ -51,6 +50,8 @@ class Phase { private String networkMode; + private boolean requiresApp = false; + /** * Create a new {@link Phase} instance. * @param name the name of the phase @@ -58,22 +59,65 @@ class Phase { */ Phase(String name, boolean verboseLogging) { this.name = name; - this.verboseLogging = verboseLogging; + withLogLevelArg(verboseLogging); + } + + void withApp(String path, Binding binding) { + withArgs("-app", path); + withBinding(binding); + this.requiresApp = true; + } + + void withBuildCache(String path, Binding binding) { + withArgs("-cache-dir", path); + withBinding(binding); } /** * Update this phase with Docker daemon access. */ void withDaemonAccess() { + this.withArgs("-daemon"); this.daemonAccess = true; } + void withImageName(ImageReference imageName) { + withArgs(imageName); + } + + void withLaunchCache(String path, Binding binding) { + withArgs("-launch-cache", path); + withBinding(binding); + } + + void withLayers(String path, Binding binding) { + withArgs("-layers", path); + withBinding(binding); + } + + void withPlatform(String path) { + withArgs("-platform", path); + } + + void withProcessType(String type) { + withArgs("-process-type", type); + } + + void withRunImage(ImageReference runImage) { + withArgs("-run-image", runImage); + } + + void withSkipRestore() { + withArgs("-skip-restore"); + } + /** * Update this phase with a debug log level arguments if verbose logging has been * requested. + * @param verboseLogging if verbose logging is requested */ - void withLogLevelArg() { - if (this.verboseLogging) { + private void withLogLevelArg(boolean verboseLogging) { + if (verboseLogging) { this.args.add("-log-level"); this.args.add("debug"); } @@ -128,6 +172,10 @@ String getName() { return this.name; } + boolean requiresApp() { + return this.requiresApp; + } + @Override public String toString() { return this.name; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/StackId.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/StackId.java index b3e344ee53a4..327ca42fadac 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/StackId.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/StackId.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** * A Stack ID. @@ -48,6 +47,10 @@ public boolean equals(Object obj) { return this.value.equals(((StackId) obj).value); } + boolean hasId() { + return this.value != null; + } + @Override public int hashCode() { return this.value.hashCode(); @@ -75,7 +78,6 @@ static StackId fromImage(Image image) { */ private static StackId fromImageConfig(ImageConfig imageConfig) { String value = imageConfig.getLabels().get(LABEL_NAME); - Assert.state(StringUtils.hasText(value), () -> "Missing '" + LABEL_NAME + "' stack label"); return new StackId(value); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/TarGzipBuildpack.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/TarGzipBuildpack.java index b485552efcd8..4174166f3d27 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/TarGzipBuildpack.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/TarGzipBuildpack.java @@ -90,13 +90,13 @@ private void copyAndRebaseEntries(OutputStream outputStream) throws IOException new GzipCompressorInputStream(Files.newInputStream(this.path))); TarArchiveOutputStream output = new TarArchiveOutputStream(outputStream)) { writeBasePathEntries(output, basePath); - TarArchiveEntry entry = tar.getNextTarEntry(); + TarArchiveEntry entry = tar.getNextEntry(); while (entry != null) { entry.setName(basePath + "/" + entry.getName()); output.putArchiveEntry(entry); StreamUtils.copy(tar, output); output.closeArchiveEntry(); - entry = tar.getNextTarEntry(); + entry = tar.getNextEntry(); } output.finish(); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java index 4efc4a0c7cb2..6e7263af8836 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java @@ -16,16 +16,10 @@ package org.springframework.boot.buildpack.platform.docker; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; @@ -33,13 +27,10 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; -import org.apache.commons.compress.archivers.tar.TarArchiveEntry; -import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.hc.core5.net.URIBuilder; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport; import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport.Response; import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig; @@ -48,7 +39,6 @@ import org.springframework.boot.buildpack.platform.docker.type.ContainerStatus; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageArchive; -import org.springframework.boot.buildpack.platform.docker.type.ImageArchiveManifest; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.boot.buildpack.platform.io.IOBiConsumer; @@ -56,7 +46,6 @@ import org.springframework.boot.buildpack.platform.json.JsonStream; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper; import org.springframework.util.Assert; -import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; /** @@ -96,7 +85,7 @@ public DockerApi() { * @param dockerHost the Docker daemon host information * @since 2.4.0 */ - public DockerApi(DockerHost dockerHost) { + public DockerApi(DockerHostConfiguration dockerHost) { this(HttpTransport.create(dockerHost)); } @@ -263,49 +252,50 @@ public void load(ImageArchive archive, UpdateListener list } /** - * Export the layers of an image as {@link TarArchive}s. + * Export the layers of an image as paths to layer tar files. * @param reference the reference to export - * @param exports a consumer to receive the layers (contents can only be accessed - * during the callback) + * @param exports a consumer to receive the layer tar file paths (file can only be + * accessed during the callback) * @throws IOException on IO error + * @since 2.7.10 + * @deprecated since 3.2.6 for removal in 3.5.0 in favor of + * {@link #exportLayers(ImageReference, IOBiConsumer)} */ - public void exportLayers(ImageReference reference, IOBiConsumer exports) - throws IOException { - exportLayerFiles(reference, (name, path) -> { - try (InputStream in = Files.newInputStream(path)) { - TarArchive archive = (out) -> StreamUtils.copy(in, out); - exports.accept(name, archive); + @Deprecated(since = "3.2.6", forRemoval = true) + public void exportLayerFiles(ImageReference reference, IOBiConsumer exports) throws IOException { + Assert.notNull(reference, "Reference must not be null"); + Assert.notNull(exports, "Exports must not be null"); + exportLayers(reference, (name, archive) -> { + Path path = Files.createTempFile("docker-export-layer-files-", null); + try { + try (OutputStream out = Files.newOutputStream(path)) { + archive.writeTo(out); + exports.accept(name, path); + } + } + finally { + Files.delete(path); } }); } /** - * Export the layers of an image as paths to layer tar files. + * Export the layers of an image as {@link TarArchive TarArchives}. * @param reference the reference to export - * @param exports a consumer to receive the layer tar file paths (file can only be - * accessed during the callback) + * @param exports a consumer to receive the layers (contents can only be accessed + * during the callback) * @throws IOException on IO error - * @since 2.7.10 */ - public void exportLayerFiles(ImageReference reference, IOBiConsumer exports) throws IOException { + public void exportLayers(ImageReference reference, IOBiConsumer exports) + throws IOException { Assert.notNull(reference, "Reference must not be null"); Assert.notNull(exports, "Exports must not be null"); - URI saveUri = buildUrl("/images/" + reference + "/get"); - Response response = http().get(saveUri); - Path exportFile = copyToTemp(response.getContent()); - ImageArchiveManifest manifest = getManifest(reference, exportFile); - try (TarArchiveInputStream tar = new TarArchiveInputStream(new FileInputStream(exportFile.toFile()))) { - TarArchiveEntry entry = tar.getNextTarEntry(); - while (entry != null) { - if (manifestContainsLayerEntry(manifest, entry.getName())) { - Path layerFile = copyToTemp(tar); - exports.accept(entry.getName(), layerFile); - Files.delete(layerFile); - } - entry = tar.getNextTarEntry(); + URI uri = buildUrl("/images/" + reference + "/get"); + try (Response response = http().get(uri)) { + try (ExportedImageTar exportedImageTar = new ExportedImageTar(reference, response.getContent())) { + exportedImageTar.exportLayers(exports); } } - Files.delete(exportFile); } /** @@ -345,37 +335,6 @@ public void tag(ImageReference sourceReference, ImageReference targetReference) http().post(uri).close(); } - private ImageArchiveManifest getManifest(ImageReference reference, Path exportFile) throws IOException { - try (TarArchiveInputStream tar = new TarArchiveInputStream(new FileInputStream(exportFile.toFile()))) { - TarArchiveEntry entry = tar.getNextTarEntry(); - while (entry != null) { - if (entry.getName().equals("manifest.json")) { - return readManifest(tar); - } - entry = tar.getNextTarEntry(); - } - } - throw new IllegalArgumentException("Manifest not found in image " + reference); - } - - private ImageArchiveManifest readManifest(TarArchiveInputStream tar) throws IOException { - String manifestContent = new BufferedReader(new InputStreamReader(tar, StandardCharsets.UTF_8)).lines() - .collect(Collectors.joining()); - return ImageArchiveManifest.of(new ByteArrayInputStream(manifestContent.getBytes(StandardCharsets.UTF_8))); - } - - private Path copyToTemp(InputStream in) throws IOException { - Path path = Files.createTempFile("create-builder-scratch-", null); - try (OutputStream out = Files.newOutputStream(path)) { - StreamUtils.copy(in, out); - } - return path; - } - - private boolean manifestContainsLayerEntry(ImageArchiveManifest manifest, String layerId) { - return manifest.getEntries().stream().anyMatch((content) -> content.getLayers().contains(layerId)); - } - } /** @@ -458,8 +417,9 @@ public void logs(ContainerReference reference, UpdateListener li public ContainerStatus wait(ContainerReference reference) throws IOException { Assert.notNull(reference, "Reference must not be null"); URI uri = buildUrl("/containers/" + reference + "/wait"); - Response response = http().post(uri); - return ContainerStatus.of(response.getContent()); + try (Response response = http().post(uri)) { + return ContainerStatus.of(response.getContent()); + } } /** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ExportedImageTar.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ExportedImageTar.java new file mode 100644 index 000000000000..c563b3734f0c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ExportedImageTar.java @@ -0,0 +1,261 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.buildpack.platform.docker; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; + +import org.springframework.boot.buildpack.platform.docker.type.BlobReference; +import org.springframework.boot.buildpack.platform.docker.type.ImageArchiveIndex; +import org.springframework.boot.buildpack.platform.docker.type.ImageArchiveManifest; +import org.springframework.boot.buildpack.platform.docker.type.ImageReference; +import org.springframework.boot.buildpack.platform.docker.type.Manifest; +import org.springframework.boot.buildpack.platform.docker.type.ManifestList; +import org.springframework.boot.buildpack.platform.io.IOBiConsumer; +import org.springframework.boot.buildpack.platform.io.TarArchive; +import org.springframework.boot.buildpack.platform.io.TarArchive.Compression; +import org.springframework.util.Assert; +import org.springframework.util.function.ThrowingFunction; + +/** + * Internal helper class used by the {@link DockerApi} to extract layers from an exported + * image tar. + * + * @author Phillip Webb + * @author Moritz Halbritter + * @author Scott Frederick + */ +class ExportedImageTar implements Closeable { + + private final Path tarFile; + + private final LayerArchiveFactory layerArchiveFactory; + + ExportedImageTar(ImageReference reference, InputStream inputStream) throws IOException { + this.tarFile = Files.createTempFile("docker-layers-", null); + Files.copy(inputStream, this.tarFile, StandardCopyOption.REPLACE_EXISTING); + this.layerArchiveFactory = LayerArchiveFactory.create(reference, this.tarFile); + } + + void exportLayers(IOBiConsumer exports) throws IOException { + try (TarArchiveInputStream tar = openTar(this.tarFile)) { + TarArchiveEntry entry = tar.getNextEntry(); + while (entry != null) { + TarArchive layerArchive = this.layerArchiveFactory.getLayerArchive(tar, entry); + if (layerArchive != null) { + exports.accept(entry.getName(), layerArchive); + } + entry = tar.getNextEntry(); + } + } + } + + private static TarArchiveInputStream openTar(Path path) throws IOException { + return new TarArchiveInputStream(Files.newInputStream(path)); + } + + @Override + public void close() throws IOException { + Files.delete(this.tarFile); + } + + /** + * Factory class used to create a {@link TarArchiveEntry} for layer. + */ + private abstract static class LayerArchiveFactory { + + /** + * Create a new {@link TarArchive} if the given entry represents a layer. + * @param tar the tar input stream + * @param entry the candidate entry + * @return a new {@link TarArchive} instance or {@code null} if this entry is not + * a layer. + */ + abstract TarArchive getLayerArchive(TarArchiveInputStream tar, TarArchiveEntry entry); + + /** + * Create a new {@link LayerArchiveFactory} for the given tar file using either + * the {@code index.json} or {@code manifest.json} to detect layers. + * @param reference the image that was referenced + * @param tarFile the source tar file + * @return a new {@link LayerArchiveFactory} instance + * @throws IOException on IO error + */ + static LayerArchiveFactory create(ImageReference reference, Path tarFile) throws IOException { + try (TarArchiveInputStream tar = openTar(tarFile)) { + ImageArchiveIndex index = null; + ImageArchiveManifest manifest = null; + TarArchiveEntry entry = tar.getNextEntry(); + while (entry != null) { + if ("index.json".equals(entry.getName())) { + index = ImageArchiveIndex.of(tar); + break; + } + if ("manifest.json".equals(entry.getName())) { + manifest = ImageArchiveManifest.of(tar); + } + entry = tar.getNextEntry(); + } + Assert.state(index != null || manifest != null, + "Exported image '%s' does not contain 'index.json' or 'manifest.json'".formatted(reference)); + return (index != null) ? new IndexLayerArchiveFactory(tarFile, index) + : new ManifestLayerArchiveFactory(tarFile, manifest); + } + } + + } + + /** + * {@link LayerArchiveFactory} backed by the more recent {@code index.json} file. + */ + private static class IndexLayerArchiveFactory extends LayerArchiveFactory { + + private final Map layerMediaTypes; + + IndexLayerArchiveFactory(Path tarFile, ImageArchiveIndex index) throws IOException { + Set manifestDigests = getDigests(index, this::isManifest); + List manifestLists = getManifestLists(tarFile, getDigests(index, this::isManifestList)); + List manifests = getManifests(tarFile, manifestDigests, manifestLists); + this.layerMediaTypes = manifests.stream() + .flatMap((manifest) -> manifest.getLayers().stream()) + .collect(Collectors.toMap(this::getEntryName, BlobReference::getMediaType)); + } + + private Set getDigests(ImageArchiveIndex index, Predicate predicate) { + return index.getManifests() + .stream() + .filter(predicate) + .map(BlobReference::getDigest) + .collect(Collectors.toUnmodifiableSet()); + } + + private List getManifestLists(Path tarFile, Set digests) throws IOException { + return getDigestMatches(tarFile, digests, ManifestList::of); + } + + private List getManifests(Path tarFile, Set manifestDigests, List manifestLists) + throws IOException { + Set digests = new HashSet<>(manifestDigests); + manifestLists.stream() + .flatMap(ManifestList::streamManifests) + .filter(this::isManifest) + .map(BlobReference::getDigest) + .forEach(digests::add); + return getDigestMatches(tarFile, digests, Manifest::of); + } + + private List getDigestMatches(Path tarFile, Set digests, + ThrowingFunction factory) throws IOException { + if (digests.isEmpty()) { + return Collections.emptyList(); + } + Set names = digests.stream().map(this::getEntryName).collect(Collectors.toUnmodifiableSet()); + List result = new ArrayList<>(); + try (TarArchiveInputStream tar = openTar(tarFile)) { + TarArchiveEntry entry = tar.getNextEntry(); + while (entry != null) { + if (names.contains(entry.getName())) { + result.add(factory.apply(tar)); + } + entry = tar.getNextEntry(); + } + } + return Collections.unmodifiableList(result); + } + + private boolean isManifest(BlobReference reference) { + return isJsonWithPrefix(reference.getMediaType(), "application/vnd.oci.image.manifest.v") + || isJsonWithPrefix(reference.getMediaType(), "application/vnd.docker.distribution.manifest.v"); + } + + private boolean isManifestList(BlobReference reference) { + return isJsonWithPrefix(reference.getMediaType(), "application/vnd.docker.distribution.manifest.list.v"); + } + + private boolean isJsonWithPrefix(String mediaType, String prefix) { + return mediaType.startsWith(prefix) && mediaType.endsWith("+json"); + } + + private String getEntryName(BlobReference reference) { + return getEntryName(reference.getDigest()); + } + + private String getEntryName(String digest) { + return "blobs/" + digest.replace(':', '/'); + } + + @Override + TarArchive getLayerArchive(TarArchiveInputStream tar, TarArchiveEntry entry) { + String mediaType = this.layerMediaTypes.get(entry.getName()); + if (mediaType == null) { + return null; + } + return TarArchive.fromInputStream(tar, getCompression(mediaType)); + } + + private Compression getCompression(String mediaType) { + if (mediaType.endsWith(".tar.gzip")) { + return Compression.GZIP; + } + if (mediaType.endsWith(".tar.zstd")) { + return Compression.ZSTD; + } + return Compression.NONE; + } + + } + + /** + * {@link LayerArchiveFactory} backed by the legacy {@code manifest.json} file. + */ + private static class ManifestLayerArchiveFactory extends LayerArchiveFactory { + + private Set layers; + + ManifestLayerArchiveFactory(Path tarFile, ImageArchiveManifest manifest) { + this.layers = manifest.getEntries() + .stream() + .flatMap((entry) -> entry.getLayers().stream()) + .collect(Collectors.toUnmodifiableSet()); + } + + @Override + TarArchive getLayerArchive(TarArchiveInputStream tar, TarArchiveEntry entry) { + if (!this.layers.contains(entry.getName())) { + return null; + } + return TarArchive.fromInputStream(tar, Compression.NONE); + } + + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/TotalProgressBar.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/TotalProgressBar.java index 63a137abbc08..a8637968799a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/TotalProgressBar.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/TotalProgressBar.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java index b82a6b28ac22..fa47c349fbd1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ */ public final class DockerConfiguration { - private final DockerHost host; + private final DockerHostConfiguration host; private final DockerRegistryAuthentication builderAuthentication; @@ -39,7 +39,7 @@ public DockerConfiguration() { this(null, null, null, false); } - private DockerConfiguration(DockerHost host, DockerRegistryAuthentication builderAuthentication, + private DockerConfiguration(DockerHostConfiguration host, DockerRegistryAuthentication builderAuthentication, DockerRegistryAuthentication publishAuthentication, boolean bindHostToBuilder) { this.host = host; this.builderAuthentication = builderAuthentication; @@ -47,7 +47,7 @@ private DockerConfiguration(DockerHost host, DockerRegistryAuthentication builde this.bindHostToBuilder = bindHostToBuilder; } - public DockerHost getHost() { + public DockerHostConfiguration getHost() { return this.host; } @@ -65,7 +65,13 @@ public DockerRegistryAuthentication getPublishRegistryAuthentication() { public DockerConfiguration withHost(String address, boolean secure, String certificatePath) { Assert.notNull(address, "Address must not be null"); - return new DockerConfiguration(new DockerHost(address, secure, certificatePath), this.builderAuthentication, + return new DockerConfiguration(DockerHostConfiguration.forAddress(address, secure, certificatePath), + this.builderAuthentication, this.publishAuthentication, this.bindHostToBuilder); + } + + public DockerConfiguration withContext(String context) { + Assert.notNull(context, "Context must not be null"); + return new DockerConfiguration(DockerHostConfiguration.forContext(context), this.builderAuthentication, this.publishAuthentication, this.bindHostToBuilder); } @@ -107,4 +113,51 @@ public DockerConfiguration withEmptyPublishRegistryAuthentication() { new DockerRegistryUserAuthentication("", "", "", ""), this.bindHostToBuilder); } + public static class DockerHostConfiguration { + + private final String address; + + private final String context; + + private final boolean secure; + + private final String certificatePath; + + public DockerHostConfiguration(String address, String context, boolean secure, String certificatePath) { + this.address = address; + this.context = context; + this.secure = secure; + this.certificatePath = certificatePath; + } + + public String getAddress() { + return this.address; + } + + public String getContext() { + return this.context; + } + + public boolean isSecure() { + return this.secure; + } + + public String getCertificatePath() { + return this.certificatePath; + } + + public static DockerHostConfiguration forAddress(String address) { + return new DockerHostConfiguration(address, null, false, null); + } + + public static DockerHostConfiguration forAddress(String address, boolean secure, String certificatePath) { + return new DockerHostConfiguration(address, null, secure, certificatePath); + } + + static DockerHostConfiguration forContext(String context) { + return new DockerHostConfiguration(null, context, false, null); + } + + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java new file mode 100644 index 000000000000..9ab0fef20192 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java @@ -0,0 +1,211 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.buildpack.platform.docker.configuration; + +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HexFormat; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.NullNode; + +import org.springframework.boot.buildpack.platform.json.MappedObject; +import org.springframework.boot.buildpack.platform.json.SharedObjectMapper; +import org.springframework.boot.buildpack.platform.system.Environment; + +/** + * Docker configuration stored in metadata files managed by the Docker CLI. + * + * @author Scott Frederick + */ +final class DockerConfigurationMetadata { + + private static final String DOCKER_CONFIG = "DOCKER_CONFIG"; + + private static final String DEFAULT_CONTEXT = "default"; + + private static final String CONFIG_DIR = ".docker"; + + private static final String CONTEXTS_DIR = "contexts"; + + private static final String META_DIR = "meta"; + + private static final String TLS_DIR = "tls"; + + private static final String DOCKER_ENDPOINT = "docker"; + + private static final String CONFIG_FILE_NAME = "config.json"; + + private static final String CONTEXT_FILE_NAME = "meta.json"; + + private final String configLocation; + + private final DockerConfig config; + + private final DockerContext context; + + private DockerConfigurationMetadata(String configLocation, DockerConfig config, DockerContext context) { + this.configLocation = configLocation; + this.config = config; + this.context = context; + } + + DockerConfig getConfiguration() { + return this.config; + } + + DockerContext getContext() { + return this.context; + } + + DockerContext forContext(String context) { + return createDockerContext(this.configLocation, context); + } + + static DockerConfigurationMetadata from(Environment environment) { + String configLocation = (environment.get(DOCKER_CONFIG) != null) ? environment.get(DOCKER_CONFIG) + : Path.of(System.getProperty("user.home"), CONFIG_DIR).toString(); + DockerConfig dockerConfig = createDockerConfig(configLocation); + DockerContext dockerContext = createDockerContext(configLocation, dockerConfig.getCurrentContext()); + return new DockerConfigurationMetadata(configLocation, dockerConfig, dockerContext); + } + + private static DockerConfig createDockerConfig(String configLocation) { + Path path = Path.of(configLocation, CONFIG_FILE_NAME); + if (!path.toFile().exists()) { + return DockerConfig.empty(); + } + try { + return DockerConfig.fromJson(readPathContent(path)); + } + catch (JsonProcessingException ex) { + throw new IllegalStateException("Error parsing Docker configuration file '" + path + "'", ex); + } + } + + private static DockerContext createDockerContext(String configLocation, String currentContext) { + if (currentContext == null || DEFAULT_CONTEXT.equals(currentContext)) { + return DockerContext.empty(); + } + Path metaPath = Path.of(configLocation, CONTEXTS_DIR, META_DIR, asHash(currentContext), CONTEXT_FILE_NAME); + Path tlsPath = Path.of(configLocation, CONTEXTS_DIR, TLS_DIR, asHash(currentContext), DOCKER_ENDPOINT); + if (!metaPath.toFile().exists()) { + throw new IllegalArgumentException("Docker context '" + currentContext + "' does not exist"); + } + try { + DockerContext context = DockerContext.fromJson(readPathContent(metaPath)); + if (tlsPath.toFile().isDirectory()) { + return context.withTlsPath(tlsPath.toString()); + } + return context; + } + catch (JsonProcessingException ex) { + throw new IllegalStateException("Error parsing Docker context metadata file '" + metaPath + "'", ex); + } + } + + private static String asHash(String currentContext) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(currentContext.getBytes(StandardCharsets.UTF_8)); + return HexFormat.of().formatHex(hash); + } + catch (NoSuchAlgorithmException ex) { + return null; + } + } + + private static String readPathContent(Path path) { + try { + return Files.readString(path); + } + catch (IOException ex) { + throw new IllegalStateException("Error reading Docker configuration file '" + path + "'", ex); + } + } + + static final class DockerConfig extends MappedObject { + + private final String currentContext; + + private DockerConfig(JsonNode node) { + super(node, MethodHandles.lookup()); + this.currentContext = valueAt("/currentContext", String.class); + } + + String getCurrentContext() { + return this.currentContext; + } + + static DockerConfig fromJson(String json) throws JsonProcessingException { + return new DockerConfig(SharedObjectMapper.get().readTree(json)); + } + + static DockerConfig empty() { + return new DockerConfig(NullNode.instance); + } + + } + + static final class DockerContext extends MappedObject { + + private final String dockerHost; + + private final Boolean skipTlsVerify; + + private final String tlsPath; + + private DockerContext(JsonNode node, String tlsPath) { + super(node, MethodHandles.lookup()); + this.dockerHost = valueAt("/Endpoints/" + DOCKER_ENDPOINT + "/Host", String.class); + this.skipTlsVerify = valueAt("/Endpoints/" + DOCKER_ENDPOINT + "/SkipTLSVerify", Boolean.class); + this.tlsPath = tlsPath; + } + + String getDockerHost() { + return this.dockerHost; + } + + Boolean isTlsVerify() { + return this.skipTlsVerify != null && !this.skipTlsVerify; + } + + String getTlsPath() { + return this.tlsPath; + } + + DockerContext withTlsPath(String tlsPath) { + return new DockerContext(this.getNode(), tlsPath); + } + + static DockerContext fromJson(String json) throws JsonProcessingException { + return new DockerContext(SharedObjectMapper.get().readTree(json), null); + } + + static DockerContext empty() { + return new DockerContext(NullNode.instance, null); + } + + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerHost.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerHost.java index 3db5f5541d57..8d6d381feba4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerHost.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerHost.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHost.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHost.java index 95272b19d0d3..e19d592df08d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHost.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHost.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import com.sun.jna.Platform; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerContext; import org.springframework.boot.buildpack.platform.system.Environment; /** @@ -43,6 +45,12 @@ public class ResolvedDockerHost extends DockerHost { private static final String DOCKER_CERT_PATH = "DOCKER_CERT_PATH"; + private static final String DOCKER_CONTEXT = "DOCKER_CONTEXT"; + + ResolvedDockerHost(String address) { + super(address); + } + ResolvedDockerHost(String address, boolean secure, String certificatePath) { super(address, secure, certificatePath); } @@ -66,11 +74,20 @@ public boolean isLocalFileReference() { } } - public static ResolvedDockerHost from(DockerHost dockerHost) { + public static ResolvedDockerHost from(DockerHostConfiguration dockerHost) { return from(Environment.SYSTEM, dockerHost); } - static ResolvedDockerHost from(Environment environment, DockerHost dockerHost) { + static ResolvedDockerHost from(Environment environment, DockerHostConfiguration dockerHost) { + DockerConfigurationMetadata config = DockerConfigurationMetadata.from(environment); + if (environment.get(DOCKER_CONTEXT) != null) { + DockerContext context = config.forContext(environment.get(DOCKER_CONTEXT)); + return new ResolvedDockerHost(context.getDockerHost(), context.isTlsVerify(), context.getTlsPath()); + } + if (dockerHost != null && dockerHost.getContext() != null) { + DockerContext context = config.forContext(dockerHost.getContext()); + return new ResolvedDockerHost(context.getDockerHost(), context.isTlsVerify(), context.getTlsPath()); + } if (environment.get(DOCKER_HOST) != null) { return new ResolvedDockerHost(environment.get(DOCKER_HOST), isTrue(environment.get(DOCKER_TLS_VERIFY)), environment.get(DOCKER_CERT_PATH)); @@ -79,7 +96,11 @@ static ResolvedDockerHost from(Environment environment, DockerHost dockerHost) { return new ResolvedDockerHost(dockerHost.getAddress(), dockerHost.isSecure(), dockerHost.getCertificatePath()); } - return new ResolvedDockerHost(Platform.isWindows() ? WINDOWS_NAMED_PIPE_PATH : DOMAIN_SOCKET_PATH, false, null); + if (config.getContext().getDockerHost() != null) { + DockerContext context = config.getContext(); + return new ResolvedDockerHost(context.getDockerHost(), context.isTlsVerify(), context.getTlsPath()); + } + return new ResolvedDockerHost(Platform.isWindows() ? WINDOWS_NAMED_PIPE_PATH : DOMAIN_SOCKET_PATH); } private static boolean isTrue(String value) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java index 4a8461fa0907..c428155142d8 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import java.io.OutputStream; import java.net.URI; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.io.IOConsumer; @@ -93,7 +94,7 @@ public interface HttpTransport { * @param dockerHost the Docker host information * @return a {@link HttpTransport} instance */ - static HttpTransport create(DockerHost dockerHost) { + static HttpTransport create(DockerHostConfiguration dockerHost) { ResolvedDockerHost host = ResolvedDockerHost.from(dockerHost); HttpTransport remote = RemoteHttpClientTransport.createIfPossible(host); return (remote != null) ? remote : LocalHttpClientTransport.create(host); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransport.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransport.java index 81e7c12e9389..a097e0f9066d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransport.java @@ -20,28 +20,28 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; -import java.net.URISyntaxException; import java.net.UnknownHostException; import com.sun.jna.Platform; import org.apache.hc.client5.http.DnsResolver; -import org.apache.hc.client5.http.SchemePortResolver; +import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager; import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.client5.http.routing.HttpRoutePlanner; import org.apache.hc.client5.http.socket.ConnectionSocketFactory; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.config.Registry; import org.apache.hc.core5.http.config.RegistryBuilder; import org.apache.hc.core5.http.protocol.HttpContext; -import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeValue; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; -import org.springframework.boot.buildpack.platform.socket.DomainSocket; import org.springframework.boot.buildpack.platform.socket.NamedPipeSocket; +import org.springframework.boot.buildpack.platform.socket.UnixDomainSocket; /** * {@link HttpClientTransport} that talks to local Docker. @@ -51,26 +51,22 @@ */ final class LocalHttpClientTransport extends HttpClientTransport { - private static final HttpHost LOCAL_DOCKER_HOST; + private static final String DOCKER_SCHEME = "docker"; - static { - try { - LOCAL_DOCKER_HOST = HttpHost.create("docker://localhost"); - } - catch (URISyntaxException ex) { - throw new RuntimeException("Error creating local Docker host address", ex); - } - } + private static final int DEFAULT_DOCKER_PORT = 2376; + + private static final HttpHost LOCAL_DOCKER_HOST = new HttpHost(DOCKER_SCHEME, "localhost", DEFAULT_DOCKER_PORT); - private LocalHttpClientTransport(HttpClient client) { - super(client, LOCAL_DOCKER_HOST); + private LocalHttpClientTransport(HttpClient client, HttpHost host) { + super(client, host); } static LocalHttpClientTransport create(ResolvedDockerHost dockerHost) { HttpClientBuilder builder = HttpClients.custom(); builder.setConnectionManager(new LocalConnectionManager(dockerHost.getAddress())); - builder.setSchemePortResolver(new LocalSchemePortResolver()); - return new LocalHttpClientTransport(builder.build()); + builder.setRoutePlanner(new LocalRoutePlanner()); + HttpHost host = new HttpHost(DOCKER_SCHEME, dockerHost.getAddress()); + return new LocalHttpClientTransport(builder.build(), host); } /** @@ -78,13 +74,18 @@ static LocalHttpClientTransport create(ResolvedDockerHost dockerHost) { */ private static class LocalConnectionManager extends BasicHttpClientConnectionManager { + private static final ConnectionConfig CONNECTION_CONFIG = ConnectionConfig.copy(ConnectionConfig.DEFAULT) + .setValidateAfterInactivity(TimeValue.NEG_ONE_MILLISECOND) + .build(); + LocalConnectionManager(String host) { super(getRegistry(host), null, null, new LocalDnsResolver()); + setConnectionConfig(CONNECTION_CONFIG); } private static Registry getRegistry(String host) { RegistryBuilder builder = RegistryBuilder.create(); - builder.register("docker", new LocalConnectionSocketFactory(host)); + builder.register(DOCKER_SCHEME, new LocalConnectionSocketFactory(host)); return builder.build(); } @@ -115,6 +116,8 @@ public String resolveCanonicalHostname(String host) throws UnknownHostException */ private static class LocalConnectionSocketFactory implements ConnectionSocketFactory { + private static final String NPIPE_PREFIX = "npipe://"; + private final String host; LocalConnectionSocketFactory(String host) { @@ -123,10 +126,10 @@ private static class LocalConnectionSocketFactory implements ConnectionSocketFac @Override public Socket createSocket(HttpContext context) throws IOException { - if (Platform.isWindows()) { - return NamedPipeSocket.get(this.host); + if (this.host.startsWith(NPIPE_PREFIX)) { + return NamedPipeSocket.get(this.host.substring(NPIPE_PREFIX.length())); } - return DomainSocket.get(this.host); + return (!Platform.isWindows()) ? UnixDomainSocket.get(this.host) : NamedPipeSocket.get(this.host); } @Override @@ -139,20 +142,13 @@ public Socket connectSocket(TimeValue connectTimeout, Socket socket, HttpHost ho } /** - * {@link SchemePortResolver} for local Docker. + * {@link HttpRoutePlanner} for local Docker. */ - private static final class LocalSchemePortResolver implements SchemePortResolver { - - private static final int DEFAULT_DOCKER_PORT = 2376; + private static final class LocalRoutePlanner implements HttpRoutePlanner { @Override - public int resolve(HttpHost host) { - Args.notNull(host, "HTTP host"); - String name = host.getSchemeName(); - if ("docker".equals(name)) { - return DEFAULT_DOCKER_PORT; - } - return -1; + public HttpRoute determineRoute(HttpHost target, HttpContext context) { + return new HttpRoute(LOCAL_DOCKER_HOST); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/BlobReference.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/BlobReference.java new file mode 100644 index 000000000000..ed2b1a28481d --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/BlobReference.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.buildpack.platform.docker.type; + +import java.lang.invoke.MethodHandles; + +import com.fasterxml.jackson.databind.JsonNode; + +import org.springframework.boot.buildpack.platform.json.MappedObject; + +/** + * A reference to a blob by its digest. + * + * @author Phillip Webb + * @since 3.2.6 + */ +public class BlobReference extends MappedObject { + + private final String digest; + + private final String mediaType; + + BlobReference(JsonNode node) { + super(node, MethodHandles.lookup()); + this.digest = valueAt("/digest", String.class); + this.mediaType = valueAt("/mediaType", String.class); + } + + /** + * Return the digest of the blob. + * @return the blob digest + */ + public String getDigest() { + return this.digest; + } + + /** + * Return the media type of the blob. + * @return the blob media type + */ + public String getMediaType() { + return this.mediaType; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerConfig.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerConfig.java index 13798eb7346f..1ee0e19ae6f3 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerConfig.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Image.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Image.java index 24a488bd4a14..dd224c8dab78 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Image.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Image.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandles; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -48,22 +47,13 @@ public class Image extends MappedObject { Image(JsonNode node) { super(node, MethodHandles.lookup()); - this.digests = getDigests(getNode().at("/RepoDigests")); + this.digests = childrenAt("/RepoDigests", JsonNode::asText); this.config = new ImageConfig(getNode().at("/Config")); this.layers = extractLayers(valueAt("/RootFS/Layers", String[].class)); this.os = valueAt("/Os", String.class); this.created = valueAt("/Created", String.class); } - private List getDigests(JsonNode node) { - if (node.isEmpty()) { - return Collections.emptyList(); - } - List digests = new ArrayList<>(); - node.forEach((child) -> digests.add(child.asText())); - return Collections.unmodifiableList(digests); - } - private List extractLayers(String[] layers) { if (layers == null) { return Collections.emptyList(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveIndex.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveIndex.java new file mode 100644 index 000000000000..df117703c531 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveIndex.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.buildpack.platform.docker.type; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.invoke.MethodHandles; +import java.util.List; + +import com.fasterxml.jackson.databind.JsonNode; + +import org.springframework.boot.buildpack.platform.json.MappedObject; + +/** + * Image archive index information as provided by {@code index.json}. + * + * @author Phillip Webb + * @since 3.2.6 + * @see OCI Image Index + * Specification + */ +public class ImageArchiveIndex extends MappedObject { + + private final Integer schemaVersion; + + private final List manifests; + + protected ImageArchiveIndex(JsonNode node) { + super(node, MethodHandles.lookup()); + this.schemaVersion = valueAt("/schemaVersion", Integer.class); + this.manifests = childrenAt("/manifests", BlobReference::new); + } + + public Integer getSchemaVersion() { + return this.schemaVersion; + } + + public List getManifests() { + return this.manifests; + } + + /** + * Create an {@link ImageArchiveIndex} from the provided JSON input stream. + * @param content the JSON input stream + * @return a new {@link ImageArchiveIndex} instance + * @throws IOException on IO error + */ + public static ImageArchiveIndex of(InputStream content) throws IOException { + return of(content, ImageArchiveIndex::new); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveManifest.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveManifest.java index 9c8c1e8cce71..1f5f70bdf919 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveManifest.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveManifest.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandles; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -28,18 +27,18 @@ import org.springframework.boot.buildpack.platform.json.MappedObject; /** - * Image archive manifest information. + * Image archive manifest information as provided by {@code manifest.json}. * * @author Scott Frederick * @since 2.7.10 */ public class ImageArchiveManifest extends MappedObject { - private final List entries = new ArrayList<>(); + private final List entries; protected ImageArchiveManifest(JsonNode node) { super(node, MethodHandles.lookup()); - getNode().elements().forEachRemaining((element) -> this.entries.add(ManifestEntry.of(element))); + this.entries = childrenAt(null, ManifestEntry::new); } /** @@ -77,10 +76,6 @@ public List getLayers() { return this.layers; } - static ManifestEntry of(JsonNode node) { - return new ManifestEntry(node); - } - @SuppressWarnings("unchecked") private List extractLayers() { List layers = valueAt("/Layers", List.class); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Manifest.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Manifest.java new file mode 100644 index 000000000000..d3d9e50b252f --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Manifest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.buildpack.platform.docker.type; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.invoke.MethodHandles; +import java.util.List; + +import com.fasterxml.jackson.databind.JsonNode; + +import org.springframework.boot.buildpack.platform.json.MappedObject; + +/** + * A manifest as defined in {@code application/vnd.docker.distribution.manifest} or + * {@code application/vnd.oci.image.manifest} files. + * + * @author Phillip Webb + * @since 3.2.6 + * @see OCI + * Image Manifest Specification + */ +public class Manifest extends MappedObject { + + private final Integer schemaVersion; + + private final String mediaType; + + private final List layers; + + protected Manifest(JsonNode node) { + super(node, MethodHandles.lookup()); + this.schemaVersion = valueAt("/schemaVersion", Integer.class); + this.mediaType = valueAt("/mediaType", String.class); + this.layers = childrenAt("/layers", BlobReference::new); + } + + public Integer getSchemaVersion() { + return this.schemaVersion; + } + + public String getMediaType() { + return this.mediaType; + } + + public List getLayers() { + return this.layers; + } + + /** + * Create an {@link Manifest} from the provided JSON input stream. + * @param content the JSON input stream + * @return a new {@link Manifest} instance + * @throws IOException on IO error + */ + public static Manifest of(InputStream content) throws IOException { + return of(content, Manifest::new); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ManifestList.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ManifestList.java new file mode 100644 index 000000000000..5d5fbcb64f3e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ManifestList.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.buildpack.platform.docker.type; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.invoke.MethodHandles; +import java.util.List; +import java.util.stream.Stream; + +import com.fasterxml.jackson.databind.JsonNode; + +import org.springframework.boot.buildpack.platform.json.MappedObject; + +/** + * A distribution manifest list as defined in + * {@code application/vnd.docker.distribution.manifest.list} files. + * + * @author Phillip Webb + * @since 3.2.6 + * @see OCI + * Image Manifest Specification + */ +public class ManifestList extends MappedObject { + + private final Integer schemaVersion; + + private final String mediaType; + + private final List manifests; + + protected ManifestList(JsonNode node) { + super(node, MethodHandles.lookup()); + this.schemaVersion = valueAt("/schemaVersion", Integer.class); + this.mediaType = valueAt("/mediaType", String.class); + this.manifests = childrenAt("/manifests", BlobReference::new); + } + + public Integer getSchemaVersion() { + return this.schemaVersion; + } + + public String getMediaType() { + return this.mediaType; + } + + public Stream streamManifests() { + return getManifests().stream(); + } + + public List getManifests() { + return this.manifests; + } + + /** + * Create an {@link ManifestList} from the provided JSON input stream. + * @param content the JSON input stream + * @return a new {@link ManifestList} instance + * @throws IOException on IO error + */ + public static ManifestList of(InputStream content) throws IOException { + return of(content, ManifestList::new); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/TarArchive.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/TarArchive.java index 70a8e7ca979c..c5d6e99ba177 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/TarArchive.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/TarArchive.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,15 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneOffset; +import java.util.zip.GZIPInputStream; + +import org.springframework.util.StreamUtils; +import org.springframework.util.function.ThrowingFunction; /** * A TAR archive that can be written to an output stream. @@ -45,6 +50,15 @@ public interface TarArchive { */ void writeTo(OutputStream outputStream) throws IOException; + /** + * Return the compression being used with the tar archive. + * @return the used compression + * @since 3.2.6 + */ + default Compression getCompression() { + return Compression.NONE; + } + /** * Factory method to create a new {@link TarArchive} instance with a specific layout. * @param layout the TAR layout @@ -68,4 +82,68 @@ static TarArchive fromZip(File zip, Owner owner) { return new ZipFileTarArchive(zip, owner); } + /** + * Factory method to adapt a ZIP file to {@link TarArchive}. Assumes that + * {@link #writeTo(OutputStream)} will only be called once. + * @param inputStream the source input stream + * @param compression the compression used + * @return a new {@link TarArchive} instance + * @since 3.2.6 + */ + static TarArchive fromInputStream(InputStream inputStream, Compression compression) { + return new TarArchive() { + + @Override + public void writeTo(OutputStream outputStream) throws IOException { + StreamUtils.copy(compression.uncompress(inputStream), outputStream); + } + + @Override + public Compression getCompression() { + return compression; + } + + }; + } + + /** + * Compression type applied to the archive. + * + * @since 3.2.6 + */ + enum Compression { + + /** + * The tar file is not compressed. + */ + NONE((inputStream) -> inputStream), + + /** + * The tar file is compressed using gzip. + */ + GZIP(GZIPInputStream::new), + + /** + * The tar file is compressed using zstd. + */ + ZSTD("zstd compression is not supported"); + + private final ThrowingFunction uncompressor; + + Compression(String uncompressError) { + this((inputStream) -> { + throw new IllegalStateException(uncompressError); + }); + } + + Compression(ThrowingFunction wrapper) { + this.uncompressor = wrapper; + } + + InputStream uncompress(InputStream inputStream) { + return this.uncompressor.apply(inputStream); + } + + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/MappedObject.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/MappedObject.java index 1e51ef5e4b94..08a16a206a3a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/MappedObject.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/MappedObject.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,12 +23,16 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.function.Function; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.util.Assert; +import org.springframework.util.StreamUtils; /** * Base class for mapped JSON objects. @@ -71,6 +75,25 @@ protected T valueAt(String expression, Class type) { return valueAt(this, this.node, this.lookup, expression, type); } + /** + * Get children at the given JSON path expression by constructing them using the given + * factory. + * @param the child type + * @param expression the JSON path expression + * @param factory factory used to create the child + * @return a list of children + * @since 3.2.6 + */ + protected List childrenAt(String expression, Function factory) { + JsonNode node = (expression != null) ? this.node.at(expression) : this.node; + if (node.isEmpty()) { + return Collections.emptyList(); + } + List children = new ArrayList<>(); + node.elements().forEachRemaining((childNode) -> children.add(factory.apply(childNode))); + return Collections.unmodifiableList(children); + } + @SuppressWarnings("unchecked") protected static T getRoot(Object proxy) { MappedInvocationHandler handler = (MappedInvocationHandler) Proxy.getInvocationHandler(proxy); @@ -128,7 +151,7 @@ protected static T of(String content, Function T of(InputStream content, Function factory) throws IOException { - return of(content, ObjectMapper::readTree, factory); + return of(StreamUtils.nonClosing(content), ObjectMapper::readTree, factory); } /** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket/BsdDomainSocket.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket/BsdDomainSocket.java deleted file mode 100644 index 4fdfeea64200..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket/BsdDomainSocket.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2012-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.buildpack.platform.socket; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.List; - -import com.sun.jna.LastErrorException; -import com.sun.jna.Native; -import com.sun.jna.Platform; -import com.sun.jna.Structure; - -import org.springframework.util.Assert; - -/** - * {@link DomainSocket} implementation for BSD based platforms. - * - * @author Phillip Webb - */ -class BsdDomainSocket extends DomainSocket { - - private static final int MAX_PATH_LENGTH = 104; - - static { - Native.register(Platform.C_LIBRARY_NAME); - } - - BsdDomainSocket(String path) throws IOException { - super(path); - } - - @Override - protected void connect(String path, int handle) { - SockaddrUn address = new SockaddrUn(AF_LOCAL, path.getBytes(StandardCharsets.UTF_8)); - connect(handle, address, address.size()); - } - - private native int connect(int fd, SockaddrUn address, int addressLen) throws LastErrorException; - - /** - * Native {@code sockaddr_un} structure as defined in {@code sys/un.h}. - */ - public static class SockaddrUn extends Structure implements Structure.ByReference { - - public byte sunLen; - - public byte sunFamily; - - public byte[] sunPath = new byte[MAX_PATH_LENGTH]; - - private SockaddrUn(byte sunFamily, byte[] path) { - Assert.isTrue(path.length < MAX_PATH_LENGTH, () -> "Path cannot exceed " + MAX_PATH_LENGTH + " bytes"); - System.arraycopy(path, 0, this.sunPath, 0, path.length); - this.sunPath[path.length] = 0; - this.sunLen = (byte) (fieldOffset("sunPath") + path.length); - this.sunFamily = sunFamily; - allocateMemory(); - } - - @Override - protected List getFieldOrder() { - return Arrays.asList(new String[] { "sunLen", "sunFamily", "sunPath" }); - } - - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket/DomainSocket.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket/DomainSocket.java deleted file mode 100644 index 0c587f6b5f13..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket/DomainSocket.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2012-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.buildpack.platform.socket; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; -import java.nio.ByteBuffer; - -import com.sun.jna.LastErrorException; -import com.sun.jna.Native; -import com.sun.jna.Platform; - -import org.springframework.boot.buildpack.platform.socket.FileDescriptor.Handle; - -/** - * A {@link Socket} implementation for Linux of BSD domain sockets. - * - * @author Phillip Webb - * @since 2.3.0 - */ -public abstract class DomainSocket extends AbstractSocket { - - private static final int SHUT_RD = 0; - - private static final int SHUT_WR = 1; - - protected static final int PF_LOCAL = 1; - - protected static final byte AF_LOCAL = 1; - - protected static final int SOCK_STREAM = 1; - - private final FileDescriptor fileDescriptor; - - private final InputStream inputStream; - - private final OutputStream outputStream; - - static { - Native.register(Platform.C_LIBRARY_NAME); - } - - DomainSocket(String path) throws IOException { - try { - this.fileDescriptor = open(path); - this.inputStream = new DomainSocketInputStream(); - this.outputStream = new DomainSocketOutputStream(); - } - catch (LastErrorException ex) { - throw new IOException(ex); - } - } - - private FileDescriptor open(String path) { - int handle = socket(PF_LOCAL, SOCK_STREAM, 0); - try { - connect(path, handle); - return new FileDescriptor(handle, this::close); - } - catch (RuntimeException ex) { - close(handle); - throw ex; - } - } - - private int read(ByteBuffer buffer) throws IOException { - try (Handle handle = this.fileDescriptor.acquire()) { - if (handle.isClosed()) { - return -1; - } - try { - return read(handle.intValue(), buffer, buffer.remaining()); - } - catch (LastErrorException ex) { - throw new IOException(ex); - } - } - } - - public void write(ByteBuffer buffer) throws IOException { - try (Handle handle = this.fileDescriptor.acquire()) { - if (!handle.isClosed()) { - try { - write(handle.intValue(), buffer, buffer.remaining()); - } - catch (LastErrorException ex) { - throw new IOException(ex); - } - } - } - } - - @Override - public InputStream getInputStream() { - return this.inputStream; - } - - @Override - public OutputStream getOutputStream() { - return this.outputStream; - } - - @Override - public void close() throws IOException { - super.close(); - try { - this.fileDescriptor.close(); - } - catch (LastErrorException ex) { - throw new IOException(ex); - } - } - - protected abstract void connect(String path, int handle); - - private native int socket(int domain, int type, int protocol) throws LastErrorException; - - private native int read(int fd, ByteBuffer buffer, int count) throws LastErrorException; - - private native int write(int fd, ByteBuffer buffer, int count) throws LastErrorException; - - private native int close(int fd) throws LastErrorException; - - /** - * Return a new {@link DomainSocket} for the given path. - * @param path the path to the domain socket - * @return a {@link DomainSocket} instance - * @throws IOException if the socket cannot be opened - */ - public static DomainSocket get(String path) throws IOException { - if (Platform.isMac() || isBsdPlatform()) { - return new BsdDomainSocket(path); - } - return new LinuxDomainSocket(path); - } - - private static boolean isBsdPlatform() { - return Platform.isFreeBSD() || Platform.iskFreeBSD() || Platform.isNetBSD() || Platform.isOpenBSD(); - } - - /** - * {@link InputStream} returned from the {@link DomainSocket}. - */ - private final class DomainSocketInputStream extends InputStream { - - @Override - public int read() throws IOException { - ByteBuffer buffer = ByteBuffer.allocate(1); - int amountRead = DomainSocket.this.read(buffer); - return (amountRead != 1) ? -1 : buffer.get() & 0xFF; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - if (len == 0) { - return 0; - } - int amountRead = DomainSocket.this.read(ByteBuffer.wrap(b, off, len)); - return (amountRead > 0) ? amountRead : -1; - } - - } - - /** - * {@link OutputStream} returned from the {@link DomainSocket}. - */ - private final class DomainSocketOutputStream extends OutputStream { - - @Override - public void write(int b) throws IOException { - ByteBuffer buffer = ByteBuffer.allocate(1); - buffer.put(0, (byte) (b & 0xFF)); - DomainSocket.this.write(buffer); - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - if (len != 0) { - DomainSocket.this.write(ByteBuffer.wrap(b, off, len)); - } - } - - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket/LinuxDomainSocket.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket/LinuxDomainSocket.java deleted file mode 100644 index 24950d6c9fc1..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket/LinuxDomainSocket.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2012-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.buildpack.platform.socket; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.List; - -import com.sun.jna.LastErrorException; -import com.sun.jna.Native; -import com.sun.jna.Platform; -import com.sun.jna.Structure; - -import org.springframework.util.Assert; - -/** - * {@link DomainSocket} implementation for Linux based platforms. - * - * @author Phillip Webb - */ -class LinuxDomainSocket extends DomainSocket { - - static { - Native.register(Platform.C_LIBRARY_NAME); - } - - LinuxDomainSocket(String path) throws IOException { - super(path); - } - - private static final int MAX_PATH_LENGTH = 108; - - @Override - protected void connect(String path, int handle) { - SockaddrUn address = new SockaddrUn(AF_LOCAL, path.getBytes(StandardCharsets.UTF_8)); - connect(handle, address, address.size()); - } - - private native int connect(int fd, SockaddrUn address, int addressLen) throws LastErrorException; - - /** - * Native {@code sockaddr_un} structure as defined in {@code sys/un.h}. - */ - public static class SockaddrUn extends Structure implements Structure.ByReference { - - public short sunFamily; - - public byte[] sunPath = new byte[MAX_PATH_LENGTH]; - - private SockaddrUn(byte sunFamily, byte[] path) { - Assert.isTrue(path.length < MAX_PATH_LENGTH, () -> "Path cannot exceed " + MAX_PATH_LENGTH + " bytes"); - System.arraycopy(path, 0, this.sunPath, 0, path.length); - this.sunPath[path.length] = 0; - this.sunFamily = sunFamily; - allocateMemory(); - } - - @Override - protected List getFieldOrder() { - return Arrays.asList(new String[] { "sunFamily", "sunPath" }); - } - - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket/UnixDomainSocket.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket/UnixDomainSocket.java new file mode 100644 index 000000000000..276397c2c5b0 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket/UnixDomainSocket.java @@ -0,0 +1,102 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.buildpack.platform.socket; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.UnixDomainSocketAddress; +import java.nio.channels.Channels; +import java.nio.channels.SocketChannel; + +/** + * A {@link Socket} implementation for Unix domain sockets. + * + * @author Scott Frederick + * @since 3.4.0 + */ +public final class UnixDomainSocket extends AbstractSocket { + + /** + * Create a new {@link Socket} for the given path. + * @param path the path to the domain socket + * @return a {@link Socket} instance + * @throws IOException if the socket cannot be opened + */ + public static Socket get(String path) throws IOException { + return new UnixDomainSocket(path); + } + + private final SocketAddress socketAddress; + + private final SocketChannel socketChannel; + + private UnixDomainSocket(String path) throws IOException { + this.socketAddress = UnixDomainSocketAddress.of(path); + this.socketChannel = SocketChannel.open(this.socketAddress); + } + + @Override + public InputStream getInputStream() throws IOException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + if (!isConnected()) { + throw new SocketException("Socket is not connected"); + } + if (isInputShutdown()) { + throw new SocketException("Socket input is shutdown"); + } + + return Channels.newInputStream(this.socketChannel); + } + + @Override + public OutputStream getOutputStream() throws IOException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + if (!isConnected()) { + throw new SocketException("Socket is not connected"); + } + if (isOutputShutdown()) { + throw new SocketException("Socket output is shutdown"); + } + + return Channels.newOutputStream(this.socketChannel); + } + + @Override + public SocketAddress getLocalSocketAddress() { + return this.socketAddress; + } + + @Override + public SocketAddress getRemoteSocketAddress() { + return this.socketAddress; + } + + @Override + public void close() throws IOException { + super.close(); + this.socketChannel.close(); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildRequestTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildRequestTests.java index e74302a1636f..d0eae13b3d9d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildRequestTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildRequestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,14 +27,18 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.springframework.boot.buildpack.platform.docker.type.Binding; +import org.springframework.boot.buildpack.platform.docker.type.ImageName; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.TarArchive; @@ -64,7 +68,7 @@ void forJarFileReturnsRequest() throws IOException { writeTestJarFile(jarFile); BuildRequest request = BuildRequest.forJarFile(jarFile); assertThat(request.getName()).hasToString("docker.io/library/my-app:0.0.1"); - assertThat(request.getBuilder()).hasToString("docker.io/" + BuildRequest.DEFAULT_BUILDER_IMAGE_NAME); + assertThat(request.getBuilder()).hasToString("docker.io/" + BuildRequest.DEFAULT_BUILDER_IMAGE_REF); assertThat(request.getApplicationContent(Owner.ROOT)).satisfies(this::hasExpectedJarContent); assertThat(request.getEnv()).isEmpty(); } @@ -75,7 +79,7 @@ void forJarFileWithNameReturnsRequest() throws IOException { writeTestJarFile(jarFile); BuildRequest request = BuildRequest.forJarFile(ImageReference.of("test-app"), jarFile); assertThat(request.getName()).hasToString("docker.io/library/test-app:latest"); - assertThat(request.getBuilder()).hasToString("docker.io/" + BuildRequest.DEFAULT_BUILDER_IMAGE_NAME); + assertThat(request.getBuilder()).hasToString("docker.io/" + BuildRequest.DEFAULT_BUILDER_IMAGE_REF); assertThat(request.getApplicationContent(Owner.ROOT)).satisfies(this::hasExpectedJarContent); assertThat(request.getEnv()).isEmpty(); } @@ -104,6 +108,7 @@ void withBuilderUpdatesBuilder() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")) .withBuilder(ImageReference.of("spring/builder")); assertThat(request.getBuilder()).hasToString("docker.io/spring/builder:latest"); + assertThat(request.isTrustBuilder()).isFalse(); } @Test @@ -113,6 +118,53 @@ void withBuilderWhenHasDigestUpdatesBuilder() throws IOException { .of("spring/builder@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d")); assertThat(request.getBuilder()).hasToString( "docker.io/spring/builder@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); + assertThat(request.isTrustBuilder()).isFalse(); + } + + @Test + void withoutBuilderTrustsDefaultBuilder() throws IOException { + BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); + assertThat(request.isTrustBuilder()).isTrue(); + } + + @Test + void withoutBuilderTrustsDefaultBuilderWithDifferentTag() throws IOException { + BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")) + .withBuilder(ImageReference.of(ImageName.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME), "other")); + assertThat(request.isTrustBuilder()).isTrue(); + } + + @Test + void withoutBuilderTrustsDefaultBuilderWithDigest() throws IOException { + BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")) + .withBuilder(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF) + .withDigest("sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d")); + assertThat(request.isTrustBuilder()).isTrue(); + } + + @ParameterizedTest + @MethodSource("trustedBuilders") + void withKnownTrustedBuilderTrustsBuilder(ImageReference builder) throws IOException { + BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")).withBuilder(builder); + assertThat(request.isTrustBuilder()).isTrue(); + } + + static Stream trustedBuilders() { + return BuildRequest.KNOWN_TRUSTED_BUILDERS.stream(); + } + + @Test + void withoutTrustBuilderAndDefaultBuilderUpdatesTrustsBuilder() throws IOException { + BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")).withTrustBuilder(false); + assertThat(request.isTrustBuilder()).isFalse(); + } + + @Test + void withTrustBuilderAndBuilderUpdatesTrustBuilder() throws IOException { + BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")) + .withBuilder(ImageReference.of("spring/builder")) + .withTrustBuilder(true); + assertThat(request.isTrustBuilder()).isTrue(); } @Test @@ -233,6 +285,22 @@ void withTagsWhenTagsIsNullThrowsException() throws IOException { .withMessage("Tags must not be null"); } + @Test + void withBuildWorkspaceVolumeAddsWorkspace() throws IOException { + BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); + BuildRequest withWorkspace = request.withBuildWorkspace(Cache.volume("build-workspace")); + assertThat(request.getBuildWorkspace()).isNull(); + assertThat(withWorkspace.getBuildWorkspace()).isEqualTo(Cache.volume("build-workspace")); + } + + @Test + void withBuildWorkspaceBindAddsWorkspace() throws IOException { + BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); + BuildRequest withWorkspace = request.withBuildWorkspace(Cache.bind("/tmp/build-workspace")); + assertThat(request.getBuildWorkspace()).isNull(); + assertThat(withWorkspace.getBuildWorkspace()).isEqualTo(Cache.bind("/tmp/build-workspace")); + } + @Test void withBuildVolumeCacheAddsCache() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); @@ -241,6 +309,14 @@ void withBuildVolumeCacheAddsCache() throws IOException { assertThat(withCache.getBuildCache()).isEqualTo(Cache.volume("build-volume")); } + @Test + void withBuildBindCacheAddsCache() throws IOException { + BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); + BuildRequest withCache = request.withBuildCache(Cache.bind("/tmp/build-cache")); + assertThat(request.getBuildCache()).isNull(); + assertThat(withCache.getBuildCache()).isEqualTo(Cache.bind("/tmp/build-cache")); + } + @Test void withBuildVolumeCacheWhenCacheIsNullThrowsException() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); @@ -256,6 +332,14 @@ void withLaunchVolumeCacheAddsCache() throws IOException { assertThat(withCache.getLaunchCache()).isEqualTo(Cache.volume("launch-volume")); } + @Test + void withLaunchBindCacheAddsCache() throws IOException { + BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); + BuildRequest withCache = request.withLaunchCache(Cache.bind("/tmp/launch-cache")); + assertThat(request.getLaunchCache()).isNull(); + assertThat(withCache.getLaunchCache()).isEqualTo(Cache.bind("/tmp/launch-cache")); + } + @Test void withLaunchVolumeCacheWhenCacheIsNullThrowsException() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); @@ -301,6 +385,13 @@ void withApplicationDirectorySetsApplicationDirectory() throws Exception { assertThat(withAppDir.getApplicationDirectory()).isEqualTo("/application"); } + @Test + void withSecurityOptionsSetsSecurityOptions() throws Exception { + BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); + BuildRequest withAppDir = request.withSecurityOptions(List.of("label=user:USER", "label=role:ROLE")); + assertThat(withAppDir.getSecurityOptions()).containsExactly("label=user:USER", "label=role:ROLE"); + } + private void hasExpectedJarContent(TarArchive archive) { try { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderMetadataTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderMetadataTests.java index f5c0c0c7b579..fd6803401d7b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderMetadataTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderMetadataTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test; +import org.springframework.boot.buildpack.platform.build.BuilderMetadata.RunImage; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; @@ -46,6 +47,29 @@ void fromImageLoadsMetadata() throws IOException { BuilderMetadata metadata = BuilderMetadata.fromImage(image); assertThat(metadata.getStack().getRunImage().getImage()).isEqualTo("cloudfoundry/run:base-cnb"); assertThat(metadata.getStack().getRunImage().getMirrors()).isEmpty(); + assertThat(metadata.getRunImages()).isEmpty(); + assertThat(metadata.getLifecycle().getVersion()).isEqualTo("0.7.2"); + assertThat(metadata.getLifecycle().getApi().getBuildpack()).isEqualTo("0.2"); + assertThat(metadata.getLifecycle().getApi().getPlatform()).isEqualTo("0.3"); + assertThat(metadata.getCreatedBy().getName()).isEqualTo("Pack CLI"); + assertThat(metadata.getCreatedBy().getVersion()) + .isEqualTo("v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)"); + assertThat(metadata.getBuildpacks()).extracting(BuildpackMetadata::getId, BuildpackMetadata::getVersion) + .contains(tuple("paketo-buildpacks/java", "4.10.0")) + .contains(tuple("paketo-buildpacks/spring-boot", "3.5.0")) + .contains(tuple("paketo-buildpacks/executable-jar", "3.1.3")) + .contains(tuple("paketo-buildpacks/graalvm", "4.1.0")) + .contains(tuple("paketo-buildpacks/java-native-image", "4.7.0")) + .contains(tuple("paketo-buildpacks/spring-boot-native-image", "2.0.1")) + .contains(tuple("paketo-buildpacks/bellsoft-liberica", "6.2.0")); + } + + @Test + void fromImageWithoutStackLoadsMetadata() throws IOException { + Image image = Image.of(getContent("image-with-empty-stack.json")); + BuilderMetadata metadata = BuilderMetadata.fromImage(image); + assertThat(metadata.getRunImages()).extracting(RunImage::getImage, RunImage::getMirrors) + .contains(tuple("cloudfoundry/run:base-cnb", Collections.emptyList())); assertThat(metadata.getLifecycle().getVersion()).isEqualTo("0.7.2"); assertThat(metadata.getLifecycle().getApi().getBuildpack()).isEqualTo("0.2"); assertThat(metadata.getLifecycle().getApi().getPlatform()).isEqualTo("0.3"); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java index c9c522a2ddca..b6e9553dfd5d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -86,7 +86,7 @@ void buildInvokesBuilder() throws Exception { DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); - given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) + given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) .willAnswer(withPulledImage(runImage)); @@ -97,7 +97,7 @@ void buildInvokesBuilder() throws Exception { assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); then(docker.image()).should() - .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull()); + .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()); then(docker.image()).should() .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()); then(docker.image()).should().load(archive.capture(), any()); @@ -115,7 +115,7 @@ void buildInvokesBuilderAndPublishesImage() throws Exception { .withBuilderRegistryTokenAuthentication("builder token") .withPublishRegistryTokenAuthentication("publish token"); given(docker.image() - .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), + .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))) .willAnswer(withPulledImage(builderImage)); given(docker.image() @@ -129,7 +129,7 @@ void buildInvokesBuilderAndPublishesImage() throws Exception { assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); then(docker.image()).should() - .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), + .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())); then(docker.image()).should() .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), @@ -168,7 +168,7 @@ void buildInvokesBuilderWithRunImageInDigestForm() throws Exception { DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image-with-run-image-digest.json"); Image runImage = loadImage("run-image.json"); - given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) + given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image() .pull(eq(ImageReference @@ -185,13 +185,33 @@ void buildInvokesBuilderWithRunImageInDigestForm() throws Exception { then(docker.image()).should().remove(archive.getValue().getTag(), true); } + @Test + void buildInvokesBuilderWithNoStack() throws Exception { + TestPrintStream out = new TestPrintStream(); + DockerApi docker = mockDockerApi(); + Image builderImage = loadImage("image-with-empty-stack.json"); + Image runImage = loadImage("run-image.json"); + given(docker.image().pull(eq(ImageReference.of("gcr.io/paketo-buildpacks/builder:latest")), any(), isNull())) + .willAnswer(withPulledImage(builderImage)); + given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) + .willAnswer(withPulledImage(runImage)); + Builder builder = new Builder(BuildLog.to(out), docker, null); + BuildRequest request = getTestRequest().withBuilder(ImageReference.of("gcr.io/paketo-buildpacks/builder")); + builder.build(request); + assertThat(out.toString()).contains("Running creator"); + assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); + ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); + then(docker.image()).should().load(archive.capture(), any()); + then(docker.image()).should().remove(archive.getValue().getTag(), true); + } + @Test void buildInvokesBuilderWithRunImageFromRequest() throws Exception { TestPrintStream out = new TestPrintStream(); DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); - given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) + given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(ImageReference.of("example.com/custom/run:latest")), any(), isNull())) .willAnswer(withPulledImage(runImage)); @@ -211,11 +231,11 @@ void buildInvokesBuilderWithNeverPullPolicy() throws Exception { DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); - given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) + given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) .willAnswer(withPulledImage(runImage)); - given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)))) + given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)))) .willReturn(builderImage); given(docker.image().inspect(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")))) .willReturn(runImage); @@ -237,11 +257,11 @@ void buildInvokesBuilderWithAlwaysPullPolicy() throws Exception { DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); - given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) + given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) .willAnswer(withPulledImage(runImage)); - given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)))) + given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)))) .willReturn(builderImage); given(docker.image().inspect(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")))) .willReturn(runImage); @@ -263,11 +283,11 @@ void buildInvokesBuilderWithIfNotPresentPullPolicy() throws Exception { DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); - given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) + given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) .willAnswer(withPulledImage(runImage)); - given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)))) + given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)))) .willThrow( new DockerEngineException("docker://localhost/", new URI("example"), 404, "NOT FOUND", null, null)) .willReturn(builderImage); @@ -293,7 +313,7 @@ void buildInvokesBuilderWithTags() throws Exception { DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); - given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) + given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) .willAnswer(withPulledImage(runImage)); @@ -319,7 +339,7 @@ void buildInvokesBuilderWithTagsAndPublishesImageAndTags() throws Exception { .withBuilderRegistryTokenAuthentication("builder token") .withPublishRegistryTokenAuthentication("publish token"); given(docker.image() - .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), + .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))) .willAnswer(withPulledImage(builderImage)); given(docker.image() @@ -334,7 +354,7 @@ void buildInvokesBuilderWithTagsAndPublishesImageAndTags() throws Exception { assertThat(out.toString()).contains("Successfully created image tag 'docker.io/library/my-application:1.2.3'"); then(docker.image()).should() - .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), + .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())); then(docker.image()).should() .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), @@ -358,7 +378,7 @@ void buildWhenStackIdDoesNotMatchThrowsException() throws Exception { DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image-with-bad-stack.json"); - given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) + given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) .willAnswer(withPulledImage(runImage)); @@ -375,7 +395,7 @@ void buildWhenBuilderReturnsErrorThrowsException() throws Exception { DockerApi docker = mockDockerApiLifecycleError(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); - given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) + given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) .willAnswer(withPulledImage(runImage)); @@ -393,7 +413,7 @@ void buildWhenDetectedRunImageInDifferentAuthenticatedRegistryThrowsException() DockerConfiguration dockerConfiguration = new DockerConfiguration() .withBuilderRegistryTokenAuthentication("builder token"); given(docker.image() - .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), + .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))) .willAnswer(withPulledImage(builderImage)); Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration); @@ -411,7 +431,7 @@ void buildWhenRequestedRunImageInDifferentAuthenticatedRegistryThrowsException() DockerConfiguration dockerConfiguration = new DockerConfiguration() .withBuilderRegistryTokenAuthentication("builder token"); given(docker.image() - .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), + .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))) .willAnswer(withPulledImage(builderImage)); Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration); @@ -427,7 +447,7 @@ void buildWhenRequestedBuildpackNotInBuilderThrowsException() throws Exception { DockerApi docker = mockDockerApiLifecycleError(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); - given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) + given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) .willAnswer(withPulledImage(runImage)); @@ -470,7 +490,7 @@ private DockerApi mockDockerApiLifecycleError() throws IOException { private BuildRequest getTestRequest() { TarArchive content = mock(TarArchive.class); ImageReference name = ImageReference.of("my-application"); - return BuildRequest.of(name, (owner) -> content); + return BuildRequest.of(name, (owner) -> content).withTrustBuilder(true); } private Image loadImage(String name) throws IOException { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/DirectoryBuildpackTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/DirectoryBuildpackTests.java index d33757f19060..3fdbd1218596 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/DirectoryBuildpackTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/DirectoryBuildpackTests.java @@ -128,10 +128,10 @@ private void assertHasExpectedLayers(Buildpack buildpack) throws IOException { byte[] content = layers.get(0).toByteArray(); try (TarArchiveInputStream tar = new TarArchiveInputStream(new ByteArrayInputStream(content))) { List entries = new ArrayList<>(); - TarArchiveEntry entry = tar.getNextTarEntry(); + TarArchiveEntry entry = tar.getNextEntry(); while (entry != null) { entries.add(entry); - entry = tar.getNextTarEntry(); + entry = tar.getNextEntry(); } assertThat(entries).extracting("name", "mode") .containsExactlyInAnyOrder(tuple("/cnb/", 0755), tuple("/cnb/buildpacks/", 0755), diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/ImageBuildpackTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/ImageBuildpackTests.java index f42d6ee60b60..4385e419300c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/ImageBuildpackTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/ImageBuildpackTests.java @@ -19,10 +19,10 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -37,6 +37,8 @@ import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.io.IOBiConsumer; +import org.springframework.boot.buildpack.platform.io.TarArchive; +import org.springframework.boot.buildpack.platform.io.TarArchive.Compression; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import static org.assertj.core.api.Assertions.assertThat; @@ -176,10 +178,9 @@ void resolveWhenUnqualifiedReferenceWithInvalidImageReferenceReturnsNull() { private Object withMockLayers(InvocationOnMock invocation) { try { - IOBiConsumer consumer = invocation.getArgument(1); + IOBiConsumer consumer = invocation.getArgument(1); File tarFile = File.createTempFile("create-builder-test-", null); - FileOutputStream out = new FileOutputStream(tarFile); - try (TarArchiveOutputStream tarOut = new TarArchiveOutputStream(out)) { + try (TarArchiveOutputStream tarOut = new TarArchiveOutputStream(new FileOutputStream(tarFile))) { tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); writeTarEntry(tarOut, "/cnb/"); writeTarEntry(tarOut, "/cnb/buildpacks/"); @@ -189,7 +190,9 @@ private Object withMockLayers(InvocationOnMock invocation) { writeTarEntry(tarOut, "/cnb/buildpacks/example_buildpack/0.0.1/" + this.longFilePath); tarOut.finish(); } - consumer.accept("test", tarFile.toPath()); + try (FileInputStream tarFileStream = new FileInputStream(tarFile)) { + consumer.accept("test", TarArchive.fromInputStream(tarFileStream, Compression.NONE)); + } Files.delete(tarFile.toPath()); } catch (IOException ex) { @@ -215,10 +218,10 @@ private void assertAppliesExpectedLayers(Buildpack buildpack) throws IOException byte[] content = layers.get(0).toByteArray(); List entries = new ArrayList<>(); try (TarArchiveInputStream tar = new TarArchiveInputStream(new ByteArrayInputStream(content))) { - TarArchiveEntry entry = tar.getNextTarEntry(); + TarArchiveEntry entry = tar.getNextEntry(); while (entry != null) { entries.add(entry); - entry = tar.getNextTarEntry(); + entry = tar.getNextEntry(); } } assertThat(entries).extracting("name", "mode") diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java index 78ee54874bb0..9769c1bf0b71 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import java.io.PrintStream; import java.nio.charset.StandardCharsets; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import com.fasterxml.jackson.core.JsonProcessingException; @@ -33,6 +34,7 @@ import org.json.JSONObject; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; import org.mockito.stubbing.Answer; import org.skyscreamer.jsonassert.JSONAssert; @@ -40,7 +42,7 @@ import org.springframework.boot.buildpack.platform.docker.DockerApi.ContainerApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.ImageApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig; @@ -52,6 +54,7 @@ import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper; +import org.springframework.boot.testsupport.junit.BooleanValueSource; import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -86,13 +89,23 @@ void setup() { this.docker = mockDockerApi(); } - @Test - void executeExecutesPhases() throws Exception { + @ParameterizedTest + @BooleanValueSource + void executeExecutesPhases(boolean trustBuilder) throws Exception { given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - createLifecycle().execute(); - assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator.json")); + createLifecycle(trustBuilder).execute(); + if (trustBuilder) { + assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator.json")); + } + else { + assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json")); + assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json")); + assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer.json")); + assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json")); + assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter.json")); + } assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } @@ -101,7 +114,7 @@ void executeWithBindingsExecutesPhases() throws Exception { given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - BuildRequest request = getTestRequest().withBindings(Binding.of("/host/src/path:/container/dest/path:ro"), + BuildRequest request = getTestRequest(true).withBindings(Binding.of("/host/src/path:/container/dest/path:ro"), Binding.of("volume-name:/container/volume/path:rw")); createLifecycle(request).execute(); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-bindings.json")); @@ -113,48 +126,62 @@ void executeExecutesPhasesWithPlatformApi03() throws Exception { given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - createLifecycle("builder-metadata-platform-api-0.3.json").execute(); + createLifecycle(true, "builder-metadata-platform-api-0.3.json").execute(); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-platform-api-0.3.json")); assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } - @Test - void executeOnlyUploadsContentOnce() throws Exception { + @ParameterizedTest + @BooleanValueSource + void executeOnlyUploadsContentOnce(boolean trustBuilder) throws Exception { given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - createLifecycle().execute(); + createLifecycle(trustBuilder).execute(); assertThat(this.content).hasSize(1); } - @Test - void executeWhenAlreadyRunThrowsException() throws Exception { + @ParameterizedTest + @BooleanValueSource + void executeWhenAlreadyRunThrowsException(boolean trustBuilder) throws Exception { given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - Lifecycle lifecycle = createLifecycle(); + Lifecycle lifecycle = createLifecycle(trustBuilder); lifecycle.execute(); assertThatIllegalStateException().isThrownBy(lifecycle::execute) .withMessage("Lifecycle has already been executed"); } - @Test - void executeWhenBuilderReturnsErrorThrowsException() throws Exception { + @ParameterizedTest + @BooleanValueSource + void executeWhenBuilderReturnsErrorThrowsException(boolean trustBuilder) throws Exception { given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(9, null)); - assertThatExceptionOfType(BuilderException.class).isThrownBy(() -> createLifecycle().execute()) - .withMessage("Builder lifecycle 'creator' failed with status code 9"); + assertThatExceptionOfType(BuilderException.class).isThrownBy(() -> createLifecycle(trustBuilder).execute()) + .withMessage( + "Builder lifecycle '" + ((trustBuilder) ? "creator" : "analyzer") + "' failed with status code 9"); } - @Test - void executeWhenCleanCacheClearsCache() throws Exception { + @ParameterizedTest + @BooleanValueSource + void executeWhenCleanCacheClearsCache(boolean trustBuilder) throws Exception { given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - BuildRequest request = getTestRequest().withCleanCache(true); + BuildRequest request = getTestRequest(trustBuilder).withCleanCache(true); createLifecycle(request).execute(); - assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-clean-cache.json")); + if (trustBuilder) { + assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-clean-cache.json")); + } + else { + assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json")); + assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json")); + assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json")); + assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter.json")); + assertThat(this.out.toString()).contains("Skipping restorer because 'cleanCache' is enabled"); + } VolumeName name = VolumeName.of("pack-cache-b35197ac41ea.build"); then(this.docker.volume()).should().delete(name, true); } @@ -165,7 +192,7 @@ void executeWhenPlatformApiNotSupportedThrowsException() throws Exception { given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); assertThatIllegalStateException() - .isThrownBy(() -> createLifecycle("builder-metadata-unsupported-api.json").execute()) + .isThrownBy(() -> createLifecycle(true, "builder-metadata-unsupported-api.json").execute()) .withMessageContaining("Detected platform API versions '0.2' are not included in supported versions"); } @@ -175,22 +202,32 @@ void executeWhenMultiplePlatformApisNotSupportedThrowsException() throws Excepti given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); assertThatIllegalStateException() - .isThrownBy(() -> createLifecycle("builder-metadata-unsupported-apis.json").execute()) + .isThrownBy(() -> createLifecycle(true, "builder-metadata-unsupported-apis.json").execute()) .withMessageContaining("Detected platform API versions '0.1,0.2' are not included in supported versions"); } - @Test - void executeWhenMultiplePlatformApisSupportedExecutesPhase() throws Exception { + @ParameterizedTest + @BooleanValueSource + void executeWhenMultiplePlatformApisSupportedExecutesPhase(boolean trustBuilder) throws Exception { given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - createLifecycle("builder-metadata-supported-apis.json").execute(); - assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator.json")); + createLifecycle(trustBuilder, "builder-metadata-supported-apis.json").execute(); + if (trustBuilder) { + assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator.json")); + } + else { + assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json")); + assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json")); + assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer.json")); + assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json")); + assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter.json")); + } } @Test void closeClearsVolumes() throws Exception { - createLifecycle().close(); + createLifecycle(true).close(); then(this.docker.volume()).should().delete(VolumeName.of("pack-layers-aaaaaaaaaa"), true); then(this.docker.volume()).should().delete(VolumeName.of("pack-app-aaaaaaaaaa"), true); } @@ -200,65 +237,163 @@ void executeWithNetworkExecutesPhases() throws Exception { given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - BuildRequest request = getTestRequest().withNetwork("test"); + BuildRequest request = getTestRequest(true).withNetwork("test"); createLifecycle(request).execute(); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-network.json")); assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } - @Test - void executeWithCacheVolumeNamesExecutesPhases() throws Exception { + @ParameterizedTest + @BooleanValueSource + void executeWithCacheVolumeNamesExecutesPhases(boolean trustBuilder) throws Exception { given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - BuildRequest request = getTestRequest().withBuildCache(Cache.volume("build-volume")) + BuildRequest request = getTestRequest(trustBuilder).withBuildWorkspace(Cache.volume("work-volume")) + .withBuildCache(Cache.volume("build-volume")) .withLaunchCache(Cache.volume("launch-volume")); createLifecycle(request).execute(); - assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-cache-volumes.json")); + if (trustBuilder) { + assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-cache-volumes.json")); + } + else { + assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer-cache-volumes.json")); + assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector-cache-volumes.json")); + assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer-cache-volumes.json")); + assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder-cache-volumes.json")); + assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-cache-volumes.json")); + } assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } - @Test - void executeWithCreatedDateExecutesPhases() throws Exception { + @ParameterizedTest + @BooleanValueSource + void executeWithCacheBindMountsExecutesPhases(boolean trustBuilder) throws Exception { given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - BuildRequest request = getTestRequest().withCreatedDate("2020-07-01T12:34:56Z"); + BuildRequest request = getTestRequest(trustBuilder).withBuildWorkspace(Cache.bind("/tmp/work")) + .withBuildCache(Cache.bind("/tmp/build-cache")) + .withLaunchCache(Cache.bind("/tmp/launch-cache")); createLifecycle(request).execute(); - assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-created-date.json")); + if (trustBuilder) { + assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-cache-bind-mounts.json")); + } + else { + assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer-cache-bind-mounts.json")); + assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector-cache-bind-mounts.json")); + assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer-cache-bind-mounts.json")); + assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder-cache-bind-mounts.json")); + assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-cache-bind-mounts.json")); + } assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } - @Test - void executeWithApplicationDirectoryExecutesPhases() throws Exception { + @ParameterizedTest + @BooleanValueSource + void executeWithCreatedDateExecutesPhases(boolean trustBuilder) throws Exception { given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - BuildRequest request = getTestRequest().withApplicationDirectory("/application"); + BuildRequest request = getTestRequest(trustBuilder).withCreatedDate("2020-07-01T12:34:56Z"); createLifecycle(request).execute(); - assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-app-dir.json")); + if (trustBuilder) { + assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-created-date.json")); + } + else { + assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json")); + assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json")); + assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer.json")); + assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json")); + assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-created-date.json")); + } assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } - @Test - void executeWithDockerHostAndRemoteAddressExecutesPhases() throws Exception { + @ParameterizedTest + @BooleanValueSource + void executeWithApplicationDirectoryExecutesPhases(boolean trustBuilder) throws Exception { given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - BuildRequest request = getTestRequest(); - createLifecycle(request, ResolvedDockerHost.from(new DockerHost("tcp://192.168.1.2:2376"))).execute(); - assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-inherit-remote.json")); + BuildRequest request = getTestRequest(trustBuilder).withApplicationDirectory("/application"); + createLifecycle(request).execute(); + if (trustBuilder) { + assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-app-dir.json")); + } + else { + assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json")); + assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector-app-dir.json")); + assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer.json")); + assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder-app-dir.json")); + assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-app-dir.json")); + } assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } - @Test - void executeWithDockerHostAndLocalAddressExecutesPhases() throws Exception { + @ParameterizedTest + @BooleanValueSource + void executeWithSecurityOptionsExecutesPhases(boolean trustBuilder) throws Exception { given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - BuildRequest request = getTestRequest(); - createLifecycle(request, ResolvedDockerHost.from(new DockerHost("/var/alt.sock"))).execute(); - assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-inherit-local.json")); + BuildRequest request = getTestRequest(trustBuilder) + .withSecurityOptions(List.of("label=user:USER", "label=role:ROLE")); + createLifecycle(request).execute(); + if (trustBuilder) { + assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-security-opts.json", true)); + } + else { + assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer-security-opts.json", true)); + assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json")); + assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer-security-opts.json", true)); + assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json")); + assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-security-opts.json", true)); + } + assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); + } + + @ParameterizedTest + @BooleanValueSource + void executeWithDockerHostAndRemoteAddressExecutesPhases(boolean trustBuilder) throws Exception { + given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); + BuildRequest request = getTestRequest(trustBuilder); + createLifecycle(request, ResolvedDockerHost.from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376"))) + .execute(); + if (trustBuilder) { + assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-inherit-remote.json")); + } + else { + assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer-inherit-remote.json")); + assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json")); + assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer-inherit-remote.json")); + assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json")); + assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-inherit-remote.json")); + } + assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); + } + + @ParameterizedTest + @BooleanValueSource + void executeWithDockerHostAndLocalAddressExecutesPhases(boolean trustBuilder) throws Exception { + given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); + BuildRequest request = getTestRequest(trustBuilder); + createLifecycle(request, ResolvedDockerHost.from(DockerHostConfiguration.forAddress("/var/alt.sock"))) + .execute(); + if (trustBuilder) { + assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-inherit-local.json")); + } + else { + assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer-inherit-local.json")); + assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json")); + assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer-inherit-local.json")); + assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json")); + assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-inherit-local.json")); + } assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } @@ -273,14 +408,16 @@ private DockerApi mockDockerApi() { return docker; } - private BuildRequest getTestRequest() { + private BuildRequest getTestRequest(boolean trustBuilder) { TarArchive content = mock(TarArchive.class); ImageReference name = ImageReference.of("my-application"); - return BuildRequest.of(name, (owner) -> content).withRunImage(ImageReference.of("cloudfoundry/run")); + return BuildRequest.of(name, (owner) -> content) + .withRunImage(ImageReference.of("cloudfoundry/run")) + .withTrustBuilder(trustBuilder); } - private Lifecycle createLifecycle() throws IOException { - return createLifecycle(getTestRequest()); + private Lifecycle createLifecycle(boolean trustBuilder) throws IOException { + return createLifecycle(getTestRequest(trustBuilder)); } private Lifecycle createLifecycle(BuildRequest request) throws IOException { @@ -288,9 +425,9 @@ private Lifecycle createLifecycle(BuildRequest request) throws IOException { return createLifecycle(request, builder); } - private Lifecycle createLifecycle(String builderMetadata) throws IOException { + private Lifecycle createLifecycle(boolean trustBuilder, String builderMetadata) throws IOException { EphemeralBuilder builder = mockEphemeralBuilder(builderMetadata); - return createLifecycle(getTestRequest(), builder); + return createLifecycle(getTestRequest(trustBuilder), builder); } private Lifecycle createLifecycle(BuildRequest request, ResolvedDockerHost dockerHost) throws IOException { @@ -342,12 +479,16 @@ private void assertPhaseWasRun(String name, IOConsumer configCo } private IOConsumer withExpectedConfig(String name) { + return withExpectedConfig(name, false); + } + + private IOConsumer withExpectedConfig(String name, boolean expectSecurityOptAlways) { return (config) -> { try { InputStream in = getClass().getResourceAsStream(name); String jsonString = FileCopyUtils.copyToString(new InputStreamReader(in, StandardCharsets.UTF_8)); JSONObject json = new JSONObject(jsonString); - if (Platform.isWindows()) { + if (!expectSecurityOptAlways && Platform.isWindows()) { JSONObject hostConfig = json.getJSONObject("HostConfig"); hostConfig.remove("SecurityOpt"); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PhaseTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PhaseTests.java index 7d5cd88cb0db..cb7d69285e31 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PhaseTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PhaseTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,7 +66,7 @@ void applyWhenWithDaemonAccessUpdatesConfigurationWithRootUser() { Update update = mock(Update.class); phase.apply(update); then(update).should().withUser("root"); - then(update).should().withCommand("/cnb/lifecycle/test", NO_ARGS); + then(update).should().withCommand("/cnb/lifecycle/test", "-daemon"); then(update).should().withLabel("author", "spring-boot"); then(update).shouldHaveNoMoreInteractions(); } @@ -74,7 +74,6 @@ void applyWhenWithDaemonAccessUpdatesConfigurationWithRootUser() { @Test void applyWhenWithLogLevelArgAndVerboseLoggingUpdatesConfigurationWithLogLevel() { Phase phase = new Phase("test", true); - phase.withLogLevelArg(); Update update = mock(Update.class); phase.apply(update); then(update).should().withCommand("/cnb/lifecycle/test", "-log-level", "debug"); @@ -85,7 +84,6 @@ void applyWhenWithLogLevelArgAndVerboseLoggingUpdatesConfigurationWithLogLevel() @Test void applyWhenWithLogLevelArgAndNonVerboseLoggingDoesNotUpdateLogLevel() { Phase phase = new Phase("test", false); - phase.withLogLevelArg(); Update update = mock(Update.class); phase.apply(update); then(update).should().withCommand("/cnb/lifecycle/test"); @@ -133,7 +131,7 @@ void applyWhenWithEnvUpdatesConfigurationWithEnv() { @Test void applyWhenWithNetworkModeUpdatesConfigurationWithNetworkMode() { - Phase phase = new Phase("test", true); + Phase phase = new Phase("test", false); phase.withNetworkMode("test"); Update update = mock(Update.class); phase.apply(update); @@ -145,7 +143,7 @@ void applyWhenWithNetworkModeUpdatesConfigurationWithNetworkMode() { @Test void applyWhenWithSecurityOptionsUpdatesConfigurationWithSecurityOptions() { - Phase phase = new Phase("test", true); + Phase phase = new Phase("test", false); phase.withSecurityOption("option1=value1"); phase.withSecurityOption("option2=value2"); Update update = mock(Update.class); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PrintStreamBuildLogTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PrintStreamBuildLogTests.java index 74cefced82ae..1e25ed10a998 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PrintStreamBuildLogTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PrintStreamBuildLogTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,7 +67,7 @@ void printsExpectedOutput() throws Exception { Consumer pullRunImageConsumer = log.pullingImage(runImageReference, ImageType.RUNNER); pullRunImageConsumer.accept(new TotalProgressEvent(100)); log.pulledImage(runImage, ImageType.RUNNER); - log.executingLifecycle(request, LifecycleVersion.parse("0.5"), VolumeName.of("pack-abc.cache")); + log.executingLifecycle(request, LifecycleVersion.parse("0.5"), Cache.volume(VolumeName.of("pack-abc.cache"))); Consumer phase1Consumer = log.runningPhase(request, "alphabet"); phase1Consumer.accept(mockLogEvent("one")); phase1Consumer.accept(mockLogEvent("two")); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/StackIdTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/StackIdTests.java index 53de18ca94b1..a804b596a317 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/StackIdTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/StackIdTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -43,12 +42,12 @@ void fromImageWhenImageIsNullThrowsException() { } @Test - void fromImageWhenLabelIsMissingThrowsException() { + void fromImageWhenLabelIsMissingHasNoId() { Image image = mock(Image.class); ImageConfig imageConfig = mock(ImageConfig.class); given(image.getConfig()).willReturn(imageConfig); - assertThatIllegalStateException().isThrownBy(() -> StackId.fromImage(image)) - .withMessage("Missing 'io.buildpacks.stack.id' stack label"); + StackId stackId = StackId.fromImage(image); + assertThat(stackId.hasId()).isFalse(); } @Test @@ -59,6 +58,7 @@ void fromImageCreatesStackId() { given(imageConfig.getLabels()).willReturn(Collections.singletonMap("io.buildpacks.stack.id", "test")); StackId stackId = StackId.fromImage(image); assertThat(stackId).hasToString("test"); + assertThat(stackId.hasId()).isTrue(); } @Test diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java index 814914861b2d..4f1dce08ace8 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java @@ -313,12 +313,14 @@ void inspectInspectImage() throws Exception { } @Test + @SuppressWarnings("removal") void exportLayersWhenReferenceIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> this.api.exportLayerFiles(null, (name, archive) -> { })).withMessage("Reference must not be null"); } @Test + @SuppressWarnings("removal") void exportLayersWhenExportsIsNullThrowsException() { ImageReference reference = ImageReference.of("gcr.io/paketo-buildpacks/builder:base"); assertThatIllegalArgumentException().isThrownBy(() -> this.api.exportLayerFiles(reference, null)) @@ -336,10 +338,10 @@ void exportLayersExportsLayerTars() throws Exception { archive.writeTo(out); try (TarArchiveInputStream in = new TarArchiveInputStream( new ByteArrayInputStream(out.toByteArray()))) { - TarArchiveEntry entry = in.getNextTarEntry(); + TarArchiveEntry entry = in.getNextEntry(); while (entry != null) { contents.add(name, entry.getName()); - entry = in.getNextTarEntry(); + entry = in.getNextEntry(); } } }); @@ -364,10 +366,10 @@ void exportLayersWithSymlinksExportsLayerTars() throws Exception { archive.writeTo(out); try (TarArchiveInputStream in = new TarArchiveInputStream( new ByteArrayInputStream(out.toByteArray()))) { - TarArchiveEntry entry = in.getNextTarEntry(); + TarArchiveEntry entry = in.getNextEntry(); while (entry != null) { contents.add(name, entry.getName()); - entry = in.getNextTarEntry(); + entry = in.getNextEntry(); } } }); @@ -393,14 +395,15 @@ void exportLayerFilesDeletesTempFiles() throws Exception { } @Test + @SuppressWarnings("removal") void exportLayersWithNoManifestThrowsException() throws Exception { ImageReference reference = ImageReference.of("gcr.io/paketo-buildpacks/builder:base"); URI exportUri = new URI(IMAGES_URL + "/gcr.io/paketo-buildpacks/builder:base/get"); given(DockerApiTests.this.http.get(exportUri)).willReturn(responseOf("export-no-manifest.tar")); - assertThatIllegalArgumentException() - .isThrownBy(() -> this.api.exportLayerFiles(reference, (name, archive) -> { - })) - .withMessageContaining("Manifest not found in image " + reference); + String expectedMessage = "Exported image '%s' does not contain 'index.json' or 'manifest.json'" + .formatted(reference); + assertThatIllegalStateException().isThrownBy(() -> this.api.exportLayerFiles(reference, (name, archive) -> { + })).withMessageContaining(expectedMessage); } @Test diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ExportedImageTarTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ExportedImageTarTests.java new file mode 100644 index 000000000000..253ed2cb03dc --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ExportedImageTarTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.buildpack.platform.docker; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import org.springframework.boot.buildpack.platform.docker.type.ImageReference; +import org.springframework.boot.buildpack.platform.io.TarArchive.Compression; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ExportedImageTar}. + * + * @author Phillip Webb + * @author Scott Frederick + */ +class ExportedImageTarTests { + + @ParameterizedTest + @ValueSource(strings = { "export-docker-desktop.tar", "export-docker-desktop-containerd.tar", + "export-docker-desktop-containerd-manifest-list.tar", "export-docker-engine.tar", "export-podman.tar" }) + void test(String tarFile) throws Exception { + ImageReference reference = ImageReference.of("test:latest"); + try (ExportedImageTar exportedImageTar = new ExportedImageTar(reference, + getClass().getResourceAsStream(tarFile))) { + Compression expectedCompression = (!tarFile.contains("containerd")) ? Compression.NONE : Compression.GZIP; + String expectedName = (expectedCompression != Compression.GZIP) + ? "5caae51697b248b905dca1a4160864b0e1a15c300981736555cdce6567e8d477" + : "f0f1fd1bdc71ac6a4dc99cea5f5e45c86c5ec26fe4d1daceeb78207303606429"; + exportedImageTar.exportLayers((name, tarArchive) -> { + assertThat(name).contains(expectedName); + assertThat(tarArchive.getCompression()).isEqualTo(expectedCompression); + }); + } + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadataTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadataTests.java new file mode 100644 index 000000000000..b47bbaa3d80c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadataTests.java @@ -0,0 +1,107 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.buildpack.platform.docker.configuration; + +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Paths; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.regex.Pattern; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerContext; +import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link DockerConfigurationMetadata}. + * + * @author Scott Frederick + */ +class DockerConfigurationMetadataTests extends AbstractJsonTests { + + private final Map environment = new LinkedHashMap<>(); + + @Test + void configWithContextIsRead() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-context/config.json")); + DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get); + assertThat(config.getConfiguration().getCurrentContext()).isEqualTo("test-context"); + assertThat(config.getContext().getDockerHost()).isEqualTo("unix:///home/user/.docker/docker.sock"); + assertThat(config.getContext().isTlsVerify()).isFalse(); + assertThat(config.getContext().getTlsPath()).isNull(); + } + + @Test + void configWithoutContextIsRead() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("without-context/config.json")); + DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get); + assertThat(config.getConfiguration().getCurrentContext()).isNull(); + assertThat(config.getContext().getDockerHost()).isNull(); + assertThat(config.getContext().isTlsVerify()).isFalse(); + assertThat(config.getContext().getTlsPath()).isNull(); + } + + @Test + void configWithDefaultContextIsRead() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); + DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get); + assertThat(config.getConfiguration().getCurrentContext()).isEqualTo("default"); + assertThat(config.getContext().getDockerHost()).isNull(); + assertThat(config.getContext().isTlsVerify()).isFalse(); + assertThat(config.getContext().getTlsPath()).isNull(); + } + + @Test + void configIsReadWithProvidedContext() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); + DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get); + DockerContext context = config.forContext("test-context"); + assertThat(context.getDockerHost()).isEqualTo("unix:///home/user/.docker/docker.sock"); + assertThat(context.isTlsVerify()).isTrue(); + assertThat(context.getTlsPath()).matches(String.join(Pattern.quote(File.separator), "^.*", + "with-default-context", "contexts", "tls", "[a-zA-z0-9]*", "docker$")); + } + + @Test + void invalidContextThrowsException() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); + assertThatIllegalArgumentException() + .isThrownBy(() -> DockerConfigurationMetadata.from(this.environment::get).forContext("invalid-context")) + .withMessageContaining("Docker context 'invalid-context' does not exist"); + } + + @Test + void configIsEmptyWhenConfigFileDoesNotExist() { + this.environment.put("DOCKER_CONFIG", "docker-config-dummy-path"); + DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get); + assertThat(config.getConfiguration().getCurrentContext()).isNull(); + assertThat(config.getContext().getDockerHost()).isNull(); + assertThat(config.getContext().isTlsVerify()).isFalse(); + } + + private String pathToResource(String resource) throws URISyntaxException { + URL url = getClass().getResource(resource); + return Paths.get(url.toURI()).getParent().toAbsolutePath().toString(); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHostTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHostTests.java index 30a1c358304b..131299849788 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHostTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHostTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,11 @@ package org.springframework.boot.buildpack.platform.docker.configuration; import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.LinkedHashMap; import java.util.Map; @@ -28,6 +31,8 @@ import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.io.TempDir; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -41,7 +46,8 @@ class ResolvedDockerHostTests { @Test @DisabledOnOs(OS.WINDOWS) - void resolveWhenDockerHostIsNullReturnsLinuxDefault() { + void resolveWhenDockerHostIsNullReturnsLinuxDefault() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, null); assertThat(dockerHost.getAddress()).isEqualTo("/var/run/docker.sock"); assertThat(dockerHost.isSecure()).isFalse(); @@ -50,7 +56,8 @@ void resolveWhenDockerHostIsNullReturnsLinuxDefault() { @Test @EnabledOnOs(OS.WINDOWS) - void resolveWhenDockerHostIsNullReturnsWindowsDefault() { + void resolveWhenDockerHostIsNullReturnsWindowsDefault() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, null); assertThat(dockerHost.getAddress()).isEqualTo("//./pipe/docker_engine"); assertThat(dockerHost.isSecure()).isFalse(); @@ -59,8 +66,10 @@ void resolveWhenDockerHostIsNullReturnsWindowsDefault() { @Test @DisabledOnOs(OS.WINDOWS) - void resolveWhenDockerHostAddressIsNullReturnsLinuxDefault() { - ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, new DockerHost(null)); + void resolveWhenDockerHostAddressIsNullReturnsLinuxDefault() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, + DockerHostConfiguration.forAddress(null)); assertThat(dockerHost.getAddress()).isEqualTo("/var/run/docker.sock"); assertThat(dockerHost.isSecure()).isFalse(); assertThat(dockerHost.getCertificatePath()).isNull(); @@ -70,7 +79,7 @@ void resolveWhenDockerHostAddressIsNullReturnsLinuxDefault() { void resolveWhenDockerHostAddressIsLocalReturnsAddress(@TempDir Path tempDir) throws IOException { String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, - new DockerHost(socketFilePath, false, null)); + DockerHostConfiguration.forAddress(socketFilePath)); assertThat(dockerHost.isLocalFileReference()).isTrue(); assertThat(dockerHost.isRemote()).isFalse(); assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath); @@ -82,7 +91,7 @@ void resolveWhenDockerHostAddressIsLocalReturnsAddress(@TempDir Path tempDir) th void resolveWhenDockerHostAddressIsLocalWithSchemeReturnsAddress(@TempDir Path tempDir) throws IOException { String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, - new DockerHost("unix://" + socketFilePath, false, null)); + DockerHostConfiguration.forAddress("unix://" + socketFilePath)); assertThat(dockerHost.isLocalFileReference()).isTrue(); assertThat(dockerHost.isRemote()).isFalse(); assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath); @@ -93,7 +102,7 @@ void resolveWhenDockerHostAddressIsLocalWithSchemeReturnsAddress(@TempDir Path t @Test void resolveWhenDockerHostAddressIsHttpReturnsAddress() { ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, - new DockerHost("http://docker.example.com", false, null)); + DockerHostConfiguration.forAddress("http://docker.example.com")); assertThat(dockerHost.isLocalFileReference()).isFalse(); assertThat(dockerHost.isRemote()).isTrue(); assertThat(dockerHost.getAddress()).isEqualTo("http://docker.example.com"); @@ -104,7 +113,7 @@ void resolveWhenDockerHostAddressIsHttpReturnsAddress() { @Test void resolveWhenDockerHostAddressIsHttpsReturnsAddress() { ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, - new DockerHost("https://docker.example.com", true, "/cert-path")); + DockerHostConfiguration.forAddress("https://docker.example.com", true, "/cert-path")); assertThat(dockerHost.isLocalFileReference()).isFalse(); assertThat(dockerHost.isRemote()).isTrue(); assertThat(dockerHost.getAddress()).isEqualTo("https://docker.example.com"); @@ -115,7 +124,7 @@ void resolveWhenDockerHostAddressIsHttpsReturnsAddress() { @Test void resolveWhenDockerHostAddressIsTcpReturnsAddress() { ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, - new DockerHost("tcp://192.168.99.100:2376", true, "/cert-path")); + DockerHostConfiguration.forAddress("tcp://192.168.99.100:2376", true, "/cert-path")); assertThat(dockerHost.isLocalFileReference()).isFalse(); assertThat(dockerHost.isRemote()).isTrue(); assertThat(dockerHost.getAddress()).isEqualTo("tcp://192.168.99.100:2376"); @@ -128,7 +137,7 @@ void resolveWhenEnvironmentAddressIsLocalReturnsAddress(@TempDir Path tempDir) t String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); this.environment.put("DOCKER_HOST", socketFilePath); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, - new DockerHost("/unused", true, "/unused")); + DockerHostConfiguration.forAddress("/unused")); assertThat(dockerHost.isLocalFileReference()).isTrue(); assertThat(dockerHost.isRemote()).isFalse(); assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath); @@ -141,7 +150,7 @@ void resolveWhenEnvironmentAddressIsLocalWithSchemeReturnsAddress(@TempDir Path String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); this.environment.put("DOCKER_HOST", "unix://" + socketFilePath); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, - new DockerHost("/unused", true, "/unused")); + DockerHostConfiguration.forAddress("/unused")); assertThat(dockerHost.isLocalFileReference()).isTrue(); assertThat(dockerHost.isRemote()).isFalse(); assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath); @@ -155,7 +164,7 @@ void resolveWhenEnvironmentAddressIsTcpReturnsAddress() { this.environment.put("DOCKER_TLS_VERIFY", "1"); this.environment.put("DOCKER_CERT_PATH", "/cert-path"); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, - new DockerHost("tcp://1.1.1.1", false, "/unused")); + DockerHostConfiguration.forAddress("tcp://1.1.1.1")); assertThat(dockerHost.isLocalFileReference()).isFalse(); assertThat(dockerHost.isRemote()).isTrue(); assertThat(dockerHost.getAddress()).isEqualTo("tcp://192.168.99.100:2376"); @@ -163,4 +172,39 @@ void resolveWhenEnvironmentAddressIsTcpReturnsAddress() { assertThat(dockerHost.getCertificatePath()).isEqualTo("/cert-path"); } + @Test + void resolveWithDockerHostContextReturnsAddress() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, + DockerHostConfiguration.forContext("test-context")); + assertThat(dockerHost.getAddress()).isEqualTo("/home/user/.docker/docker.sock"); + assertThat(dockerHost.isSecure()).isTrue(); + assertThat(dockerHost.getCertificatePath()).isNotNull(); + } + + @Test + void resolveWithDockerConfigMetadataContextReturnsAddress() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-context/config.json")); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, null); + assertThat(dockerHost.getAddress()).isEqualTo("/home/user/.docker/docker.sock"); + assertThat(dockerHost.isSecure()).isFalse(); + assertThat(dockerHost.getCertificatePath()).isNull(); + } + + @Test + void resolveWhenEnvironmentHasAddressAndContextPrefersContext() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-context/config.json")); + this.environment.put("DOCKER_CONTEXT", "test-context"); + this.environment.put("DOCKER_HOST", "notused"); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, null); + assertThat(dockerHost.getAddress()).isEqualTo("/home/user/.docker/docker.sock"); + assertThat(dockerHost.isSecure()).isFalse(); + assertThat(dockerHost.getCertificatePath()).isNull(); + } + + private String pathToResource(String resource) throws URISyntaxException { + URL url = getClass().getResource(resource); + return Paths.get(url.toURI()).getParent().toAbsolutePath().toString(); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java index c04cd5e719db..a383d0240ec9 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; import static org.assertj.core.api.Assertions.assertThat; @@ -37,21 +37,21 @@ class HttpTransportTests { @Test void createWhenDockerHostVariableIsAddressReturnsRemote() { - HttpTransport transport = HttpTransport.create(new DockerHost("tcp://192.168.1.0")); + HttpTransport transport = HttpTransport.create(DockerHostConfiguration.forAddress("tcp://192.168.1.0")); assertThat(transport).isInstanceOf(RemoteHttpClientTransport.class); } @Test void createWhenDockerHostVariableIsFileReturnsLocal(@TempDir Path tempDir) throws IOException { String dummySocketFilePath = Files.createTempFile(tempDir, "http-transport", null).toAbsolutePath().toString(); - HttpTransport transport = HttpTransport.create(new DockerHost(dummySocketFilePath)); + HttpTransport transport = HttpTransport.create(DockerHostConfiguration.forAddress(dummySocketFilePath)); assertThat(transport).isInstanceOf(LocalHttpClientTransport.class); } @Test void createWhenDockerHostVariableIsUnixSchemePrefixedFileReturnsLocal(@TempDir Path tempDir) throws IOException { String dummySocketFilePath = "unix://" + Files.createTempFile(tempDir, "http-transport", null).toAbsolutePath(); - HttpTransport transport = HttpTransport.create(new DockerHost(dummySocketFilePath)); + HttpTransport transport = HttpTransport.create(DockerHostConfiguration.forAddress(dummySocketFilePath)); assertThat(transport).isInstanceOf(LocalHttpClientTransport.class); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransportTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransportTests.java index 78ff1d0c71fe..81cd780c5b04 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransportTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import static org.assertj.core.api.Assertions.assertThat; @@ -39,24 +39,28 @@ class LocalHttpClientTransportTests { @Test void createWhenDockerHostIsFileReturnsTransport(@TempDir Path tempDir) throws IOException { String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); - ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost(socketFilePath)); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(DockerHostConfiguration.forAddress(socketFilePath)); LocalHttpClientTransport transport = LocalHttpClientTransport.create(dockerHost); assertThat(transport).isNotNull(); + assertThat(transport.getHost().toHostString()).isEqualTo(socketFilePath); } @Test void createWhenDockerHostIsFileThatDoesNotExistReturnsTransport(@TempDir Path tempDir) { String socketFilePath = Paths.get(tempDir.toString(), "dummy").toAbsolutePath().toString(); - ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost(socketFilePath)); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(DockerHostConfiguration.forAddress(socketFilePath)); LocalHttpClientTransport transport = LocalHttpClientTransport.create(dockerHost); assertThat(transport).isNotNull(); + assertThat(transport.getHost().toHostString()).isEqualTo(socketFilePath); } @Test void createWhenDockerHostIsAddressReturnsTransport() { - ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost("tcp://192.168.1.2:2376")); + ResolvedDockerHost dockerHost = ResolvedDockerHost + .from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376")); LocalHttpClientTransport transport = LocalHttpClientTransport.create(dockerHost); assertThat(transport).isNotNull(); + assertThat(transport.getHost().toHostString()).isEqualTo("tcp://192.168.1.2:2376"); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java index a56373709eff..529709d5cc38 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java @@ -23,7 +23,7 @@ import org.apache.hc.core5.http.HttpHost; import org.junit.jupiter.api.Test; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.docker.ssl.SslContextFactory; @@ -49,28 +49,31 @@ void createIfPossibleWhenDockerHostIsNotSetReturnsNull() { @Test void createIfPossibleWhenDockerHostIsDefaultReturnsNull() { - ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost(null)); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(DockerHostConfiguration.forAddress(null)); RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost); assertThat(transport).isNull(); } @Test void createIfPossibleWhenDockerHostIsFileReturnsNull() { - ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost("unix:///var/run/socket.sock")); + ResolvedDockerHost dockerHost = ResolvedDockerHost + .from(DockerHostConfiguration.forAddress("unix:///var/run/socket.sock")); RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost); assertThat(transport).isNull(); } @Test void createIfPossibleWhenDockerHostIsAddressReturnsTransport() { - ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost("tcp://192.168.1.2:2376")); + ResolvedDockerHost dockerHost = ResolvedDockerHost + .from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376")); RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost); assertThat(transport).isNotNull(); } @Test void createIfPossibleWhenNoTlsVerifyUsesHttp() { - ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost("tcp://192.168.1.2:2376")); + ResolvedDockerHost dockerHost = ResolvedDockerHost + .from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376")); RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost); assertThat(transport.getHost()).satisfies(hostOf("http", "192.168.1.2", 2376)); } @@ -80,14 +83,15 @@ void createIfPossibleWhenTlsVerifyUsesHttps() throws Exception { SslContextFactory sslContextFactory = mock(SslContextFactory.class); given(sslContextFactory.forDirectory("/test-cert-path")).willReturn(SSLContext.getDefault()); ResolvedDockerHost dockerHost = ResolvedDockerHost - .from(new DockerHost("tcp://192.168.1.2:2376", true, "/test-cert-path")); + .from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376", true, "/test-cert-path")); RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost, sslContextFactory); assertThat(transport.getHost()).satisfies(hostOf("https", "192.168.1.2", 2376)); } @Test void createIfPossibleWhenTlsVerifyWithMissingCertPathThrowsException() { - ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost("tcp://192.168.1.2:2376", true, null)); + ResolvedDockerHost dockerHost = ResolvedDockerHost + .from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376", true, null)); assertThatIllegalArgumentException().isThrownBy(() -> RemoteHttpClientTransport.createIfPossible(dockerHost)) .withMessageContaining("Docker host TLS verification requires trust material"); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveIndexTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveIndexTests.java new file mode 100644 index 000000000000..87117ddd0fb9 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveIndexTests.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.buildpack.platform.docker.type; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ImageArchiveIndex}. + * + * @author Phillip Webb + */ +class ImageArchiveIndexTests extends AbstractJsonTests { + + @Test + void loadJson() throws IOException { + String content = getContentAsString("image-archive-index.json"); + ImageArchiveIndex index = getIndex(content); + assertThat(index.getSchemaVersion()).isEqualTo(2); + assertThat(index.getManifests()).hasSize(1); + BlobReference manifest = index.getManifests().get(0); + assertThat(manifest.getMediaType()).isEqualTo("application/vnd.docker.distribution.manifest.list.v2+json"); + assertThat(manifest.getDigest()) + .isEqualTo("sha256:3bbe02431d8e5124ffe816ec27bf6508b50edd1d10218be1a03e799a186b9004"); + } + + private ImageArchiveIndex getIndex(String content) throws IOException { + return new ImageArchiveIndex(getObjectMapper().readTree(content)); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveManifestTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveManifestTests.java index 31cd8a768cdf..76fd174cb147 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveManifestTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveManifestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,8 @@ class ImageArchiveManifestTests extends AbstractJsonTests { @Test void getLayersReturnsLayers() throws Exception { - ImageArchiveManifest manifest = getManifest(); + String content = getContentAsString("image-archive-manifest.json"); + ImageArchiveManifest manifest = getManifest(content); List expectedLayers = new ArrayList<>(); for (int blankLayersCount = 0; blankLayersCount < 46; blankLayersCount++) { expectedLayers.add("blank_" + blankLayersCount); @@ -50,20 +51,20 @@ void getLayersReturnsLayers() throws Exception { @Test void getLayersWithNoLayersReturnsEmptyList() throws Exception { String content = "[{\"Layers\": []}]"; - ImageArchiveManifest manifest = new ImageArchiveManifest(getObjectMapper().readTree(content)); + ImageArchiveManifest manifest = getManifest(content); assertThat(manifest.getEntries()).hasSize(1); - assertThat(manifest.getEntries().get(0).getLayers()).hasSize(0); + assertThat(manifest.getEntries().get(0).getLayers()).isEmpty(); } @Test void getLayersWithEmptyManifestReturnsEmptyList() throws Exception { String content = "[]"; - ImageArchiveManifest manifest = new ImageArchiveManifest(getObjectMapper().readTree(content)); + ImageArchiveManifest manifest = getManifest(content); assertThat(manifest.getEntries()).isEmpty(); } - private ImageArchiveManifest getManifest() throws IOException { - return new ImageArchiveManifest(getObjectMapper().readTree(getContent("image-archive-manifest.json"))); + private ImageArchiveManifest getManifest(String content) throws IOException { + return new ImageArchiveManifest(getObjectMapper().readTree(content)); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveTests.java index 20717b8a5d7c..123f49bbcdd9 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveTests.java @@ -54,14 +54,14 @@ void fromImageWritesToValidArchiveTar() throws Exception { try (TarArchiveInputStream tar = new TarArchiveInputStream( new ByteArrayInputStream(outputStream.toByteArray()))) { for (int i = 0; i < EXISTING_IMAGE_LAYER_COUNT; i++) { - TarArchiveEntry blankEntry = tar.getNextTarEntry(); + TarArchiveEntry blankEntry = tar.getNextEntry(); assertThat(blankEntry.getName()).isEqualTo("blank_" + i); } - TarArchiveEntry layerEntry = tar.getNextTarEntry(); + TarArchiveEntry layerEntry = tar.getNextEntry(); byte[] layerContent = read(tar, layerEntry.getSize()); - TarArchiveEntry configEntry = tar.getNextTarEntry(); + TarArchiveEntry configEntry = tar.getNextEntry(); byte[] configContent = read(tar, configEntry.getSize()); - TarArchiveEntry manifestEntry = tar.getNextTarEntry(); + TarArchiveEntry manifestEntry = tar.getNextEntry(); byte[] manifestContent = read(tar, manifestEntry.getSize()); assertExpectedLayer(layerEntry, layerContent); assertExpectedConfig(configEntry, configContent); @@ -72,7 +72,7 @@ void fromImageWritesToValidArchiveTar() throws Exception { private void assertExpectedLayer(TarArchiveEntry entry, byte[] content) throws Exception { assertThat(entry.getName()).isEqualTo("bb09e17fd1bd2ee47155f1349645fcd9fff31e1247c7ed99cad469f1c16a4216.tar"); try (TarArchiveInputStream tar = new TarArchiveInputStream(new ByteArrayInputStream(content))) { - TarArchiveEntry contentEntry = tar.getNextTarEntry(); + TarArchiveEntry contentEntry = tar.getNextEntry(); assertThat(contentEntry.getName()).isEqualTo("/spring/"); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceTests.java index 9ec351e503db..b22c27e1ba2c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceTests.java @@ -255,7 +255,7 @@ void randomWherePrefixIsNullThrowsException() { void inTaggedFormWhenHasDigestThrowsException() { ImageReference reference = ImageReference .of("ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); - assertThatIllegalStateException().isThrownBy(() -> reference.inTaggedForm()) + assertThatIllegalStateException().isThrownBy(reference::inTaggedForm) .withMessage( "Image reference 'docker.io/library/ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d' cannot contain a digest"); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/LayerTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/LayerTests.java index b07861d7fb55..bad2124fb8d5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/LayerTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/LayerTests.java @@ -61,9 +61,9 @@ void ofCreatesLayer() throws Exception { layer.writeTo(outputStream); try (TarArchiveInputStream tarStream = new TarArchiveInputStream( new ByteArrayInputStream(outputStream.toByteArray()))) { - assertThat(tarStream.getNextTarEntry().getName()).isEqualTo("/directory/"); - assertThat(tarStream.getNextTarEntry().getName()).isEqualTo("/directory/file"); - assertThat(tarStream.getNextTarEntry()).isNull(); + assertThat(tarStream.getNextEntry().getName()).isEqualTo("/directory/"); + assertThat(tarStream.getNextEntry().getName()).isEqualTo("/directory/file"); + assertThat(tarStream.getNextEntry()).isNull(); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ManifestListTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ManifestListTests.java new file mode 100644 index 000000000000..148b9cd93503 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ManifestListTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.buildpack.platform.docker.type; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ManifestList}. + * + * @author Phillip Webb + */ +class ManifestListTests extends AbstractJsonTests { + + @Test + void loadJsonFromDistributionManifestList() throws IOException { + String content = getContentAsString("distribution-manifest-list.json"); + ManifestList manifestList = getManifestList(content); + assertThat(manifestList.getSchemaVersion()).isEqualTo(2); + assertThat(manifestList.getMediaType()).isEqualTo("application/vnd.docker.distribution.manifest.list.v2+json"); + assertThat(manifestList.getManifests()).hasSize(2); + } + + private ManifestList getManifestList(String content) throws IOException { + return new ManifestList(getObjectMapper().readTree(content)); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ManifestTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ManifestTests.java new file mode 100644 index 000000000000..ac24305d0e1e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ManifestTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.buildpack.platform.docker.type; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link Manifest}. + * + * @author Phillip Webb + */ +class ManifestTests extends AbstractJsonTests { + + @Test + void loadJsonFromDistributionManifest() throws IOException { + String content = getContentAsString("distribution-manifest.json"); + Manifest manifestList = getManifest(content); + assertThat(manifestList.getSchemaVersion()).isEqualTo(2); + assertThat(manifestList.getMediaType()).isEqualTo("application/vnd.docker.distribution.manifest.v2+json"); + assertThat(manifestList.getLayers()).hasSize(1); + } + + @Test + void loadJsonFromImageManifest() throws IOException { + String content = getContentAsString("image-manifest.json"); + Manifest manifestList = getManifest(content); + assertThat(manifestList.getSchemaVersion()).isEqualTo(2); + assertThat(manifestList.getMediaType()).isEqualTo("application/vnd.oci.image.manifest.v1+json"); + assertThat(manifestList.getLayers()).hasSize(1); + } + + private Manifest getManifest(String content) throws IOException { + return new Manifest(getObjectMapper().readTree(content)); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/TarArchiveTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/TarArchiveTests.java index 615dc03a0fa6..1a587eb5be61 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/TarArchiveTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/TarArchiveTests.java @@ -58,10 +58,10 @@ void ofWritesTarContent() throws Exception { try (TarArchiveInputStream tarStream = new TarArchiveInputStream( new ByteArrayInputStream(outputStream.toByteArray()))) { List entries = new ArrayList<>(); - TarArchiveEntry entry = tarStream.getNextTarEntry(); + TarArchiveEntry entry = tarStream.getNextEntry(); while (entry != null) { entries.add(entry); - entry = tarStream.getNextTarEntry(); + entry = tarStream.getNextEntry(); } assertThat(entries).hasSize(6); assertThat(entries.get(0).getName()).isEqualTo("/workspace/"); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/TarLayoutWriterTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/TarLayoutWriterTests.java index 1bb1d87fdbd2..f212ad69fe56 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/TarLayoutWriterTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/TarLayoutWriterTests.java @@ -44,8 +44,8 @@ void writesTarArchive() throws Exception { } try (TarArchiveInputStream tarInputStream = new TarArchiveInputStream( new ByteArrayInputStream(outputStream.toByteArray()))) { - TarArchiveEntry directoryEntry = tarInputStream.getNextTarEntry(); - TarArchiveEntry fileEntry = tarInputStream.getNextTarEntry(); + TarArchiveEntry directoryEntry = tarInputStream.getNextEntry(); + TarArchiveEntry fileEntry = tarInputStream.getNextEntry(); byte[] fileContent = new byte[(int) fileEntry.getSize()]; tarInputStream.read(fileContent); assertThat(tarInputStream.getNextEntry()).isNull(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/ZipFileTarArchiveTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/ZipFileTarArchiveTests.java index a1c887510d26..48c2f6f70e11 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/ZipFileTarArchiveTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/ZipFileTarArchiveTests.java @@ -67,11 +67,11 @@ void writeToAdaptsContent() throws Exception { tarArchive.writeTo(outputStream); try (TarArchiveInputStream tarStream = new TarArchiveInputStream( new ByteArrayInputStream(outputStream.toByteArray()))) { - TarArchiveEntry dirEntry = tarStream.getNextTarEntry(); + TarArchiveEntry dirEntry = tarStream.getNextEntry(); assertThat(dirEntry.getName()).isEqualTo("spring/"); assertThat(dirEntry.getLongUserId()).isEqualTo(123); assertThat(dirEntry.getLongGroupId()).isEqualTo(456); - TarArchiveEntry fileEntry = tarStream.getNextTarEntry(); + TarArchiveEntry fileEntry = tarStream.getNextEntry(); assertThat(fileEntry.getName()).isEqualTo("spring/boot"); assertThat(fileEntry.getLongUserId()).isEqualTo(123); assertThat(fileEntry.getLongGroupId()).isEqualTo(456); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image-with-empty-stack.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image-with-empty-stack.json new file mode 100644 index 000000000000..faf454eeeb56 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image-with-empty-stack.json @@ -0,0 +1,130 @@ +{ + "Id": "sha256:44cc64492fb6a6d78d3e6d087f380ae6e479aa1b2c79823b32cdacfcc2f3d715", + "RepoTags": [ + "paketo-buildpacks/cnb:base", + "paketo-buildpacks/builder:base-platform-api-0.2" + ], + "RepoDigests": [ + "paketo-buidpacks/cnb@sha256:5b03a853e636b78c44e475bbc514e2b7b140cc41cca8ab907e9753431ae8c0b0" + ], + "Parent": "", + "Comment": "", + "Created": "1980-01-01T00:00:01Z", + "Container": "", + "ContainerConfig": { + "Hostname": "", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": null, + "Image": "", + "Volumes": null, + "WorkingDir": "", + "Entrypoint": null, + "OnBuild": null, + "Labels": null + }, + "DockerVersion": "", + "Author": "", + "Config": { + "Hostname": "", + "Domainname": "", + "User": "1000:1000", + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "CNB_USER_ID=1000", + "CNB_GROUP_ID=1000", + "CNB_STACK_ID=io.buildpacks.stacks.bionic" + ], + "Cmd": [ + "/bin/bash" + ], + "ArgsEscaped": true, + "Image": "sha256:2d153261a5e359c632a17377cfb5d1986c27b96c8b6e95334bf80f1029dbd4bb", + "Volumes": null, + "WorkingDir": "/layers", + "Entrypoint": null, + "OnBuild": null, + "Labels": { + "io.buildpacks.builder.metadata": "{\"description\":\"Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang\",\"buildpacks\":[{\"id\":\"paketo-buildpacks/dotnet-core\",\"version\":\"0.0.9\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-core\"},{\"id\":\"paketo-buildpacks/dotnet-core-runtime\",\"version\":\"0.0.201\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-core-runtime\"},{\"id\":\"paketo-buildpacks/dotnet-core-sdk\",\"version\":\"0.0.196\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-core-sdk\"},{\"id\":\"paketo-buildpacks/dotnet-execute\",\"version\":\"0.0.180\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-execute\"},{\"id\":\"paketo-buildpacks/dotnet-publish\",\"version\":\"0.0.121\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-publish\"},{\"id\":\"paketo-buildpacks/dotnet-core-aspnet\",\"version\":\"0.0.196\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-core-aspnet\"},{\"id\":\"paketo-buildpacks/java-native-image\",\"version\":\"4.7.0\",\"homepage\":\"https://github.com/paketo-buildpacks/java-native-image\"},{\"id\":\"paketo-buildpacks/spring-boot\",\"version\":\"3.5.0\",\"homepage\":\"https://github.com/paketo-buildpacks/spring-boot\"},{\"id\":\"paketo-buildpacks/executable-jar\",\"version\":\"3.1.3\",\"homepage\":\"https://github.com/paketo-buildpacks/executable-jar\"},{\"id\":\"paketo-buildpacks/graalvm\",\"version\":\"4.1.0\",\"homepage\":\"https://github.com/paketo-buildpacks/graalvm\"},{\"id\":\"paketo-buildpacks/gradle\",\"version\":\"3.5.0\",\"homepage\":\"https://github.com/paketo-buildpacks/gradle\"},{\"id\":\"paketo-buildpacks/leiningen\",\"version\":\"1.2.1\",\"homepage\":\"https://github.com/paketo-buildpacks/leiningen\"},{\"id\":\"paketo-buildpacks/sbt\",\"version\":\"3.6.0\",\"homepage\":\"https://github.com/paketo-buildpacks/sbt\"},{\"id\":\"paketo-buildpacks/spring-boot-native-image\",\"version\":\"2.0.1\",\"homepage\":\"https://github.com/paketo-buildpacks/spring-boot-native-image\"},{\"id\":\"paketo-buildpacks/environment-variables\",\"version\":\"2.1.2\",\"homepage\":\"https://github.com/paketo-buildpacks/environment-variables\"},{\"id\":\"paketo-buildpacks/image-labels\",\"version\":\"2.0.7\",\"homepage\":\"https://github.com/paketo-buildpacks/image-labels\"},{\"id\":\"paketo-buildpacks/maven\",\"version\":\"3.2.1\",\"homepage\":\"https://github.com/paketo-buildpacks/maven\"},{\"id\":\"paketo-buildpacks/java\",\"version\":\"4.10.0\",\"homepage\":\"https://github.com/paketo-buildpacks/java\"},{\"id\":\"paketo-buildpacks/ca-certificates\",\"version\":\"1.0.1\",\"homepage\":\"https://github.com/paketo-buildpacks/ca-certificates\"},{\"id\":\"paketo-buildpacks/environment-variables\",\"version\":\"2.1.2\",\"homepage\":\"https://github.com/paketo-buildpacks/environment-variables\"},{\"id\":\"paketo-buildpacks/executable-jar\",\"version\":\"3.1.3\",\"homepage\":\"https://github.com/paketo-buildpacks/executable-jar\"},{\"id\":\"paketo-buildpacks/procfile\",\"version\":\"3.0.0\",\"homepage\":\"https://github.com/paketo-buildpacks/procfile\"},{\"id\":\"paketo-buildpacks/apache-tomcat\",\"version\":\"3.2.0\",\"homepage\":\"https://github.com/paketo-buildpacks/apache-tomcat\"},{\"id\":\"paketo-buildpacks/gradle\",\"version\":\"3.5.0\",\"homepage\":\"https://github.com/paketo-buildpacks/gradle\"},{\"id\":\"paketo-buildpacks/maven\",\"version\":\"3.2.1\",\"homepage\":\"https://github.com/paketo-buildpacks/maven\"},{\"id\":\"paketo-buildpacks/sbt\",\"version\":\"3.6.0\",\"homepage\":\"https://github.com/paketo-buildpacks/sbt\"},{\"id\":\"paketo-buildpacks/bellsoft-liberica\",\"version\":\"6.2.0\",\"homepage\":\"https://github.com/paketo-buildpacks/bellsoft-liberica\"},{\"id\":\"paketo-buildpacks/google-stackdriver\",\"version\":\"2.16.0\",\"homepage\":\"https://github.com/paketo-buildpacks/google-stackdriver\"},{\"id\":\"paketo-buildpacks/image-labels\",\"version\":\"2.0.7\",\"homepage\":\"https://github.com/paketo-buildpacks/image-labels\"},{\"id\":\"paketo-buildpacks/dist-zip\",\"version\":\"2.2.2\",\"homepage\":\"https://github.com/paketo-buildpacks/dist-zip\"},{\"id\":\"paketo-buildpacks/spring-boot\",\"version\":\"3.5.0\",\"homepage\":\"https://github.com/paketo-buildpacks/spring-boot\"},{\"id\":\"paketo-buildpacks/jmx\",\"version\":\"2.1.4\",\"homepage\":\"https://github.com/paketo-buildpacks/jmx\"},{\"id\":\"paketo-buildpacks/leiningen\",\"version\":\"1.2.1\",\"homepage\":\"https://github.com/paketo-buildpacks/leiningen\"}],\"stack\":{\"runImage\":{\"image\":\"\",\"mirrors\":null}},\"images\":[{\"image\":\"cloudfoundry/run:base-cnb\",\"mirrors\":null}],\"lifecycle\":{\"version\":\"0.7.2\",\"api\":{\"buildpack\":\"0.2\",\"platform\":\"0.3\"}},\"createdBy\":{\"name\":\"Pack CLI\",\"version\":\"v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)\"}}", + "io.buildpacks.buildpack.layers": "{\"org.cloudfoundry.archiveexpanding\":{\"v1.0.102\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:73b1a8ac1f7fca3d545766ce7fd3c56b40a63724ab78e464d71a29da0c6ac31c\"}},\"org.cloudfoundry.azureapplicationinsights\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a0a2f7c467efbb8b1ac222f09013b88b68f3c117ec6b6e9dc95564be50f271ab\"}},\"org.cloudfoundry.buildsystem\":{\"v1.2.15\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:3f50d3a0e1a969a9606b59e5295842d731e425108cb349ce6c69a5b30ea1bab9\"}},\"org.cloudfoundry.debug\":{\"v1.2.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:04559213a01cfac69a8d6a6facb58b8681666525c74f605207c40a61a0f4c9b7\"}},\"org.cloudfoundry.dep\":{\"0.0.101\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:6aae3a2d671d369eec34dc9146ef267d06c87461f271fbfbe9136775ecf5dfb8\"}},\"org.cloudfoundry.distzip\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a0715e661e13d7d3ded5bdc068edd01e5b3aa0e2805152f4c8a1428b4e0673df\"}},\"org.cloudfoundry.dotnet-core\":{\"v0.0.6\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.158\",\"optional\":true},{\"id\":\"org.cloudfoundry.icu\",\"version\":\"0.0.43\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.127\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.118\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.122\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.68\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.115\"}]}],\"layerDiffID\":\"sha256:aa0effdf787ecfe74d60d6771006717fd1a9ce1ce0a8161624baa61b68120357\"}},\"org.cloudfoundry.dotnet-core-aspnet\":{\"0.0.118\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:a06615b5adc1a3afb7abd524e82f6900a28910927fcf0d4e9b85fd1fcbeb53ad\"}},\"org.cloudfoundry.dotnet-core-build\":{\"0.0.68\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:26d6f1e76275d17860005f7ab9b74fdd2283fcf84e0446bd88d49a6b4e9609f9\"}},\"org.cloudfoundry.dotnet-core-conf\":{\"0.0.115\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:55f7c052cf70c8ca01b8e241c0c5c8a9675599d4904c69bfb961a472e246238d\"}},\"org.cloudfoundry.dotnet-core-runtime\":{\"0.0.127\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:d9958b816a9ad179fca8c18d17c07e9814b152d461c685e1443bec6f990ab990\"}},\"org.cloudfoundry.dotnet-core-sdk\":{\"0.0.122\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:52142799a4b687fe6e5cf397c41064499ea6cc554b94904d46c1acade998e11f\"}},\"org.cloudfoundry.go\":{\"v0.0.4\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.89\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.101\"}]}],\"layerDiffID\":\"sha256:352a299d6af4773322ed3643d8f98b01aad6f15d838d1852e52a0a3ca56c6efb\"}},\"org.cloudfoundry.go-compiler\":{\"0.0.105\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:cb21f14e306d94e437c5418d275bcc6efcea6bc9b3d26a400bdf54fa62242c24\"}},\"org.cloudfoundry.go-mod\":{\"0.0.89\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:c9da8171f5ca048109ffba5e940e3a7d2db567eda281f92b0eb483173df06add\"}},\"org.cloudfoundry.googlestackdriver\":{\"v1.1.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:ff29efc56c31eeccc79a33c6e4abd7b1ab3547d95e1cf83974af65a493576c41\"}},\"org.cloudfoundry.icu\":{\"0.0.43\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:48063dcdd043f9c88604d10fe9542569be8f8111d46806c96b08d77763ffa347\"}},\"org.cloudfoundry.jdbc\":{\"v1.1.14\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a9c9bbbd69c212b7ab3c1a7f03011ccc4d99a6fce1bf1c785325c7bcad789e62\"}},\"org.cloudfoundry.jmx\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:da62dec6eb4ed884952a1b867fd89e3bfe3c510e5c849cc0ac7050ff867a2469\"}},\"org.cloudfoundry.jvmapplication\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:c10732392b97c121a78a5f20201c2a5e834a2b8677196cdd49260a489a54fd22\"}},\"org.cloudfoundry.node-engine\":{\"0.0.158\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:70cf83155575fdb607f23ace41e31b1d5cb1c24dbbbf56f71c383b583724d339\"},\"0.0.163\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:11486cb955594f9d43909b60f94209bb6854f502a5a093207b657afbaa38a777\"}},\"org.cloudfoundry.nodejs\":{\"v2.0.8\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.yarn-install\",\"version\":\"0.1.10\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.1.3\"}]}],\"layerDiffID\":\"sha256:76fe727e4aafc7f56f01282296ab736521c38b9d19c1ae5ebb193f9cd55fa109\"}},\"org.cloudfoundry.npm\":{\"0.1.3\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:243bbd007cb0ee99b704bfe0cf62e1301baa4095ab4c39b01293787a0e4234f1\"}},\"org.cloudfoundry.openjdk\":{\"v1.2.14\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:486b2abf434bb90cf04bab74f2f8bd2eb488ff90632b56eac4bddcbbf02e8151\"}},\"org.cloudfoundry.procfile\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:b7b78159dfdaa0dd484c58652e02fa6b755abfd0adb88f106d16178144e46f33\"}},\"org.cloudfoundry.springautoreconfiguration\":{\"v1.1.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:c185540c10fea822c6db1b987fcfe22b55a4662648124b98475db4c9dcddb2ab\"}},\"org.cloudfoundry.springboot\":{\"v1.2.13\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:b87e68574cc7dccbe974fa760702ef650711036bf144fd9da1f3a2d8f6ac335f\"}},\"org.cloudfoundry.tomcat\":{\"v1.3.18\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:467c0082c57b80b48487a9b8429887c0744ddc5b066b3f7678866bde89b78ab2\"}},\"org.cloudfoundry.yarn-install\":{\"0.1.10\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:6aefa0ba7ce01584b4a531b18e36470298cee3b30ecae0e0c64b532a5cebd6e7\"}}}", + "io.buildpacks.buildpack.order": "[{\"group\":[{\"id\":\"org.cloudfoundry.openjdk\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"optional\":true},{\"id\":\"org.cloudfoundry.jvmapplication\"},{\"id\":\"org.cloudfoundry.tomcat\",\"optional\":true},{\"id\":\"org.cloudfoundry.springboot\",\"optional\":true},{\"id\":\"org.cloudfoundry.distzip\",\"optional\":true},{\"id\":\"org.cloudfoundry.procfile\",\"optional\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"optional\":true},{\"id\":\"org.cloudfoundry.debug\",\"optional\":true},{\"id\":\"org.cloudfoundry.googlestackdriver\",\"optional\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"optional\":true},{\"id\":\"org.cloudfoundry.jmx\",\"optional\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"optional\":true}]},{\"group\":[{\"id\":\"org.cloudfoundry.nodejs\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.dotnet-core\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.procfile\"}]}]" + } + }, + "Architecture": "amd64", + "Os": "linux", + "Size": 688884758, + "VirtualSize": 688884758, + "GraphDriver": { + "Data": { + "LowerDir": "/var/lib/docker/overlay2/6a79181b2840da2706624f46ce5abd4448973b4f951925d5a276b273256063b2/diff:/var/lib/docker/overlay2/429419a203100f60ab16ec6c879fce975c8138422b9053f80accd6124c730fc2/diff:/var/lib/docker/overlay2/6e45ed6daf4f4f3b90fd1ec5fa958775000875661d3e8be3f1af218d192b058d/diff:/var/lib/docker/overlay2/22928ad308cdd55b3fe849d92b6e38c6bc303ba7c9beb8c0e79aa958e16b1864/diff:/var/lib/docker/overlay2/2ca9ec213226a1604f57c8e141d6f1168134a5cb2ccd8f91ee9be5a39036e6bf/diff:/var/lib/docker/overlay2/96ae944fe00ec20cf5b4441b112ebcc9395faaf08108c9ee38c62e1da33af1c8/diff:/var/lib/docker/overlay2/13ee52e300e476e27350c9ac6274dedf26af85c3079b42a41f9dfc92eff57a80/diff:/var/lib/docker/overlay2/223edb4cc62a2ba2b8bda866905a55c4798c6c32e31d22d60e6ed4f3169ce85e/diff:/var/lib/docker/overlay2/a41235cd7277299cb74ead47def3771885948719e24075ea3bf37580f3af7ae2/diff:/var/lib/docker/overlay2/ed0438e8e2c27b9d62ad21a0761237c350a2ffc9e52f47c019e4f627091c832e/diff:/var/lib/docker/overlay2/0c27c8229b31eafc57ab739b44962dcc07b72f3d8950888873ecb3cfd385032f/diff:/var/lib/docker/overlay2/0957cbcca052cd58bcf9a3d945b0e6876b0df79c1c534da1872c3415a019427d/diff:/var/lib/docker/overlay2/b621414d53d71349c07df8ed45e3e04b2e97bfbaf4bf0d86463f46e0f810eeb4/diff:/var/lib/docker/overlay2/ad521bc47f0bb44262358cf47c3d81a544d098494cf24a5b510620d34eb9c353/diff:/var/lib/docker/overlay2/081501d5bfbd927e69c10eb320513c7c0d5f00bea8cf9e55faa90579fd33adf4/diff:/var/lib/docker/overlay2/fb1ba66bee5568f5700c72865d020d4171a62bfdd099c3cc05b9a253d36a35a4/diff:/var/lib/docker/overlay2/06bcc6b3adeca727d554f1a745ee33242dfe1b3c6392023ac947666057303288/diff:/var/lib/docker/overlay2/1c5397d63d893202dffde29013ee826fb695bda26c718ee03ddde376be4da0a3/diff:/var/lib/docker/overlay2/76075fb7fd3c6b3fb116fb3b464e220918e56d94461c61af9a1aff288ebdba60/diff:/var/lib/docker/overlay2/43d1026bb7b618393912ecc9ddf57b604336184d5f8dc70bcf6332b5f08a3e8d/diff:/var/lib/docker/overlay2/ee27d1fba3deaca0556f7bab171cb3368f169011dd132cf335b5308728f6db8f/diff:/var/lib/docker/overlay2/464d3ec8d86ff31dcb5063ea25521368ea8e9c7964f65e15ff5e0e1ecdbe991e/diff:/var/lib/docker/overlay2/a4a80c33c8b78f68bdc9dbd5903cc2ba1d48e78b9a97d43acb018823ece8e6cb/diff:/var/lib/docker/overlay2/6494f2f1693cff8b16d51fa95620eb0bb691a76fb39b5175d953649577791297/diff:/var/lib/docker/overlay2/9d49e146f82eb5fc4fd81613538e9c5f5f95091fbbc8c49729c6c9140ae356de/diff:/var/lib/docker/overlay2/2934818c52bcd017abe000e71342d67fbc9ccb7dbc165ce05e3250e2110229a5/diff:/var/lib/docker/overlay2/651ca06b2bf75e2122855264287fc937f30d2b49229d628909895be7128b4eb6/diff:/var/lib/docker/overlay2/c93bab59be44fa1b66689dc059d26742d00d2e787d06c3236e1f116199c9807e/diff:/var/lib/docker/overlay2/d0a8e2a0c7e0df172f7a8ebe75e2dce371bb6cc65531b06799bc677c5b5e3627/diff:/var/lib/docker/overlay2/7d14bac240e0d7936351e3fac80b7fbe2a209f4de8992091c4f75e41f9627852/diff:/var/lib/docker/overlay2/d6b192ea137a4ae95e309d263ee8c890e35da02aacd9bdcf5adbd4c28a0c0a3f/diff:/var/lib/docker/overlay2/335bfb632ab7723e25fb5dc7b67389e6ec38178ef10bfbf83337501403e61574/diff:/var/lib/docker/overlay2/0293c7e3472da58f51cbdf15fb293ff71e32c1f80f83f00fb09f8941deef5e43/diff:/var/lib/docker/overlay2/55faa8b47bcb0dd29c3836580f451a0461dd499065af9c830beff6e8329ab484/diff:/var/lib/docker/overlay2/afcb6e109c1ba7d71b8a8b7e573d4ce04f22da3fe0ee523359db5cfb95e65bb6/diff:/var/lib/docker/overlay2/b42eefd9bf6629ae9d16e7aba6ba3939d37816aba7a0999f6d639012a3119be1/diff:/var/lib/docker/overlay2/a9832c8f81ee889a622ce4d95d9f4bab2f91d30e18f69bfd7cfc385c781068d4/diff:/var/lib/docker/overlay2/224041c135f13881a98b9e833584bedab81d5650061457f522a1ebd1daa2c77a/diff:/var/lib/docker/overlay2/73dfd4e2075fccb239b3d5e9b33b32b8e410bdc3cd5a620b41346f44cc5c51f7/diff:/var/lib/docker/overlay2/b3924ed7c91730f6714d33c455db888604b59ab093033b3f59ac16ecdd777987/diff:/var/lib/docker/overlay2/e36a32cd0ab20b216a8db1a8a166b17464399e4d587d22504088a7a6ef0a68a4/diff:/var/lib/docker/overlay2/3334e94fe191333b65f571912c0fcfbbf31aeb090a2fb9b4cfdbc32a37c0fe5f/diff", + "MergedDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/merged", + "UpperDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/diff", + "WorkDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/work" + }, + "Name": "overlay2" + }, + "RootFS": { + "Type": "layers", + "Layers": [ + "sha256:c8be1b8f4d60d99c281fc2db75e0f56df42a83ad2f0b091621ce19357e19d853", + "sha256:977183d4e9995d9cd5ffdfc0f29e911ec9de777bcb0f507895daa1068477f76f", + "sha256:6597da2e2e52f4d438ad49a14ca79324f130a9ea08745505aa174a8db51cb79d", + "sha256:16542a8fc3be1bfaff6ed1daa7922e7c3b47b6c3a8d98b7fca58b9517bb99b75", + "sha256:2df36adfe1af661aebb75a0db796b074bb8f861fbc8f98f6f642570692b3b133", + "sha256:f499c7d34e01d860492ef1cc34b7d7e1319b3c3c81ee7d23258b21605b5902ca", + "sha256:c4bf1d4e5d4adb566b173a0769d247f67c5dd8ff90dfdcebd8c7060f1c06caa9", + "sha256:15259abd479904cbe0d8d421e5b05b2e5745e2bf82e62cdd7fb6d3eafbe4168a", + "sha256:6aa3691a73805f608e5fce69fb6bc89aec8362f58a6b4be2682515e9cfa3cc1a", + "sha256:2d6ad1b66f5660dd860c1fe2d90d26398fcfab4dc1c87c3d5e7c0fc24f8d6fb2", + "sha256:ff29efc56c31eeccc79a33c6e4abd7b1ab3547d95e1cf83974af65a493576c41", + "sha256:b87e68574cc7dccbe974fa760702ef650711036bf144fd9da1f3a2d8f6ac335f", + "sha256:04559213a01cfac69a8d6a6facb58b8681666525c74f605207c40a61a0f4c9b7", + "sha256:467c0082c57b80b48487a9b8429887c0744ddc5b066b3f7678866bde89b78ab2", + "sha256:352a299d6af4773322ed3643d8f98b01aad6f15d838d1852e52a0a3ca56c6efb", + "sha256:486b2abf434bb90cf04bab74f2f8bd2eb488ff90632b56eac4bddcbbf02e8151", + "sha256:3f50d3a0e1a969a9606b59e5295842d731e425108cb349ce6c69a5b30ea1bab9", + "sha256:c10732392b97c121a78a5f20201c2a5e834a2b8677196cdd49260a489a54fd22", + "sha256:c185540c10fea822c6db1b987fcfe22b55a4662648124b98475db4c9dcddb2ab", + "sha256:73b1a8ac1f7fca3d545766ce7fd3c56b40a63724ab78e464d71a29da0c6ac31c", + "sha256:da62dec6eb4ed884952a1b867fd89e3bfe3c510e5c849cc0ac7050ff867a2469", + "sha256:76fe727e4aafc7f56f01282296ab736521c38b9d19c1ae5ebb193f9cd55fa109", + "sha256:a9c9bbbd69c212b7ab3c1a7f03011ccc4d99a6fce1bf1c785325c7bcad789e62", + "sha256:b7b78159dfdaa0dd484c58652e02fa6b755abfd0adb88f106d16178144e46f33", + "sha256:aa0effdf787ecfe74d60d6771006717fd1a9ce1ce0a8161624baa61b68120357", + "sha256:a0a2f7c467efbb8b1ac222f09013b88b68f3c117ec6b6e9dc95564be50f271ab", + "sha256:a0715e661e13d7d3ded5bdc068edd01e5b3aa0e2805152f4c8a1428b4e0673df", + "sha256:6aae3a2d671d369eec34dc9146ef267d06c87461f271fbfbe9136775ecf5dfb8", + "sha256:cb21f14e306d94e437c5418d275bcc6efcea6bc9b3d26a400bdf54fa62242c24", + "sha256:c9da8171f5ca048109ffba5e940e3a7d2db567eda281f92b0eb483173df06add", + "sha256:11486cb955594f9d43909b60f94209bb6854f502a5a093207b657afbaa38a777", + "sha256:243bbd007cb0ee99b704bfe0cf62e1301baa4095ab4c39b01293787a0e4234f1", + "sha256:6aefa0ba7ce01584b4a531b18e36470298cee3b30ecae0e0c64b532a5cebd6e7", + "sha256:a06615b5adc1a3afb7abd524e82f6900a28910927fcf0d4e9b85fd1fcbeb53ad", + "sha256:26d6f1e76275d17860005f7ab9b74fdd2283fcf84e0446bd88d49a6b4e9609f9", + "sha256:55f7c052cf70c8ca01b8e241c0c5c8a9675599d4904c69bfb961a472e246238d", + "sha256:d9958b816a9ad179fca8c18d17c07e9814b152d461c685e1443bec6f990ab990", + "sha256:52142799a4b687fe6e5cf397c41064499ea6cc554b94904d46c1acade998e11f", + "sha256:48063dcdd043f9c88604d10fe9542569be8f8111d46806c96b08d77763ffa347", + "sha256:70cf83155575fdb607f23ace41e31b1d5cb1c24dbbbf56f71c383b583724d339", + "sha256:6cf0f8f815d5371cf5c04e7ebf76c62467948d693b8343184d1446036980d261", + "sha256:7cbffcbb09fc5e9d00372e80990016609c09cc3113429ddc951c4a19b1a5ec72", + "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" + ] + }, + "Metadata": { + "LastTagTime": "0001-01-01T00:00:00Z" + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-cache-bind-mounts.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-cache-bind-mounts.json new file mode 100644 index 000000000000..2656dde2f0cd --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-cache-bind-mounts.json @@ -0,0 +1,31 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/analyzer", + "-daemon", + "-launch-cache", + "/launch-cache", + "-layers", + "/layers", + "-run-image", + "docker.io/cloudfoundry/run:latest", + "docker.io/library/my-application:latest" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/var/run/docker.sock:/var/run/docker.sock", + "/tmp/launch-cache:/launch-cache", + "/tmp/work-layers:/layers" + ], + "SecurityOpt" : [ + "label=disable" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-cache-volumes.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-cache-volumes.json new file mode 100644 index 000000000000..285d666b0d2a --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-cache-volumes.json @@ -0,0 +1,31 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/analyzer", + "-daemon", + "-launch-cache", + "/launch-cache", + "-layers", + "/layers", + "-run-image", + "docker.io/cloudfoundry/run:latest", + "docker.io/library/my-application:latest" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/var/run/docker.sock:/var/run/docker.sock", + "launch-volume:/launch-cache", + "work-volume-layers:/layers" + ], + "SecurityOpt" : [ + "label=disable" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-inherit-local.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-inherit-local.json new file mode 100644 index 000000000000..915034d958b2 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-inherit-local.json @@ -0,0 +1,31 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/analyzer", + "-daemon", + "-launch-cache", + "/launch-cache", + "-layers", + "/layers", + "-run-image", + "docker.io/cloudfoundry/run:latest", + "docker.io/library/my-application:latest" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/var/alt.sock:/var/run/docker.sock", + "pack-cache-b35197ac41ea.launch:/launch-cache", + "pack-layers-aaaaaaaaaa:/layers" + ], + "SecurityOpt" : [ + "label=disable" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-inherit-remote.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-inherit-remote.json new file mode 100644 index 000000000000..a2fffb5f6bb6 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-inherit-remote.json @@ -0,0 +1,31 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/analyzer", + "-daemon", + "-launch-cache", + "/launch-cache", + "-layers", + "/layers", + "-run-image", + "docker.io/cloudfoundry/run:latest", + "docker.io/library/my-application:latest" + ], + "Env": [ + "DOCKER_HOST=tcp://192.168.1.2:2376", + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "pack-cache-b35197ac41ea.launch:/launch-cache", + "pack-layers-aaaaaaaaaa:/layers" + ], + "SecurityOpt" : [ + "label=disable" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-security-opts.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-security-opts.json new file mode 100644 index 000000000000..96049f5c6fd4 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-security-opts.json @@ -0,0 +1,32 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/analyzer", + "-daemon", + "-launch-cache", + "/launch-cache", + "-layers", + "/layers", + "-run-image", + "docker.io/cloudfoundry/run:latest", + "docker.io/library/my-application:latest" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/var/run/docker.sock:/var/run/docker.sock", + "pack-cache-b35197ac41ea.launch:/launch-cache", + "pack-layers-aaaaaaaaaa:/layers" + ], + "SecurityOpt" : [ + "label=user:USER", + "label=role:ROLE" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer.json new file mode 100644 index 000000000000..bb678a0f9b31 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer.json @@ -0,0 +1,31 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/analyzer", + "-daemon", + "-launch-cache", + "/launch-cache", + "-layers", + "/layers", + "-run-image", + "docker.io/cloudfoundry/run:latest", + "docker.io/library/my-application:latest" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/var/run/docker.sock:/var/run/docker.sock", + "pack-cache-b35197ac41ea.launch:/launch-cache", + "pack-layers-aaaaaaaaaa:/layers" + ], + "SecurityOpt" : [ + "label=disable" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-builder-app-dir.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-builder-app-dir.json new file mode 100644 index 000000000000..f3554898cb5e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-builder-app-dir.json @@ -0,0 +1,24 @@ +{ + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/builder", + "-app", + "/application", + "-layers", + "/layers", + "-platform", + "/platform" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "pack-app-aaaaaaaaaa:/application", + "pack-layers-aaaaaaaaaa:/layers" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-builder-cache-bind-mounts.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-builder-cache-bind-mounts.json new file mode 100644 index 000000000000..2cd60a23bdd1 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-builder-cache-bind-mounts.json @@ -0,0 +1,24 @@ +{ + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/builder", + "-app", + "/workspace", + "-layers", + "/layers", + "-platform", + "/platform" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/tmp/work-app:/workspace", + "/tmp/work-layers:/layers" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-builder-cache-volumes.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-builder-cache-volumes.json new file mode 100644 index 000000000000..82870ca9de05 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-builder-cache-volumes.json @@ -0,0 +1,24 @@ +{ + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/builder", + "-app", + "/workspace", + "-layers", + "/layers", + "-platform", + "/platform" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "work-volume-app:/workspace", + "work-volume-layers:/layers" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-builder.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-builder.json new file mode 100644 index 000000000000..98fd56c21674 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-builder.json @@ -0,0 +1,24 @@ +{ + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/builder", + "-app", + "/workspace", + "-layers", + "/layers", + "-platform", + "/platform" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "pack-app-aaaaaaaaaa:/workspace", + "pack-layers-aaaaaaaaaa:/layers" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-app-dir.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-app-dir.json index 6acd7a12ea51..8daba810213e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-app-dir.json +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-app-dir.json @@ -26,11 +26,11 @@ }, "HostConfig": { "Binds": [ - "/var/run/docker.sock:/var/run/docker.sock", - "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/application", + "pack-layers-aaaaaaaaaa:/layers", "pack-cache-b35197ac41ea.build:/cache", - "pack-cache-b35197ac41ea.launch:/launch-cache" + "pack-cache-b35197ac41ea.launch:/launch-cache", + "/var/run/docker.sock:/var/run/docker.sock" ], "SecurityOpt" : [ "label=disable" diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-bindings.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-bindings.json index 85ac90ce4d8d..c5fa49874804 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-bindings.json +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-bindings.json @@ -26,11 +26,11 @@ }, "HostConfig": { "Binds": [ - "/var/run/docker.sock:/var/run/docker.sock", - "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace", + "pack-layers-aaaaaaaaaa:/layers", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache", + "/var/run/docker.sock:/var/run/docker.sock", "/host/src/path:/container/dest/path:ro", "volume-name:/container/volume/path:rw" ], diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-cache-bind-mounts.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-cache-bind-mounts.json new file mode 100644 index 000000000000..7c7c285d58d5 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-cache-bind-mounts.json @@ -0,0 +1,39 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/creator", + "-app", + "/workspace", + "-platform", + "/platform", + "-run-image", + "docker.io/cloudfoundry/run:latest", + "-layers", + "/layers", + "-cache-dir", + "/cache", + "-launch-cache", + "/launch-cache", + "-daemon", + "docker.io/library/my-application:latest" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/tmp/work-app:/workspace", + "/tmp/work-layers:/layers", + "/tmp/build-cache:/cache", + "/tmp/launch-cache:/launch-cache", + "/var/run/docker.sock:/var/run/docker.sock" + ], + "SecurityOpt" : [ + "label=disable" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-cache-volumes.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-cache-volumes.json index 7bd3d9a24ca0..4cd1fe314f9e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-cache-volumes.json +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-cache-volumes.json @@ -26,11 +26,11 @@ }, "HostConfig": { "Binds": [ - "/var/run/docker.sock:/var/run/docker.sock", - "pack-layers-aaaaaaaaaa:/layers", - "pack-app-aaaaaaaaaa:/workspace", + "work-volume-app:/workspace", + "work-volume-layers:/layers", "build-volume:/cache", - "launch-volume:/launch-cache" + "launch-volume:/launch-cache", + "/var/run/docker.sock:/var/run/docker.sock" ], "SecurityOpt" : [ "label=disable" diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-clean-cache.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-clean-cache.json index 1239aaa2f25c..0b2472c5ad02 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-clean-cache.json +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-clean-cache.json @@ -27,11 +27,11 @@ }, "HostConfig": { "Binds": [ - "/var/run/docker.sock:/var/run/docker.sock", - "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace", + "pack-layers-aaaaaaaaaa:/layers", "pack-cache-b35197ac41ea.build:/cache", - "pack-cache-b35197ac41ea.launch:/launch-cache" + "pack-cache-b35197ac41ea.launch:/launch-cache", + "/var/run/docker.sock:/var/run/docker.sock" ], "SecurityOpt" : [ "label=disable" diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-created-date.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-created-date.json index a316d9633a44..1b2907a93a5f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-created-date.json +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-created-date.json @@ -27,11 +27,11 @@ }, "HostConfig": { "Binds": [ - "/var/run/docker.sock:/var/run/docker.sock", - "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace", + "pack-layers-aaaaaaaaaa:/layers", "pack-cache-b35197ac41ea.build:/cache", - "pack-cache-b35197ac41ea.launch:/launch-cache" + "pack-cache-b35197ac41ea.launch:/launch-cache", + "/var/run/docker.sock:/var/run/docker.sock" ], "SecurityOpt" : [ "label=disable" diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-inherit-local.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-inherit-local.json index 15ea893ce996..e0f7fa8cb9bd 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-inherit-local.json +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-inherit-local.json @@ -26,11 +26,11 @@ }, "HostConfig": { "Binds": [ - "/var/alt.sock:/var/run/docker.sock", - "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace", + "pack-layers-aaaaaaaaaa:/layers", "pack-cache-b35197ac41ea.build:/cache", - "pack-cache-b35197ac41ea.launch:/launch-cache" + "pack-cache-b35197ac41ea.launch:/launch-cache", + "/var/alt.sock:/var/run/docker.sock" ], "SecurityOpt" : [ "label=disable" diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-inherit-remote.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-inherit-remote.json index 55a7958b8b10..af703b95a20c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-inherit-remote.json +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-inherit-remote.json @@ -27,8 +27,8 @@ }, "HostConfig": { "Binds": [ - "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace", + "pack-layers-aaaaaaaaaa:/layers", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache" ], diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-network.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-network.json index 32169341c2a8..7eef5bf79538 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-network.json +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-network.json @@ -27,11 +27,11 @@ "HostConfig": { "NetworkMode": "test", "Binds": [ - "/var/run/docker.sock:/var/run/docker.sock", - "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace", + "pack-layers-aaaaaaaaaa:/layers", "pack-cache-b35197ac41ea.build:/cache", - "pack-cache-b35197ac41ea.launch:/launch-cache" + "pack-cache-b35197ac41ea.launch:/launch-cache", + "/var/run/docker.sock:/var/run/docker.sock" ], "SecurityOpt" : [ "label=disable" diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-platform-api-0.3.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-platform-api-0.3.json index 38e6df207541..96cd67316c88 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-platform-api-0.3.json +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-platform-api-0.3.json @@ -7,7 +7,13 @@ "author" : "spring-boot" }, "HostConfig" : { - "Binds" : [ "/var/run/docker.sock:/var/run/docker.sock", "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache" ], + "Binds" : [ + "pack-app-aaaaaaaaaa:/workspace", + "pack-layers-aaaaaaaaaa:/layers", + "pack-cache-b35197ac41ea.build:/cache", + "pack-cache-b35197ac41ea.launch:/launch-cache", + "/var/run/docker.sock:/var/run/docker.sock" + ], "SecurityOpt" : [ "label=disable" ] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-security-opts.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-security-opts.json new file mode 100644 index 000000000000..4f1a1e75fb2b --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-security-opts.json @@ -0,0 +1,40 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/creator", + "-app", + "/workspace", + "-platform", + "/platform", + "-run-image", + "docker.io/cloudfoundry/run:latest", + "-layers", + "/layers", + "-cache-dir", + "/cache", + "-launch-cache", + "/launch-cache", + "-daemon", + "docker.io/library/my-application:latest" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "pack-app-aaaaaaaaaa:/workspace", + "pack-layers-aaaaaaaaaa:/layers", + "pack-cache-b35197ac41ea.build:/cache", + "pack-cache-b35197ac41ea.launch:/launch-cache", + "/var/run/docker.sock:/var/run/docker.sock" + ], + "SecurityOpt" : [ + "label=user:USER", + "label=role:ROLE" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator.json index 5c85d04b8f0d..7cda92d89960 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator.json +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator.json @@ -26,11 +26,11 @@ }, "HostConfig": { "Binds": [ - "/var/run/docker.sock:/var/run/docker.sock", - "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace", + "pack-layers-aaaaaaaaaa:/layers", "pack-cache-b35197ac41ea.build:/cache", - "pack-cache-b35197ac41ea.launch:/launch-cache" + "pack-cache-b35197ac41ea.launch:/launch-cache", + "/var/run/docker.sock:/var/run/docker.sock" ], "SecurityOpt" : [ "label=disable" diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-detector-app-dir.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-detector-app-dir.json new file mode 100644 index 000000000000..7eb3173afb6c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-detector-app-dir.json @@ -0,0 +1,24 @@ +{ + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/detector", + "-app", + "/application", + "-layers", + "/layers", + "-platform", + "/platform" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "pack-app-aaaaaaaaaa:/application", + "pack-layers-aaaaaaaaaa:/layers" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-detector-cache-bind-mounts.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-detector-cache-bind-mounts.json new file mode 100644 index 000000000000..706239cb5d74 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-detector-cache-bind-mounts.json @@ -0,0 +1,24 @@ +{ + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/detector", + "-app", + "/workspace", + "-layers", + "/layers", + "-platform", + "/platform" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/tmp/work-app:/workspace", + "/tmp/work-layers:/layers" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-detector-cache-volumes.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-detector-cache-volumes.json new file mode 100644 index 000000000000..729600142f97 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-detector-cache-volumes.json @@ -0,0 +1,24 @@ +{ + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/detector", + "-app", + "/workspace", + "-layers", + "/layers", + "-platform", + "/platform" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "work-volume-app:/workspace", + "work-volume-layers:/layers" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-detector.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-detector.json new file mode 100644 index 000000000000..d5a9eb922e67 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-detector.json @@ -0,0 +1,24 @@ +{ + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/detector", + "-app", + "/workspace", + "-layers", + "/layers", + "-platform", + "/platform" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "pack-app-aaaaaaaaaa:/workspace", + "pack-layers-aaaaaaaaaa:/layers" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-app-dir.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-app-dir.json new file mode 100644 index 000000000000..91b436b568ca --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-app-dir.json @@ -0,0 +1,35 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/exporter", + "-daemon", + "-app", + "/application", + "-cache-dir", + "/cache", + "-launch-cache", + "/launch-cache", + "-layers", + "/layers", + "docker.io/library/my-application:latest" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/var/run/docker.sock:/var/run/docker.sock", + "pack-app-aaaaaaaaaa:/application", + "pack-cache-b35197ac41ea.build:/cache", + "pack-cache-b35197ac41ea.launch:/launch-cache", + "pack-layers-aaaaaaaaaa:/layers" + ], + "SecurityOpt" : [ + "label=disable" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-cache-bind-mounts.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-cache-bind-mounts.json new file mode 100644 index 000000000000..c27c53ebd976 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-cache-bind-mounts.json @@ -0,0 +1,35 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/exporter", + "-daemon", + "-app", + "/workspace", + "-cache-dir", + "/cache", + "-launch-cache", + "/launch-cache", + "-layers", + "/layers", + "docker.io/library/my-application:latest" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/var/run/docker.sock:/var/run/docker.sock", + "/tmp/work-app:/workspace", + "/tmp/build-cache:/cache", + "/tmp/launch-cache:/launch-cache", + "/tmp/work-layers:/layers" + ], + "SecurityOpt" : [ + "label=disable" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-cache-volumes.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-cache-volumes.json new file mode 100644 index 000000000000..413a9889237f --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-cache-volumes.json @@ -0,0 +1,35 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/exporter", + "-daemon", + "-app", + "/workspace", + "-cache-dir", + "/cache", + "-launch-cache", + "/launch-cache", + "-layers", + "/layers", + "docker.io/library/my-application:latest" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/var/run/docker.sock:/var/run/docker.sock", + "work-volume-app:/workspace", + "build-volume:/cache", + "launch-volume:/launch-cache", + "work-volume-layers:/layers" + ], + "SecurityOpt" : [ + "label=disable" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-created-date.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-created-date.json new file mode 100644 index 000000000000..1de479740581 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-created-date.json @@ -0,0 +1,36 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/exporter", + "-daemon", + "-app", + "/workspace", + "-cache-dir", + "/cache", + "-launch-cache", + "/launch-cache", + "-layers", + "/layers", + "docker.io/library/my-application:latest" + ], + "Env": [ + "CNB_PLATFORM_API=0.8", + "SOURCE_DATE_EPOCH=1593606896" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/var/run/docker.sock:/var/run/docker.sock", + "pack-app-aaaaaaaaaa:/workspace", + "pack-cache-b35197ac41ea.build:/cache", + "pack-cache-b35197ac41ea.launch:/launch-cache", + "pack-layers-aaaaaaaaaa:/layers" + ], + "SecurityOpt" : [ + "label=disable" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-inherit-local.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-inherit-local.json new file mode 100644 index 000000000000..b70d66133d53 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-inherit-local.json @@ -0,0 +1,35 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/exporter", + "-daemon", + "-app", + "/workspace", + "-cache-dir", + "/cache", + "-launch-cache", + "/launch-cache", + "-layers", + "/layers", + "docker.io/library/my-application:latest" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/var/alt.sock:/var/run/docker.sock", + "pack-app-aaaaaaaaaa:/workspace", + "pack-cache-b35197ac41ea.build:/cache", + "pack-cache-b35197ac41ea.launch:/launch-cache", + "pack-layers-aaaaaaaaaa:/layers" + ], + "SecurityOpt" : [ + "label=disable" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-inherit-remote.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-inherit-remote.json new file mode 100644 index 000000000000..28f3083b171f --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-inherit-remote.json @@ -0,0 +1,35 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/exporter", + "-daemon", + "-app", + "/workspace", + "-cache-dir", + "/cache", + "-launch-cache", + "/launch-cache", + "-layers", + "/layers", + "docker.io/library/my-application:latest" + ], + "Env": [ + "DOCKER_HOST=tcp://192.168.1.2:2376", + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "pack-app-aaaaaaaaaa:/workspace", + "pack-cache-b35197ac41ea.build:/cache", + "pack-cache-b35197ac41ea.launch:/launch-cache", + "pack-layers-aaaaaaaaaa:/layers" + ], + "SecurityOpt" : [ + "label=disable" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-security-opts.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-security-opts.json new file mode 100644 index 000000000000..ee7f41d87e3a --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-security-opts.json @@ -0,0 +1,36 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/exporter", + "-daemon", + "-app", + "/workspace", + "-cache-dir", + "/cache", + "-launch-cache", + "/launch-cache", + "-layers", + "/layers", + "docker.io/library/my-application:latest" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/var/run/docker.sock:/var/run/docker.sock", + "pack-app-aaaaaaaaaa:/workspace", + "pack-cache-b35197ac41ea.build:/cache", + "pack-cache-b35197ac41ea.launch:/launch-cache", + "pack-layers-aaaaaaaaaa:/layers" + ], + "SecurityOpt" : [ + "label=user:USER", + "label=role:ROLE" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter.json new file mode 100644 index 000000000000..56893e385e58 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter.json @@ -0,0 +1,35 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/exporter", + "-daemon", + "-app", + "/workspace", + "-cache-dir", + "/cache", + "-launch-cache", + "/launch-cache", + "-layers", + "/layers", + "docker.io/library/my-application:latest" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/var/run/docker.sock:/var/run/docker.sock", + "pack-app-aaaaaaaaaa:/workspace", + "pack-cache-b35197ac41ea.build:/cache", + "pack-cache-b35197ac41ea.launch:/launch-cache", + "pack-layers-aaaaaaaaaa:/layers" + ], + "SecurityOpt" : [ + "label=disable" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-cache-bind-mounts.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-cache-bind-mounts.json new file mode 100644 index 000000000000..78f51a68aa3d --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-cache-bind-mounts.json @@ -0,0 +1,28 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/restorer", + "-daemon", + "-cache-dir", + "/cache", + "-layers", + "/layers" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/var/run/docker.sock:/var/run/docker.sock", + "/tmp/build-cache:/cache", + "/tmp/work-layers:/layers" + ], + "SecurityOpt" : [ + "label=disable" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-cache-volumes.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-cache-volumes.json new file mode 100644 index 000000000000..9408724c8f0c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-cache-volumes.json @@ -0,0 +1,28 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/restorer", + "-daemon", + "-cache-dir", + "/cache", + "-layers", + "/layers" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/var/run/docker.sock:/var/run/docker.sock", + "build-volume:/cache", + "work-volume-layers:/layers" + ], + "SecurityOpt" : [ + "label=disable" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-inherit-local.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-inherit-local.json new file mode 100644 index 000000000000..a5a54b5a4d27 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-inherit-local.json @@ -0,0 +1,28 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/restorer", + "-daemon", + "-cache-dir", + "/cache", + "-layers", + "/layers" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/var/alt.sock:/var/run/docker.sock", + "pack-cache-b35197ac41ea.build:/cache", + "pack-layers-aaaaaaaaaa:/layers" + ], + "SecurityOpt" : [ + "label=disable" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-inherit-remote.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-inherit-remote.json new file mode 100644 index 000000000000..b8af6eea0995 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-inherit-remote.json @@ -0,0 +1,28 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/restorer", + "-daemon", + "-cache-dir", + "/cache", + "-layers", + "/layers" + ], + "Env": [ + "DOCKER_HOST=tcp://192.168.1.2:2376", + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "pack-cache-b35197ac41ea.build:/cache", + "pack-layers-aaaaaaaaaa:/layers" + ], + "SecurityOpt" : [ + "label=disable" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-security-opts.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-security-opts.json new file mode 100644 index 000000000000..b43f8428b085 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-security-opts.json @@ -0,0 +1,29 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/restorer", + "-daemon", + "-cache-dir", + "/cache", + "-layers", + "/layers" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/var/run/docker.sock:/var/run/docker.sock", + "pack-cache-b35197ac41ea.build:/cache", + "pack-layers-aaaaaaaaaa:/layers" + ], + "SecurityOpt" : [ + "label=user:USER", + "label=role:ROLE" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer.json new file mode 100644 index 000000000000..ccbc3144638e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer.json @@ -0,0 +1,28 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/restorer", + "-daemon", + "-cache-dir", + "/cache", + "-layers", + "/layers" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/var/run/docker.sock:/var/run/docker.sock", + "pack-cache-b35197ac41ea.build:/cache", + "pack-layers-aaaaaaaaaa:/layers" + ], + "SecurityOpt" : [ + "label=disable" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-context/config.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-context/config.json new file mode 100644 index 000000000000..7e3fa77f5bfe --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-context/config.json @@ -0,0 +1,3 @@ +{ + "currentContext": "test-context" +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-context/contexts/meta/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/meta.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-context/contexts/meta/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/meta.json new file mode 100644 index 000000000000..fa4655b1a026 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-context/contexts/meta/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/meta.json @@ -0,0 +1,12 @@ +{ + "Name": "test-context", + "Metadata": { + "Description": "A context for testing" + }, + "Endpoints": { + "docker": { + "Host": "unix:///home/user/.docker/docker.sock", + "SkipTLSVerify": true + } + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/config.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/config.json new file mode 100644 index 000000000000..6eaf50253da3 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/config.json @@ -0,0 +1,3 @@ +{ + "currentContext": "default" +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/meta/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/meta.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/meta/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/meta.json new file mode 100644 index 000000000000..f072aa2647e2 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/meta/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/meta.json @@ -0,0 +1,12 @@ +{ + "Name": "test-context", + "Metadata": { + "Description": "A context for testing" + }, + "Endpoints": { + "docker": { + "Host": "unix:///home/user/.docker/docker.sock", + "SkipTLSVerify": false + } + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/tls/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/docker/cert.pem b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/tls/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/docker/cert.pem new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/tls/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/docker/key.pem b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/tls/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/docker/key.pem new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/without-context/config.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/without-context/config.json new file mode 100644 index 000000000000..2c63c0851048 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/without-context/config.json @@ -0,0 +1,2 @@ +{ +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/export-docker-desktop-containerd-manifest-list.tar b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/export-docker-desktop-containerd-manifest-list.tar new file mode 100644 index 000000000000..09b9e04564c2 Binary files /dev/null and b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/export-docker-desktop-containerd-manifest-list.tar differ diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/export-docker-desktop-containerd.tar b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/export-docker-desktop-containerd.tar new file mode 100644 index 000000000000..473e17c07717 Binary files /dev/null and b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/export-docker-desktop-containerd.tar differ diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/export-docker-desktop.tar b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/export-docker-desktop.tar new file mode 100644 index 000000000000..61ffcd40b334 Binary files /dev/null and b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/export-docker-desktop.tar differ diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/export-docker-engine.tar b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/export-docker-engine.tar new file mode 100644 index 000000000000..2ae031ee47d9 Binary files /dev/null and b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/export-docker-engine.tar differ diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/export-podman.tar b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/export-podman.tar new file mode 100644 index 000000000000..d6f6b0813432 Binary files /dev/null and b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/export-podman.tar differ diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/distribution-manifest-list.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/distribution-manifest-list.json new file mode 100644 index 000000000000..d1e4f676433e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/distribution-manifest-list.json @@ -0,0 +1,24 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", + "manifests": [ + { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 428, + "digest": "sha256:6dba064234a3aa60f7da2e0f1f8b86dccb7df2841136f577b08bd6a89004cb23", + "platform": { + "architecture": "amd64", + "os": "linux" + } + }, + { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 428, + "digest": "sha256:c036aba2c51a86a7a338f60af4730df725c2abff1b8b565d753896fd9533dfad", + "platform": { + "architecture": "arm64", + "os": "linux" + } + } + ] +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/distribution-manifest.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/distribution-manifest.json new file mode 100644 index 000000000000..0d41d2593f6e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/distribution-manifest.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": 1175, + "digest": "sha256:b2160a0f9037918d3ca2270fb90f656f425760b337a5ed3813c3a48c09825065" + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 4872935, + "digest": "sha256:13ac7da0441b95b1960de1b87ed2c1ef129026cc69b926ffbe734a7dcc4fa40c" + } + ] +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-archive-index.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-archive-index.json new file mode 100644 index 000000000000..4369b7ce0525 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-archive-index.json @@ -0,0 +1,15 @@ +{ + "schemaVersion": 2, + "manifests": [ + { + "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", + "digest": "sha256:3bbe02431d8e5124ffe816ec27bf6508b50edd1d10218be1a03e799a186b9004", + "size": 529, + "annotations": { + "containerd.io/distribution.source.gcr.io": "paketo-buildpacks/adoptium", + "io.containerd.image.name": "gcr.io/paketo-buildpacks/adoptium:latest", + "org.opencontainers.image.ref.name": "latest" + } + } + ] +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-manifest.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-manifest.json new file mode 100644 index 000000000000..5a91f5d567a7 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-manifest.json @@ -0,0 +1,20 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.oci.image.config.v1+json", + "digest": "sha256:ee382dc5c080aa6af5ea716041eaa4442c9d461520388627dfe51709c679043e", + "size": 849, + "platform": { + "architecture": "amd64", + "os": "linux" + } + }, + "layers": [ + { + "mediaType": "application/vnd.oci.image.layer.v1.tar", + "digest": "sha256:5caae51697b248b905dca1a4160864b0e1a15c300981736555cdce6567e8d477", + "size": 6656 + } + ] +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-cli/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-cli/build.gradle index 35714e676f53..bf42c6816213 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-cli/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-cli/build.gradle @@ -35,7 +35,7 @@ dependencies { intTestImplementation("org.junit.jupiter:junit-jupiter") intTestImplementation("org.springframework:spring-core") - loader(project(":spring-boot-project:spring-boot-tools:spring-boot-loader")) + loader(project(":spring-boot-project:spring-boot-tools:spring-boot-loader-classic")) testImplementation(project(":spring-boot-project:spring-boot")) testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) @@ -66,7 +66,7 @@ task fullJar(type: Jar) { } manifest { attributes( - "Main-Class": "org.springframework.boot.loader.JarLauncher", + "Main-Class": "org.springframework.boot.loader.launch.JarLauncher", "Start-Class": "org.springframework.boot.cli.SpringCli" ) } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/content/bin/spring.bat b/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/content/bin/spring.bat index c9c0081c06f7..3bec92853213 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/content/bin/spring.bat +++ b/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/content/bin/spring.bat @@ -59,7 +59,7 @@ set CMD_LINE_ARGS=%$ @rem Setup the command line set CLASSPATH=%SPRING_HOME%\lib\* -"%JAVA_EXE%" %JAVA_OPTS% -cp "%CLASSPATH%" org.springframework.boot.loader.JarLauncher %CMD_LINE_ARGS% +"%JAVA_EXE%" %JAVA_OPTS% -cp "%CLASSPATH%" org.springframework.boot.loader.launch.JarLauncher %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell diff --git a/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/executablecontent/bin/spring b/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/executablecontent/bin/spring index 0e025b27d6f7..dda4e9b2819b 100755 --- a/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/executablecontent/bin/spring +++ b/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/executablecontent/bin/spring @@ -115,4 +115,4 @@ if $cygwin; then fi IFS=" " read -r -a javaOpts <<< "$JAVA_OPTS" -exec "${JAVA_HOME}/bin/java" "${javaOpts[@]}" -cp "$CLASSPATH" org.springframework.boot.loader.JarLauncher "$@" +exec "${JAVA_HOME}/bin/java" "${javaOpts[@]}" -cp "$CLASSPATH" org.springframework.boot.loader.launch.JarLauncher "$@" diff --git a/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/homebrew/spring-boot.rb b/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/homebrew/spring-boot.rb index f3017bd67d92..2dede209226b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/homebrew/spring-boot.rb +++ b/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/homebrew/spring-boot.rb @@ -2,8 +2,8 @@ class SpringBoot < Formula homepage 'https://spring.io/projects/spring-boot' - url '${repo}/org/springframework/boot/spring-boot-cli/${project.version}/spring-boot-cli-${project.version}-bin.tar.gz' - version '${project.version}' + url '${repo}/org/springframework/boot/spring-boot-cli/${version}/spring-boot-cli-${version}-bin.tar.gz' + version '${version}' sha256 '${hash}' head 'https://github.com/spring-projects/spring-boot.git', :branch => "main" diff --git a/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/HelpCommand.java b/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/HelpCommand.java index 63291a6b7b92..6cae752f3f55 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/HelpCommand.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/HelpCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,10 +81,7 @@ public String getUsageHelp() { } private boolean isHelpShown(Command command) { - if (command instanceof HelpCommand || command instanceof HintCommand) { - return false; - } - return true; + return !(command instanceof HelpCommand) && !(command instanceof HintCommand); } @Override diff --git a/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitCommand.java b/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitCommand.java index e60677acddc8..557c44304d7c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitCommand.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ForkProcessCommand.java b/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ForkProcessCommand.java index a3c282faf429..eb2336d75ca8 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ForkProcessCommand.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ForkProcessCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,7 @@ */ class ForkProcessCommand extends RunProcessCommand { - private static final String MAIN_CLASS = "org.springframework.boot.loader.JarLauncher"; + private static final String MAIN_CLASS = "org.springframework.boot.loader.launch.JarLauncher"; private final Command command; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitCommandTests.java b/spring-boot-project/spring-boot-tools/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitCommandTests.java index 0bf1b7db2499..f9607f01d9fd 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitCommandTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitCommandTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/build.gradle new file mode 100644 index 000000000000..186a2cff85a1 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/build.gradle @@ -0,0 +1,58 @@ +plugins { + id "java" + id "org.springframework.boot.conventions" +} + +description = "Spring Boot Configuration Metadata Changelog Generator" + +configurations { + oldMetadata + newMetadata +} + +dependencies { + implementation(enforcedPlatform(project(":spring-boot-project:spring-boot-dependencies"))) + implementation(project(":spring-boot-project:spring-boot-tools:spring-boot-configuration-metadata")) + + testImplementation(enforcedPlatform(project(":spring-boot-project:spring-boot-dependencies"))) + testImplementation("org.assertj:assertj-core") + testImplementation("org.junit.jupiter:junit-jupiter") +} + +if (project.hasProperty("oldVersion") && project.hasProperty("newVersion")) { + dependencies { + ["spring-boot", + "spring-boot-actuator", + "spring-boot-actuator-autoconfigure", + "spring-boot-autoconfigure", + "spring-boot-devtools", + "spring-boot-test-autoconfigure"].each { + oldMetadata("org.springframework.boot:$it:$oldVersion") + newMetadata("org.springframework.boot:$it:$newVersion") + } + } + + def prepareOldMetadata = tasks.register("prepareOldMetadata", Sync) { + from(configurations.oldMetadata) + if (project.hasProperty("oldVersion")) { + destinationDir = project.file("build/configuration-metadata-diff/$oldVersion") + } + } + + def prepareNewMetadata = tasks.register("prepareNewMetadata", Sync) { + from(configurations.newMetadata) + if (project.hasProperty("newVersion")) { + destinationDir = project.file("build/configuration-metadata-diff/$newVersion") + } + } + + tasks.register("generate", JavaExec) { + inputs.files(prepareOldMetadata, prepareNewMetadata) + outputs.file(project.file("build/configuration-metadata-changelog.adoc")) + classpath = sourceSets.main.runtimeClasspath + mainClass = 'org.springframework.boot.configurationmetadata.changelog.ChangelogGenerator' + if (project.hasProperty("oldVersion") && project.hasProperty("newVersion")) { + args = [project.file("build/configuration-metadata-diff/$oldVersion"), project.file("build/configuration-metadata-diff/$newVersion"), project.file("build/configuration-metadata-changelog.adoc")] + } + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Changelog.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Changelog.java new file mode 100644 index 000000000000..964298fe567c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Changelog.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata.changelog; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; + +/** + * A changelog containing differences computed from two repositories of configuration + * metadata. + * + * @param oldVersionNumber the name of the old version + * @param newVersionNumber the name of the new version + * @param differences the differences + * @author Stephane Nicoll + * @author Andy Wilkinson + * @author Phillip Webb + */ +record Changelog(String oldVersionNumber, String newVersionNumber, List differences) { + + static Changelog of(String oldVersionNumber, ConfigurationMetadataRepository oldMetadata, String newVersionNumber, + ConfigurationMetadataRepository newMetadata) { + return new Changelog(oldVersionNumber, newVersionNumber, computeDifferences(oldMetadata, newMetadata)); + } + + static List computeDifferences(ConfigurationMetadataRepository oldMetadata, + ConfigurationMetadataRepository newMetadata) { + List seenIds = new ArrayList<>(); + List differences = new ArrayList<>(); + for (ConfigurationMetadataProperty oldProperty : oldMetadata.getAllProperties().values()) { + String id = oldProperty.getId(); + seenIds.add(id); + ConfigurationMetadataProperty newProperty = newMetadata.getAllProperties().get(id); + Difference difference = Difference.compute(oldProperty, newProperty); + if (difference != null) { + differences.add(difference); + } + } + for (ConfigurationMetadataProperty newProperty : newMetadata.getAllProperties().values()) { + if ((!seenIds.contains(newProperty.getId())) && (!newProperty.isDeprecated())) { + differences.add(new Difference(DifferenceType.ADDED, null, newProperty)); + } + } + return List.copyOf(differences); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGenerator.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGenerator.java new file mode 100644 index 000000000000..9d1ee1d62282 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGenerator.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata.changelog; + +import java.io.File; +import java.io.IOException; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder; + +/** + * Generates a configuration metadata changelog. Requires three arguments: + * + *

    + *
  1. The path of a directory containing jar files of the old version + *
  2. The path of a directory containing jar files of the new version + *
  3. The path of a file to which the asciidoc changelog will be written + *
+ * + * The name of each directory will be used as version numbers in generated changelog. + * + * @author Andy Wilkinson + * @author Phillip Webb + * @since 3.2.0 + */ +public final class ChangelogGenerator { + + private ChangelogGenerator() { + } + + public static void main(String[] args) throws IOException { + generate(new File(args[0]), new File(args[1]), new File(args[2])); + } + + private static void generate(File oldDir, File newDir, File out) throws IOException { + String oldVersionNumber = oldDir.getName(); + ConfigurationMetadataRepository oldMetadata = buildRepository(oldDir); + String newVersionNumber = newDir.getName(); + ConfigurationMetadataRepository newMetadata = buildRepository(newDir); + Changelog changelog = Changelog.of(oldVersionNumber, oldMetadata, newVersionNumber, newMetadata); + try (ChangelogWriter writer = new ChangelogWriter(out)) { + writer.write(changelog); + } + System.out.println("%nConfiguration metadata changelog written to '%s'".formatted(out)); + } + + static ConfigurationMetadataRepository buildRepository(File directory) { + ConfigurationMetadataRepositoryJsonBuilder builder = ConfigurationMetadataRepositoryJsonBuilder.create(); + for (File file : directory.listFiles()) { + try (JarFile jarFile = new JarFile(file)) { + JarEntry metadataEntry = jarFile.getJarEntry("META-INF/spring-configuration-metadata.json"); + if (metadataEntry != null) { + builder.withJsonResource(jarFile.getInputStream(metadataEntry)); + } + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + return builder.build(); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriter.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriter.java new file mode 100644 index 000000000000..a87c6bb47d80 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriter.java @@ -0,0 +1,235 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata.changelog; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.text.BreakIterator; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.boot.configurationmetadata.Deprecation; + +/** + * Writes a {@link Changelog} using asciidoc markup. + * + * @author Stephane Nicoll + * @author Andy Wilkinson + * @author Phillip Webb + * @author Moritz Halbritter + */ +class ChangelogWriter implements AutoCloseable { + + private static final Comparator COMPARING_ID = Comparator + .comparing(ConfigurationMetadataProperty::getId); + + private final PrintWriter out; + + ChangelogWriter(File out) throws IOException { + this(new FileWriter(out)); + } + + ChangelogWriter(Writer out) { + this.out = new PrintWriter(out); + } + + void write(Changelog changelog) { + String oldVersionNumber = changelog.oldVersionNumber(); + String newVersionNumber = changelog.newVersionNumber(); + Map> differencesByType = collateByType(changelog); + write("Configuration property changes between `%s` and `%s`%n", oldVersionNumber, newVersionNumber); + write("%n%n%n== Deprecated in %s%n%n", newVersionNumber); + writeDeprecated(differencesByType.get(DifferenceType.DEPRECATED)); + write("%n%n%n== Added in %s%n%n", newVersionNumber); + writeAdded(differencesByType.get(DifferenceType.ADDED)); + write("%n%n%n== Removed in %s%n%n", newVersionNumber); + writeRemoved(differencesByType.get(DifferenceType.DELETED), differencesByType.get(DifferenceType.DEPRECATED)); + } + + private Map> collateByType(Changelog differences) { + Map> byType = new HashMap<>(); + for (DifferenceType type : DifferenceType.values()) { + byType.put(type, new ArrayList<>()); + } + for (Difference difference : differences.differences()) { + byType.get(difference.type()).add(difference); + } + return byType; + } + + private void writeDeprecated(List differences) { + List rows = sortProperties(differences, Difference::newProperty).stream() + .filter(this::isDeprecatedInRelease) + .toList(); + writeTable("| Key | Replacement | Reason", rows, this::writeDeprecated); + } + + private void writeDeprecated(Difference difference) { + writeDeprecatedPropertyRow(difference.newProperty()); + } + + private void writeAdded(List differences) { + List rows = sortProperties(differences, Difference::newProperty); + writeTable("| Key | Default value | Description", rows, this::writeAdded); + } + + private void writeAdded(Difference difference) { + writeRegularPropertyRow(difference.newProperty()); + } + + private void writeRemoved(List deleted, List deprecated) { + List rows = getRemoved(deleted, deprecated); + writeTable("| Key | Replacement | Reason", rows, this::writeRemoved); + } + + private List getRemoved(List deleted, List deprecated) { + List result = new ArrayList<>(deleted); + deprecated.stream().filter(Predicate.not(this::isDeprecatedInRelease)).forEach(result::remove); + return sortProperties(result, + (difference) -> getFirstNonNull(difference, Difference::oldProperty, Difference::newProperty)); + } + + private void writeRemoved(Difference difference) { + writeDeprecatedPropertyRow(getFirstNonNull(difference, Difference::newProperty, Difference::oldProperty)); + } + + private List sortProperties(List differences, + Function extractor) { + return differences.stream().sorted(Comparator.comparing(extractor, COMPARING_ID)).toList(); + } + + @SafeVarargs + @SuppressWarnings("varargs") + private P getFirstNonNull(T t, Function... extractors) { + return Stream.of(extractors) + .map((extractor) -> extractor.apply(t)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + + private void writeTable(String header, List rows, Consumer action) { + if (rows.isEmpty()) { + write("_None_.%n"); + } + else { + writeTableBreak(); + write(header + "%n%n"); + for (Iterator iterator = rows.iterator(); iterator.hasNext();) { + action.accept(iterator.next()); + write((!iterator.hasNext()) ? null : "%n"); + } + writeTableBreak(); + } + } + + private void writeTableBreak() { + write("|======================%n"); + } + + private void writeRegularPropertyRow(ConfigurationMetadataProperty property) { + writeCell(monospace(property.getId())); + writeCell(monospace(asString(property.getDefaultValue()))); + writeCell(property.getShortDescription()); + } + + private void writeDeprecatedPropertyRow(ConfigurationMetadataProperty property) { + Deprecation deprecation = (property.getDeprecation() != null) ? property.getDeprecation() : new Deprecation(); + writeCell(monospace(property.getId())); + writeCell(monospace(deprecation.getReplacement())); + writeCell(getFirstSentence(deprecation.getReason())); + } + + private String getFirstSentence(String text) { + if (text == null) { + return null; + } + int dot = text.indexOf('.'); + if (dot != -1) { + BreakIterator breakIterator = BreakIterator.getSentenceInstance(Locale.US); + breakIterator.setText(text); + String sentence = text.substring(breakIterator.first(), breakIterator.next()).trim(); + return removeSpaceBetweenLine(sentence); + } + String[] lines = text.split(System.lineSeparator()); + return lines[0].trim(); + } + + private String removeSpaceBetweenLine(String text) { + String[] lines = text.split(System.lineSeparator()); + return Arrays.stream(lines).map(String::trim).collect(Collectors.joining(" ")); + } + + private boolean isDeprecatedInRelease(Difference difference) { + Deprecation deprecation = difference.newProperty().getDeprecation(); + return (deprecation != null) && (deprecation.getLevel() != Deprecation.Level.ERROR); + } + + private String monospace(String value) { + return (value != null) ? "`%s`".formatted(value) : null; + } + + private void writeCell(String content) { + if (content == null) { + write("|%n"); + } + else { + String escaped = escapeForTableCell(content); + write("| %s%n".formatted(escaped)); + } + } + + private String escapeForTableCell(String content) { + return content.replace("|", "\\|"); + } + + private void write(String format, Object... args) { + if (format != null) { + Object[] strings = Arrays.stream(args).map(this::asString).toArray(); + this.out.append(format.formatted(strings)); + } + } + + private String asString(Object value) { + if (value instanceof Object[] array) { + return Stream.of(array).map(this::asString).collect(Collectors.joining(", ")); + } + return (value != null) ? value.toString() : null; + } + + @Override + public void close() { + this.out.close(); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Difference.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Difference.java new file mode 100644 index 000000000000..8d0fb66cfa7e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Difference.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata.changelog; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.boot.configurationmetadata.Deprecation.Level; + +/** + * A difference the metadata. + * + * @param type the type of the difference + * @param oldProperty the old property + * @param newProperty the new property + * @author Stephane Nicoll + * @author Andy Wilkinson + * @author Phillip Webb + */ +record Difference(DifferenceType type, ConfigurationMetadataProperty oldProperty, + ConfigurationMetadataProperty newProperty) { + + static Difference compute(ConfigurationMetadataProperty oldProperty, ConfigurationMetadataProperty newProperty) { + if (newProperty == null) { + if (!(oldProperty.isDeprecated() && oldProperty.getDeprecation().getLevel() == Level.ERROR)) { + return new Difference(DifferenceType.DELETED, oldProperty, null); + } + return null; + } + if (newProperty.isDeprecated() && !oldProperty.isDeprecated()) { + return new Difference(DifferenceType.DEPRECATED, oldProperty, newProperty); + } + if (oldProperty.isDeprecated() && oldProperty.getDeprecation().getLevel() == Level.WARNING + && newProperty.isDeprecated() && newProperty.getDeprecation().getLevel() == Level.ERROR) { + return new Difference(DifferenceType.DELETED, oldProperty, newProperty); + } + return null; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/DifferenceType.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/DifferenceType.java new file mode 100644 index 000000000000..b673310b4072 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/DifferenceType.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata.changelog; + +/** + * The type of a difference in the metadata. + * + * @author Andy Wilkinson + */ +enum DifferenceType { + + /** + * The entry has been added. + */ + ADDED, + + /** + * The entry has been made deprecated. It may or may not still exist in the previous + * version. + */ + DEPRECATED, + + /** + * The entry has been deleted. + */ + DELETED + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/package-info.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/package-info.java new file mode 100644 index 000000000000..96eca3173f88 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Spring Boot configuration metadata changelog generator. + */ +package org.springframework.boot.configurationmetadata.changelog; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGeneratorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGeneratorTests.java new file mode 100644 index 000000000000..efa1760c2736 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGeneratorTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata.changelog; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ChangelogGenerator}. + * + * @author Phillip Webb + */ +class ChangelogGeneratorTests { + + @TempDir + File temp; + + @Test + void generateChangeLog() throws IOException { + File oldJars = new File(this.temp, "1.0"); + addJar(oldJars, "sample-1.0.json"); + File newJars = new File(this.temp, "2.0"); + addJar(newJars, "sample-2.0.json"); + File out = new File(this.temp, "changes.adoc"); + String[] args = new String[] { oldJars.getAbsolutePath(), newJars.getAbsolutePath(), out.getAbsolutePath() }; + ChangelogGenerator.main(args); + assertThat(out).usingCharset(StandardCharsets.UTF_8) + .hasSameTextualContentAs(new File("src/test/resources/sample.adoc")); + } + + private void addJar(File directory, String filename) throws IOException { + directory.mkdirs(); + try (JarOutputStream out = new JarOutputStream(new FileOutputStream(new File(directory, "sample.jar")))) { + out.putNextEntry(new ZipEntry("META-INF/spring-configuration-metadata.json")); + try (InputStream in = new FileInputStream("src/test/resources/" + filename)) { + in.transferTo(out); + out.closeEntry(); + } + } + + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogTests.java new file mode 100644 index 000000000000..5057cb8087ee --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata.changelog; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link Changelog}. + * + * @author Stephane Nicoll + * @author Andy Wilkinson + */ +class ChangelogTests { + + @Test + void diffContainsDifferencesBetweenLeftAndRightInputs() { + Changelog differences = TestChangelog.load(); + assertThat(differences).isNotNull(); + assertThat(differences.oldVersionNumber()).isEqualTo("1.0"); + assertThat(differences.newVersionNumber()).isEqualTo("2.0"); + assertThat(differences.differences()).hasSize(4); + List added = differences.differences() + .stream() + .filter((difference) -> difference.type() == DifferenceType.ADDED) + .toList(); + assertThat(added).hasSize(1); + assertProperty(added.get(0).newProperty(), "test.add", String.class, "new"); + List deleted = differences.differences() + .stream() + .filter((difference) -> difference.type() == DifferenceType.DELETED) + .toList(); + assertThat(deleted).hasSize(2) + .anySatisfy((entry) -> assertProperty(entry.oldProperty(), "test.delete", String.class, "delete")) + .anySatisfy( + (entry) -> assertProperty(entry.newProperty(), "test.delete.deprecated", String.class, "delete")); + List deprecated = differences.differences() + .stream() + .filter((difference) -> difference.type() == DifferenceType.DEPRECATED) + .toList(); + assertThat(deprecated).hasSize(1); + assertProperty(deprecated.get(0).oldProperty(), "test.deprecate", String.class, "wrong"); + assertProperty(deprecated.get(0).newProperty(), "test.deprecate", String.class, "wrong"); + } + + private void assertProperty(ConfigurationMetadataProperty property, String id, Class type, Object defaultValue) { + assertThat(property).isNotNull(); + assertThat(property.getId()).isEqualTo(id); + assertThat(property.getType()).isEqualTo(type.getName()); + assertThat(property.getDefaultValue()).isEqualTo(defaultValue); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriterTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriterTests.java new file mode 100644 index 000000000000..5e72e3b567d0 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriterTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata.changelog; + +import java.io.File; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; + +import org.assertj.core.util.Files; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ChangelogWriter}. + * + * @author Phillip Webb + */ +class ChangelogWriterTests { + + @Test + void writeChangelog() { + StringWriter out = new StringWriter(); + try (ChangelogWriter writer = new ChangelogWriter(out)) { + writer.write(TestChangelog.load()); + } + String expected = Files.contentOf(new File("src/test/resources/sample.adoc"), StandardCharsets.UTF_8); + assertThat(out).hasToString(expected); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/TestChangelog.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/TestChangelog.java new file mode 100644 index 000000000000..58a1c34642b7 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/TestChangelog.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata.changelog; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder; + +/** + * Factory to create test {@link Changelog} instance. + * + * @author Andy Wilkinson + * @author Phillip Webb + */ +final class TestChangelog { + + private TestChangelog() { + } + + static Changelog load() { + ConfigurationMetadataRepository previousRepository = load("sample-1.0.json"); + ConfigurationMetadataRepository repository = load("sample-2.0.json"); + return Changelog.of("1.0", previousRepository, "2.0", repository); + } + + private static ConfigurationMetadataRepository load(String filename) { + try (InputStream inputStream = new FileInputStream("src/test/resources/" + filename)) { + return ConfigurationMetadataRepositoryJsonBuilder.create(inputStream).build(); + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-1.0.json b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-1.0.json new file mode 100644 index 000000000000..a0584bc5695b --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-1.0.json @@ -0,0 +1,31 @@ +{ + "properties": [ + { + "name": "test.equal", + "type": "java.lang.String", + "description": "Test equality.", + "defaultValue": "test" + }, + { + "name": "test.deprecate", + "type": "java.lang.String", + "description": "Test deprecate.", + "defaultValue": "wrong" + }, + { + "name": "test.delete", + "type": "java.lang.String", + "description": "Test delete.", + "defaultValue": "delete" + }, + { + "name": "test.delete.deprecated", + "type": "java.lang.String", + "description": "Test delete deprecated.", + "defaultValue": "delete", + "deprecation": { + "level": "warning" + } + } + ] +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-2.0.json b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-2.0.json new file mode 100644 index 000000000000..ef959d39c9eb --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-2.0.json @@ -0,0 +1,36 @@ +{ + "properties": [ + { + "name": "test.add", + "type": "java.lang.String", + "description": "Test add.", + "defaultValue": "new" + }, + { + "name": "test.equal", + "type": "java.lang.String", + "description": "Test equality.", + "defaultValue": "test" + }, + { + "name": "test.deprecate", + "type": "java.lang.String", + "description": "Test deprecate.", + "defaultValue": "wrong", + "deprecation": { + "level": "error" + } + }, + { + "name": "test.delete.deprecated", + "type": "java.lang.String", + "description": "Test delete deprecated.", + "defaultValue": "delete", + "deprecation": { + "level": "error", + "replacement": "test.add", + "reason": "it was just bad" + } + } + ] +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample.adoc b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample.adoc new file mode 100644 index 000000000000..26876aa85426 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample.adoc @@ -0,0 +1,35 @@ +Configuration property changes between `1.0` and `2.0` + + + +== Deprecated in 2.0 + +_None_. + + + +== Added in 2.0 + +|====================== +| Key | Default value | Description + +| `test.add` +| `new` +| Test add. +|====================== + + + +== Removed in 2.0 + +|====================== +| Key | Replacement | Reason + +| `test.delete` +| +| + +| `test.delete.deprecated` +| `test.add` +| it was just bad +|====================== diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/SimpleConfigurationMetadataRepository.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/SimpleConfigurationMetadataRepository.java index bba6c2562787..c196afb5e2f2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/SimpleConfigurationMetadataRepository.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/SimpleConfigurationMetadataRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,11 +54,8 @@ public Map getAllProperties() { public void add(Collection sources) { for (ConfigurationMetadataSource source : sources) { String groupId = source.getGroupId(); - ConfigurationMetadataGroup group = this.allGroups.get(groupId); - if (group == null) { - group = new ConfigurationMetadataGroup(groupId); - this.allGroups.put(groupId, group); - } + ConfigurationMetadataGroup group = this.allGroups.computeIfAbsent(groupId, + (key) -> new ConfigurationMetadataGroup(groupId)); String sourceType = source.getType(); if (sourceType != null) { addOrMergeSource(group.getSources(), sourceType, source); @@ -74,9 +71,9 @@ public void add(Collection sources) { */ public void add(ConfigurationMetadataProperty property, ConfigurationMetadataSource source) { if (source != null) { - putIfAbsent(source.getProperties(), property.getId(), property); + source.getProperties().putIfAbsent(property.getId(), property); } - putIfAbsent(getGroup(source).getProperties(), property.getId(), property); + getGroup(source).getProperties().putIfAbsent(property.getId(), property); } /** @@ -91,7 +88,7 @@ public void include(ConfigurationMetadataRepository repository) { } else { // Merge properties - group.getProperties().forEach((name, value) -> putIfAbsent(existingGroup.getProperties(), name, value)); + group.getProperties().forEach((name, value) -> existingGroup.getProperties().putIfAbsent(name, value)); // Merge sources group.getSources().forEach((name, value) -> addOrMergeSource(existingGroup.getSources(), name, value)); } @@ -101,12 +98,7 @@ public void include(ConfigurationMetadataRepository repository) { private ConfigurationMetadataGroup getGroup(ConfigurationMetadataSource source) { if (source == null) { - ConfigurationMetadataGroup rootGroup = this.allGroups.get(ROOT_GROUP); - if (rootGroup == null) { - rootGroup = new ConfigurationMetadataGroup(ROOT_GROUP); - this.allGroups.put(ROOT_GROUP, rootGroup); - } - return rootGroup; + return this.allGroups.computeIfAbsent(ROOT_GROUP, (key) -> new ConfigurationMetadataGroup(ROOT_GROUP)); } return this.allGroups.get(source.getGroupId()); } @@ -118,13 +110,7 @@ private void addOrMergeSource(Map sources, sources.put(name, source); } else { - source.getProperties().forEach((k, v) -> putIfAbsent(existingSource.getProperties(), k, v)); - } - } - - private void putIfAbsent(Map map, String key, V value) { - if (!map.containsKey(key)) { - map.put(key, value); + source.getProperties().forEach((k, v) -> existingSource.getProperties().putIfAbsent(k, v)); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/build.gradle index 43a7ac73f2d0..a7bde3691ba8 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/build.gradle @@ -27,4 +27,7 @@ dependencies { testImplementation("org.mockito:mockito-core") testImplementation("org.projectlombok:lombok") testImplementation("org.springframework:spring-core") + testImplementation("org.apache.commons:commons-dbcp2") { + exclude group: "commons-logging", module: "commons-logging" + } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/json-shade/java/org/springframework/boot/configurationprocessor/json/JSON.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/json-shade/java/org/springframework/boot/configurationprocessor/json/JSON.java index 242705b599ac..97404944fced 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/json-shade/java/org/springframework/boot/configurationprocessor/json/JSON.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/json-shade/java/org/springframework/boot/configurationprocessor/json/JSON.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/json-shade/java/org/springframework/boot/configurationprocessor/json/JSONObject.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/json-shade/java/org/springframework/boot/configurationprocessor/json/JSONObject.java index 66e4e36e6846..acf177a7508e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/json-shade/java/org/springframework/boot/configurationprocessor/json/JSONObject.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/json-shade/java/org/springframework/boot/configurationprocessor/json/JSONObject.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/json-shade/java/org/springframework/boot/configurationprocessor/json/JSONTokener.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/json-shade/java/org/springframework/boot/configurationprocessor/json/JSONTokener.java index be1672533f1f..682ca94ae7ea 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/json-shade/java/org/springframework/boot/configurationprocessor/json/JSONTokener.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/json-shade/java/org/springframework/boot/configurationprocessor/json/JSONTokener.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java index 6c805144a8d8..5e55b564d8fd 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,14 +20,12 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.time.Duration; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.Stack; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; @@ -57,6 +55,8 @@ * @author Phillip Webb * @author Kris De Volder * @author Jonas Keßler + * @author Scott Frederick + * @author Moritz Halbritter * @since 1.2.0 */ @SupportedAnnotationTypes({ ConfigurationMetadataAnnotationProcessor.AUTO_CONFIGURATION_ANNOTATION, @@ -102,8 +102,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor static final String AUTO_CONFIGURATION_ANNOTATION = "org.springframework.boot.autoconfigure.AutoConfiguration"; - private static final Set SUPPORTED_OPTIONS = Collections - .unmodifiableSet(Collections.singleton(ADDITIONAL_METADATA_LOCATIONS_OPTION)); + private static final Set SUPPORTED_OPTIONS = Set.of(ADDITIONAL_METADATA_LOCATIONS_OPTION); private MetadataStore metadataStore; @@ -136,8 +135,8 @@ protected String defaultValueAnnotation() { } protected Set endpointAnnotations() { - return new HashSet<>(Arrays.asList(CONTROLLER_ENDPOINT_ANNOTATION, ENDPOINT_ANNOTATION, JMX_ENDPOINT_ANNOTATION, - REST_CONTROLLER_ENDPOINT_ANNOTATION, SERVLET_ENDPOINT_ANNOTATION, WEB_ENDPOINT_ANNOTATION)); + return Set.of(CONTROLLER_ENDPOINT_ANNOTATION, ENDPOINT_ANNOTATION, JMX_ENDPOINT_ANNOTATION, + REST_CONTROLLER_ENDPOINT_ANNOTATION, SERVLET_ENDPOINT_ANNOTATION, WEB_ENDPOINT_ANNOTATION); } protected String readOperationAnnotation() { @@ -213,10 +212,10 @@ private void processElement(Element element) { if (annotation != null) { String prefix = getPrefix(annotation); if (element instanceof TypeElement typeElement) { - processAnnotatedTypeElement(prefix, typeElement, new Stack<>()); + processAnnotatedTypeElement(prefix, typeElement, new ArrayDeque<>()); } else if (element instanceof ExecutableElement executableElement) { - processExecutableElement(prefix, executableElement, new Stack<>()); + processExecutableElement(prefix, executableElement, new ArrayDeque<>()); } } } @@ -225,13 +224,13 @@ else if (element instanceof ExecutableElement executableElement) { } } - private void processAnnotatedTypeElement(String prefix, TypeElement element, Stack seen) { + private void processAnnotatedTypeElement(String prefix, TypeElement element, Deque seen) { String type = this.metadataEnv.getTypeUtils().getQualifiedName(element); this.metadataCollector.add(ItemMetadata.newGroup(prefix, type, type, null)); processTypeElement(prefix, element, null, seen); } - private void processExecutableElement(String prefix, ExecutableElement element, Stack seen) { + private void processExecutableElement(String prefix, ExecutableElement element, Deque seen) { if ((!element.getModifiers().contains(Modifier.PRIVATE)) && (TypeKind.VOID != element.getReturnType().getKind())) { Element returns = this.processingEnv.getTypeUtils().asElement(element.getReturnType()); @@ -254,7 +253,7 @@ private void processExecutableElement(String prefix, ExecutableElement element, } private void processTypeElement(String prefix, TypeElement element, ExecutableElement source, - Stack seen) { + Deque seen) { if (!seen.contains(element)) { seen.push(element); new PropertyDescriptorResolver(this.metadataEnv).resolve(element, source).forEach((descriptor) -> { @@ -290,18 +289,29 @@ private void processEndpoint(AnnotationMirror annotation, TypeElement element) { return; // Can't process that endpoint } String endpointKey = ItemMetadata.newItemMetadataPrefix("management.endpoint.", endpointId); - Boolean enabledByDefault = (Boolean) elementValues.get("enableByDefault"); + boolean enabledByDefault = (boolean) elementValues.getOrDefault("enableByDefault", true); String type = this.metadataEnv.getTypeUtils().getQualifiedName(element); - this.metadataCollector.add(ItemMetadata.newGroup(endpointKey, type, type, null)); - this.metadataCollector.add(ItemMetadata.newProperty(endpointKey, "enabled", Boolean.class.getName(), type, null, - String.format("Whether to enable the %s endpoint.", endpointId), - (enabledByDefault != null) ? enabledByDefault : true, null)); + this.metadataCollector.addIfAbsent(ItemMetadata.newGroup(endpointKey, type, type, null)); + this.metadataCollector.add( + ItemMetadata.newProperty(endpointKey, "enabled", Boolean.class.getName(), type, null, + "Whether to enable the %s endpoint.".formatted(endpointId), enabledByDefault, null), + (existing) -> checkEnabledValueMatchesExisting(existing, enabledByDefault, type)); if (hasMainReadOperation(element)) { - this.metadataCollector.add(ItemMetadata.newProperty(endpointKey, "cache.time-to-live", + this.metadataCollector.addIfAbsent(ItemMetadata.newProperty(endpointKey, "cache.time-to-live", Duration.class.getName(), type, null, "Maximum time that a response can be cached.", "0ms", null)); } } + private void checkEnabledValueMatchesExisting(ItemMetadata existing, boolean enabledByDefault, String sourceType) { + boolean existingDefaultValue = (boolean) existing.getDefaultValue(); + if (enabledByDefault != existingDefaultValue) { + throw new IllegalStateException( + "Existing property '%s' from type %s has a conflicting value. Existing value: %b, new value from type %s: %b" + .formatted(existing.getName(), existing.getSourceType(), existingDefaultValue, sourceType, + enabledByDefault)); + } + } + private boolean hasMainReadOperation(TypeElement element) { for (ExecutableElement method : ElementFilter.methodsIn(element.getEnclosedElements())) { if (this.metadataEnv.getReadOperationAnnotation(method) != null @@ -322,16 +332,11 @@ private boolean hasNoOrOptionalParameters(ExecutableElement method) { } private String getPrefix(AnnotationMirror annotation) { - Map elementValues = this.metadataEnv.getAnnotationElementValues(annotation); - Object prefix = elementValues.get("prefix"); - if (prefix != null && !"".equals(prefix)) { - return (String) prefix; - } - Object value = elementValues.get("value"); - if (value != null && !"".equals(value)) { - return (String) value; + String prefix = this.metadataEnv.getAnnotationElementStringValue(annotation, "prefix"); + if (prefix != null) { + return prefix; } - return null; + return this.metadataEnv.getAnnotationElementStringValue(annotation, "value"); } protected ConfigurationMetadata writeMetadata() throws Exception { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptor.java index a567a946585f..da36d12a31d7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptor.java @@ -16,195 +16,47 @@ package org.springframework.boot.configurationprocessor; +import java.util.Arrays; import java.util.List; -import java.util.Map; -import java.util.function.Function; -import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; -import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.TypeKindVisitor8; -import javax.tools.Diagnostic.Kind; /** * A {@link PropertyDescriptor} for a constructor parameter. * * @author Stephane Nicoll + * @author Phillip Webb */ -class ConstructorParameterPropertyDescriptor extends PropertyDescriptor { +class ConstructorParameterPropertyDescriptor extends ParameterPropertyDescriptor { - ConstructorParameterPropertyDescriptor(TypeElement ownerElement, ExecutableElement factoryMethod, - VariableElement source, String name, TypeMirror type, VariableElement field, ExecutableElement getter, - ExecutableElement setter) { - super(ownerElement, factoryMethod, source, name, type, field, getter, setter); - } + private final ExecutableElement setter; - @Override - protected boolean isProperty(MetadataGenerationEnvironment env) { - // If it's a constructor parameter, it doesn't matter as we must be able to bind - // it to build the object. - return !isNested(env); - } + private final VariableElement field; - @Override - protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) { - Object defaultValue = getDefaultValueFromAnnotation(environment, getSource()); - if (defaultValue != null) { - return defaultValue; - } - return getSource().asType().accept(DefaultPrimitiveTypeVisitor.INSTANCE, null); + ConstructorParameterPropertyDescriptor(String name, TypeMirror type, VariableElement parameter, + TypeElement declaringElement, ExecutableElement getter, ExecutableElement setter, VariableElement field) { + super(name, type, parameter, declaringElement, getter); + this.setter = setter; + this.field = field; } - private Object getDefaultValueFromAnnotation(MetadataGenerationEnvironment environment, Element element) { - AnnotationMirror annotation = environment.getDefaultValueAnnotation(element); - List defaultValue = getDefaultValue(environment, annotation); - if (defaultValue != null) { - try { - TypeMirror specificType = determineSpecificType(environment); - if (defaultValue.size() == 1) { - return coerceValue(specificType, defaultValue.get(0)); - } - return defaultValue.stream().map((value) -> coerceValue(specificType, value)).toList(); - } - catch (IllegalArgumentException ex) { - environment.getMessager().printMessage(Kind.ERROR, ex.getMessage(), element, annotation); - } - } - return null; - } - - @SuppressWarnings("unchecked") - private List getDefaultValue(MetadataGenerationEnvironment environment, AnnotationMirror annotation) { - if (annotation == null) { - return null; - } - Map values = environment.getAnnotationElementValues(annotation); - return (List) values.get("value"); - } - - private TypeMirror determineSpecificType(MetadataGenerationEnvironment environment) { - TypeMirror candidate = getSource().asType(); - TypeMirror elementCandidate = environment.getTypeUtils().extractElementType(candidate); - if (elementCandidate != null) { - candidate = elementCandidate; - } - PrimitiveType primitiveType = environment.getTypeUtils().getPrimitiveType(candidate); - return (primitiveType != null) ? primitiveType : candidate; - } - - private Object coerceValue(TypeMirror type, String value) { - Object coercedValue = type.accept(DefaultValueCoercionTypeVisitor.INSTANCE, value); - return (coercedValue != null) ? coercedValue : value; + @Override + protected List getDeprecatableElements() { + return Arrays.asList(getGetter(), this.setter, this.field); } - private static final class DefaultValueCoercionTypeVisitor extends TypeKindVisitor8 { - - private static final DefaultValueCoercionTypeVisitor INSTANCE = new DefaultValueCoercionTypeVisitor(); - - private T parseNumber(String value, Function parser, - PrimitiveType primitiveType) { - try { - return parser.apply(value); - } - catch (NumberFormatException ex) { - throw new IllegalArgumentException( - String.format("Invalid %s representation '%s'", primitiveType, value)); - } - } - - @Override - public Object visitPrimitiveAsBoolean(PrimitiveType t, String value) { - return Boolean.parseBoolean(value); - } - - @Override - public Object visitPrimitiveAsByte(PrimitiveType t, String value) { - return parseNumber(value, Byte::parseByte, t); - } - - @Override - public Object visitPrimitiveAsShort(PrimitiveType t, String value) { - return parseNumber(value, Short::parseShort, t); - } - - @Override - public Object visitPrimitiveAsInt(PrimitiveType t, String value) { - return parseNumber(value, Integer::parseInt, t); - } - - @Override - public Object visitPrimitiveAsLong(PrimitiveType t, String value) { - return parseNumber(value, Long::parseLong, t); - } - - @Override - public Object visitPrimitiveAsChar(PrimitiveType t, String value) { - if (value.length() > 1) { - throw new IllegalArgumentException(String.format("Invalid character representation '%s'", value)); - } - return value; - } - - @Override - public Object visitPrimitiveAsFloat(PrimitiveType t, String value) { - return parseNumber(value, Float::parseFloat, t); - } - - @Override - public Object visitPrimitiveAsDouble(PrimitiveType t, String value) { - return parseNumber(value, Double::parseDouble, t); - } - + @Override + protected boolean isMarkedAsNested(MetadataGenerationEnvironment environment) { + return environment.getNestedConfigurationPropertyAnnotation(this.field) != null; } - private static final class DefaultPrimitiveTypeVisitor extends TypeKindVisitor8 { - - private static final DefaultPrimitiveTypeVisitor INSTANCE = new DefaultPrimitiveTypeVisitor(); - - @Override - public Object visitPrimitiveAsBoolean(PrimitiveType t, Void ignore) { - return false; - } - - @Override - public Object visitPrimitiveAsByte(PrimitiveType t, Void ignore) { - return (byte) 0; - } - - @Override - public Object visitPrimitiveAsShort(PrimitiveType t, Void ignore) { - return (short) 0; - } - - @Override - public Object visitPrimitiveAsInt(PrimitiveType t, Void ignore) { - return 0; - } - - @Override - public Object visitPrimitiveAsLong(PrimitiveType t, Void ignore) { - return 0L; - } - - @Override - public Object visitPrimitiveAsChar(PrimitiveType t, Void ignore) { - return null; - } - - @Override - public Object visitPrimitiveAsFloat(PrimitiveType t, Void ignore) { - return 0F; - } - - @Override - public Object visitPrimitiveAsDouble(PrimitiveType t, Void ignore) { - return 0D; - } - + @Override + protected String resolveDescription(MetadataGenerationEnvironment environment) { + return environment.getTypeUtils().getJavaDoc(this.field); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/JavaBeanPropertyDescriptor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/JavaBeanPropertyDescriptor.java index 4fcf11366b4a..88023f5a493c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/JavaBeanPropertyDescriptor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/JavaBeanPropertyDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,10 @@ package org.springframework.boot.configurationprocessor; +import java.util.Arrays; +import java.util.List; + +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; @@ -25,23 +29,55 @@ * A {@link PropertyDescriptor} for a standard JavaBean property. * * @author Stephane Nicoll + * @author Phillip Webb */ -class JavaBeanPropertyDescriptor extends PropertyDescriptor { +class JavaBeanPropertyDescriptor extends PropertyDescriptor { + + private final ExecutableElement setter; + + private final VariableElement field; - JavaBeanPropertyDescriptor(TypeElement ownerElement, ExecutableElement factoryMethod, ExecutableElement getter, - String name, TypeMirror type, VariableElement field, ExecutableElement setter) { - super(ownerElement, factoryMethod, getter, name, type, field, getter, setter); + private final ExecutableElement factoryMethod; + + JavaBeanPropertyDescriptor(String name, TypeMirror type, TypeElement declaringElement, ExecutableElement getter, + ExecutableElement setter, VariableElement field, ExecutableElement factoryMethod) { + super(name, type, declaringElement, getter); + this.setter = setter; + this.field = field; + this.factoryMethod = factoryMethod; + } + + ExecutableElement getSetter() { + return this.setter; } @Override - protected boolean isProperty(MetadataGenerationEnvironment env) { - boolean isCollection = env.getTypeUtils().isCollectionOrMap(getType()); - return !env.isExcluded(getType()) && getGetter() != null && (getSetter() != null || isCollection); + protected boolean isMarkedAsNested(MetadataGenerationEnvironment environment) { + return environment.getNestedConfigurationPropertyAnnotation(this.field) != null + || environment.getNestedConfigurationPropertyAnnotation(getGetter()) != null; + } + + @Override + protected String resolveDescription(MetadataGenerationEnvironment environment) { + return environment.getTypeUtils().getJavaDoc(this.field); } @Override protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) { - return environment.getFieldDefaultValue(getOwnerElement(), getName()); + return environment.getFieldDefaultValue(getDeclaringElement(), getName()); + } + + @Override + protected List getDeprecatableElements() { + return Arrays.asList(getGetter(), this.setter, this.field, this.factoryMethod); + } + + @Override + public boolean isProperty(MetadataGenerationEnvironment env) { + boolean isCollection = env.getTypeUtils().isCollectionOrMap(getType()); + boolean hasGetter = getGetter() != null; + boolean hasSetter = getSetter() != null; + return !env.isExcluded(getType()) && hasGetter && (hasSetter || isCollection); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/LombokPropertyDescriptor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/LombokPropertyDescriptor.java index 81a6d6b5a46d..83a50b863d58 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/LombokPropertyDescriptor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/LombokPropertyDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,23 +16,25 @@ package org.springframework.boot.configurationprocessor; +import java.util.Arrays; +import java.util.List; import java.util.Map; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; -import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation; - /** * A {@link PropertyDescriptor} for a Lombok field. * * @author Stephane Nicoll + * @author Phillip Webb */ -class LombokPropertyDescriptor extends PropertyDescriptor { +class LombokPropertyDescriptor extends PropertyDescriptor { private static final String LOMBOK_DATA_ANNOTATION = "lombok.Data"; @@ -44,44 +46,62 @@ class LombokPropertyDescriptor extends PropertyDescriptor { private static final String LOMBOK_ACCESS_LEVEL_PUBLIC = "PUBLIC"; - LombokPropertyDescriptor(TypeElement typeElement, ExecutableElement factoryMethod, VariableElement field, - String name, TypeMirror type, ExecutableElement getter, ExecutableElement setter) { - super(typeElement, factoryMethod, field, name, type, field, getter, setter); + private final ExecutableElement setter; + + private final VariableElement field; + + private final ExecutableElement factoryMethod; + + LombokPropertyDescriptor(String name, TypeMirror type, TypeElement declaringElement, ExecutableElement getter, + ExecutableElement setter, VariableElement field, ExecutableElement factoryMethod) { + super(name, type, declaringElement, getter); + this.factoryMethod = factoryMethod; + this.field = field; + this.setter = setter; + } + + VariableElement getField() { + return this.field; } @Override - protected boolean isProperty(MetadataGenerationEnvironment env) { - if (!hasLombokPublicAccessor(env, true)) { - return false; - } - boolean isCollection = env.getTypeUtils().isCollectionOrMap(getType()); - return !env.isExcluded(getType()) && (hasSetter(env) || isCollection); + protected boolean isMarkedAsNested(MetadataGenerationEnvironment environment) { + return environment.getNestedConfigurationPropertyAnnotation(getField()) != null; + } + + @Override + protected String resolveDescription(MetadataGenerationEnvironment environment) { + return environment.getTypeUtils().getJavaDoc(this.field); } @Override protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) { - return environment.getFieldDefaultValue(getOwnerElement(), getName()); + return environment.getFieldDefaultValue(getDeclaringElement(), getName()); + } + + @Override + protected List getDeprecatableElements() { + return Arrays.asList(getGetter(), this.setter, this.field, this.factoryMethod); } @Override - protected boolean isNested(MetadataGenerationEnvironment environment) { - if (!hasLombokPublicAccessor(environment, true)) { + public boolean isProperty(MetadataGenerationEnvironment env) { + if (!hasLombokPublicAccessor(env, true)) { return false; } - return super.isNested(environment); + boolean isCollection = env.getTypeUtils().isCollectionOrMap(getType()); + return !env.isExcluded(getType()) && (hasSetter(env) || isCollection); } @Override - protected ItemDeprecation resolveItemDeprecation(MetadataGenerationEnvironment environment) { - boolean deprecated = environment.isDeprecated(getField()) || environment.isDeprecated(getGetter()) - || environment.isDeprecated(getFactoryMethod()); - return deprecated ? environment.resolveItemDeprecation(getGetter()) : null; + public boolean isNested(MetadataGenerationEnvironment environment) { + return hasLombokPublicAccessor(environment, true) && super.isNested(environment); } private boolean hasSetter(MetadataGenerationEnvironment env) { boolean nonFinalPublicField = !getField().getModifiers().contains(Modifier.FINAL) && hasLombokPublicAccessor(env, false); - return getSetter() != null || nonFinalPublicField; + return this.setter != null || nonFinalPublicField; } /** @@ -98,12 +118,12 @@ private boolean hasLombokPublicAccessor(MetadataGenerationEnvironment env, boole if (lombokMethodAnnotationOnField != null) { return isAccessLevelPublic(env, lombokMethodAnnotationOnField); } - AnnotationMirror lombokMethodAnnotationOnElement = env.getAnnotation(getOwnerElement(), annotation); + AnnotationMirror lombokMethodAnnotationOnElement = env.getAnnotation(getDeclaringElement(), annotation); if (lombokMethodAnnotationOnElement != null) { return isAccessLevelPublic(env, lombokMethodAnnotationOnElement); } - return (env.hasAnnotation(getOwnerElement(), LOMBOK_DATA_ANNOTATION) - || env.hasAnnotation(getOwnerElement(), LOMBOK_VALUE_ANNOTATION)); + return (env.hasAnnotation(getDeclaringElement(), LOMBOK_DATA_ANNOTATION) + || env.hasAnnotation(getDeclaringElement(), LOMBOK_VALUE_ANNOTATION)); } private boolean isAccessLevelPublic(MetadataGenerationEnvironment env, AnnotationMirror lombokAnnotation) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataCollector.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataCollector.java index c6fe7f81d79e..2aa1a55a074e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataCollector.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataCollector.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import java.util.function.Consumer; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; @@ -35,6 +36,7 @@ * * @author Andy Wilkinson * @author Kris De Volder + * @author Moritz Halbritter * @since 1.2.2 */ public class MetadataCollector { @@ -76,6 +78,24 @@ public void add(ItemMetadata metadata) { this.metadataItems.add(metadata); } + public void add(ItemMetadata metadata, Consumer onConflict) { + ItemMetadata existing = find(metadata.getName()); + if (existing != null) { + onConflict.accept(existing); + return; + } + add(metadata); + } + + public boolean addIfAbsent(ItemMetadata metadata) { + ItemMetadata existing = find(metadata.getName()); + if (existing != null) { + return false; + } + add(metadata); + return true; + } + public boolean hasSimilarGroup(ItemMetadata metadata) { if (!metadata.isOfItemType(ItemMetadata.ItemType.GROUP)) { throw new IllegalStateException("item " + metadata + " must be a group"); @@ -105,6 +125,13 @@ public ConfigurationMetadata getMetadata() { return metadata; } + private ItemMetadata find(String name) { + return this.metadataItems.stream() + .filter((candidate) -> name.equals(candidate.getName())) + .findFirst() + .orElse(null); + } + private boolean shouldBeMerged(ItemMetadata itemMetadata) { String sourceType = itemMetadata.getSourceType(); return (sourceType != null && !deletedInCurrentBuild(sourceType) && !processedInCurrentBuild(sourceType)); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java index 5109ffed45d6..88751f65c40f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,30 +49,23 @@ * Provide utilities to detect and validate configuration properties. * * @author Stephane Nicoll + * @author Scott Frederick + * @author Moritz Halbritter */ class MetadataGenerationEnvironment { private static final String NULLABLE_ANNOTATION = "org.springframework.lang.Nullable"; - private static final Set TYPE_EXCLUDES; - static { - Set excludes = new HashSet<>(); - excludes.add("com.zaxxer.hikari.IConnectionCustomizer"); - excludes.add("groovy.lang.MetaClass"); - excludes.add("groovy.text.markup.MarkupTemplateEngine"); - excludes.add("java.io.Writer"); - excludes.add("java.io.PrintWriter"); - excludes.add("java.lang.ClassLoader"); - excludes.add("java.util.concurrent.ThreadFactory"); - excludes.add("jakarta.jms.XAConnectionFactory"); - excludes.add("javax.sql.DataSource"); - excludes.add("javax.sql.XADataSource"); - excludes.add("org.apache.tomcat.jdbc.pool.PoolConfiguration"); - excludes.add("org.apache.tomcat.jdbc.pool.Validator"); - excludes.add("org.flywaydb.core.api.callback.FlywayCallback"); - excludes.add("org.flywaydb.core.api.resolver.MigrationResolver"); - TYPE_EXCLUDES = Collections.unmodifiableSet(excludes); - } + private static final Set TYPE_EXCLUDES = Set.of("com.zaxxer.hikari.IConnectionCustomizer", + "groovy.lang.MetaClass", "groovy.text.markup.MarkupTemplateEngine", "java.io.Writer", "java.io.PrintWriter", + "java.lang.ClassLoader", "java.util.concurrent.ThreadFactory", "jakarta.jms.XAConnectionFactory", + "javax.sql.DataSource", "javax.sql.XADataSource", "org.apache.tomcat.jdbc.pool.PoolConfiguration", + "org.apache.tomcat.jdbc.pool.Validator", "org.flywaydb.core.api.callback.FlywayCallback", + "org.flywaydb.core.api.resolver.MigrationResolver"); + + private static final Set DEPRECATION_EXCLUDES = Set.of( + "org.apache.commons.dbcp2.BasicDataSource#getPassword", + "org.apache.commons.dbcp2.BasicDataSource#getUsername"); private final TypeUtils typeUtils; @@ -161,6 +154,13 @@ boolean isExcluded(TypeMirror type) { } boolean isDeprecated(Element element) { + if (element == null) { + return false; + } + String elementName = element.getEnclosingElement() + "#" + element.getSimpleName(); + if (DEPRECATION_EXCLUDES.contains(elementName)) { + return false; + } if (isElementDeprecated(element)) { return true; } @@ -174,14 +174,13 @@ ItemDeprecation resolveItemDeprecation(Element element) { AnnotationMirror annotation = getAnnotation(element, this.deprecatedConfigurationPropertyAnnotation); String reason = null; String replacement = null; + String since = null; if (annotation != null) { - Map elementValues = getAnnotationElementValues(annotation); - reason = (String) elementValues.get("reason"); - replacement = (String) elementValues.get("replacement"); + reason = getAnnotationElementStringValue(annotation, "reason"); + replacement = getAnnotationElementStringValue(annotation, "replacement"); + since = getAnnotationElementStringValue(annotation, "since"); } - reason = (reason == null || reason.isEmpty()) ? null : reason; - replacement = (replacement == null || replacement.isEmpty()) ? null : replacement; - return new ItemDeprecation(reason, replacement); + return new ItemDeprecation(reason, replacement, since); } boolean hasConstructorBindingAnnotation(ExecutableElement element) { @@ -279,6 +278,16 @@ Map getAnnotationElementValues(AnnotationMirror annotation) { return values; } + String getAnnotationElementStringValue(AnnotationMirror annotation, String name) { + return annotation.getElementValues() + .entrySet() + .stream() + .filter((element) -> element.getKey().getSimpleName().toString().equals(name)) + .map((element) -> asString(getAnnotationValue(element.getValue()))) + .findFirst() + .orElse(null); + } + private Object getAnnotationValue(AnnotationValue annotationValue) { Object value = annotationValue.getValue(); if (value instanceof List) { @@ -289,6 +298,10 @@ private Object getAnnotationValue(AnnotationValue annotationValue) { return value; } + private String asString(Object value) { + return (value == null || value.toString().isEmpty()) ? null : (String) value; + } + TypeElement getConfigurationPropertiesAnnotationElement() { return this.elements.getTypeElement(this.configurationPropertiesAnnotation); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ParameterPropertyDescriptor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ParameterPropertyDescriptor.java new file mode 100644 index 000000000000..806f5cc2d549 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ParameterPropertyDescriptor.java @@ -0,0 +1,216 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationprocessor; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.TypeKindVisitor8; +import javax.tools.Diagnostic.Kind; + +/** + * {@link PropertyDescriptor} created from a constructor or record parameter. + * + * @author Stephane Nicoll + * @author Phillip Webb + */ +abstract class ParameterPropertyDescriptor extends PropertyDescriptor { + + private final VariableElement parameter; + + ParameterPropertyDescriptor(String name, TypeMirror type, VariableElement parameter, TypeElement declaringElement, + ExecutableElement getter) { + super(name, type, declaringElement, getter); + this.parameter = parameter; + + } + + final VariableElement getParameter() { + return this.parameter; + } + + @Override + protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) { + Object defaultValue = getDefaultValueFromAnnotation(environment, getParameter()); + return (defaultValue != null) ? defaultValue + : getParameter().asType().accept(DefaultPrimitiveTypeVisitor.INSTANCE, null); + } + + private Object getDefaultValueFromAnnotation(MetadataGenerationEnvironment environment, Element element) { + AnnotationMirror annotation = environment.getDefaultValueAnnotation(element); + List defaultValue = getDefaultValue(environment, annotation); + if (defaultValue != null) { + TypeMirror specificType = determineSpecificType(environment); + try { + List coerced = defaultValue.stream().map((value) -> coerceValue(specificType, value)).toList(); + return (coerced.size() != 1) ? coerced : coerced.get(0); + } + catch (IllegalArgumentException ex) { + environment.getMessager().printMessage(Kind.ERROR, ex.getMessage(), element, annotation); + } + } + return null; + } + + @SuppressWarnings("unchecked") + private List getDefaultValue(MetadataGenerationEnvironment environment, AnnotationMirror annotation) { + if (annotation == null) { + return null; + } + Map values = environment.getAnnotationElementValues(annotation); + return (List) values.get("value"); + } + + private TypeMirror determineSpecificType(MetadataGenerationEnvironment environment) { + TypeMirror parameterType = getParameter().asType(); + TypeMirror elementType = environment.getTypeUtils().extractElementType(parameterType); + parameterType = (elementType != null) ? elementType : parameterType; + PrimitiveType primitiveType = environment.getTypeUtils().getPrimitiveType(parameterType); + return (primitiveType != null) ? primitiveType : parameterType; + } + + private Object coerceValue(TypeMirror type, String value) { + Object coercedValue = type.accept(DefaultValueCoercionTypeVisitor.INSTANCE, value); + return (coercedValue != null) ? coercedValue : value; + } + + @Override + public boolean isProperty(MetadataGenerationEnvironment env) { + return !isNested(env); // We must be able to bind it to build the object. + } + + /** + * Visitor that gets the default value for primitives. + */ + private static final class DefaultPrimitiveTypeVisitor extends TypeKindVisitor8 { + + static final DefaultPrimitiveTypeVisitor INSTANCE = new DefaultPrimitiveTypeVisitor(); + + @Override + public Object visitPrimitiveAsBoolean(PrimitiveType type, Void parameter) { + return false; + } + + @Override + public Object visitPrimitiveAsByte(PrimitiveType type, Void parameter) { + return (byte) 0; + } + + @Override + public Object visitPrimitiveAsShort(PrimitiveType type, Void parameter) { + return (short) 0; + } + + @Override + public Object visitPrimitiveAsInt(PrimitiveType type, Void parameter) { + return 0; + } + + @Override + public Object visitPrimitiveAsLong(PrimitiveType type, Void parameter) { + return 0L; + } + + @Override + public Object visitPrimitiveAsChar(PrimitiveType type, Void parameter) { + return null; + } + + @Override + public Object visitPrimitiveAsFloat(PrimitiveType type, Void parameter) { + return 0F; + } + + @Override + public Object visitPrimitiveAsDouble(PrimitiveType type, Void parameter) { + return 0D; + } + + } + + /** + * Visitor that gets the default using coercion. + */ + private static final class DefaultValueCoercionTypeVisitor extends TypeKindVisitor8 { + + static final DefaultValueCoercionTypeVisitor INSTANCE = new DefaultValueCoercionTypeVisitor(); + + private T parseNumber(String value, Function parser, + PrimitiveType primitiveType) { + try { + return parser.apply(value); + } + catch (NumberFormatException ex) { + throw new IllegalArgumentException( + String.format("Invalid %s representation '%s'", primitiveType, value)); + } + } + + @Override + public Object visitPrimitiveAsBoolean(PrimitiveType type, String value) { + return Boolean.parseBoolean(value); + } + + @Override + public Object visitPrimitiveAsByte(PrimitiveType type, String value) { + return parseNumber(value, Byte::parseByte, type); + } + + @Override + public Object visitPrimitiveAsShort(PrimitiveType type, String value) { + return parseNumber(value, Short::parseShort, type); + } + + @Override + public Object visitPrimitiveAsInt(PrimitiveType type, String value) { + return parseNumber(value, Integer::parseInt, type); + } + + @Override + public Object visitPrimitiveAsLong(PrimitiveType type, String value) { + return parseNumber(value, Long::parseLong, type); + } + + @Override + public Object visitPrimitiveAsChar(PrimitiveType type, String value) { + if (value.length() > 1) { + throw new IllegalArgumentException(String.format("Invalid character representation '%s'", value)); + } + return value; + } + + @Override + public Object visitPrimitiveAsFloat(PrimitiveType type, String value) { + return parseNumber(value, Float::parseFloat, type); + } + + @Override + public Object visitPrimitiveAsDouble(PrimitiveType type, String value) { + return parseNumber(value, Double::parseDouble, type); + } + + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptor.java index 83cc34704225..386f5b2992c0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,12 @@ package org.springframework.boot.configurationprocessor; +import java.util.List; + import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; @@ -30,134 +31,107 @@ /** * Description of a property that can be candidate for metadata generation. * - * @param the type of the source element that determines the property * @author Stephane Nicoll + * @author Phillip Webb */ -abstract class PropertyDescriptor { - - private final TypeElement ownerElement; - - private final ExecutableElement factoryMethod; - - private final S source; +abstract class PropertyDescriptor { private final String name; private final TypeMirror type; - private final VariableElement field; + private final TypeElement declaringElement; private final ExecutableElement getter; - private final ExecutableElement setter; - - protected PropertyDescriptor(TypeElement ownerElement, ExecutableElement factoryMethod, S source, String name, - TypeMirror type, VariableElement field, ExecutableElement getter, ExecutableElement setter) { - this.ownerElement = ownerElement; - this.factoryMethod = factoryMethod; - this.source = source; + /** + * Create a new {@link PropertyDescriptor} instance. + * @param name the property name + * @param type the property type + * @param declaringElement the element that declared the item + * @param getter the getter for the property or {@code null} + */ + PropertyDescriptor(String name, TypeMirror type, TypeElement declaringElement, ExecutableElement getter) { + this.declaringElement = declaringElement; this.name = name; this.type = type; - this.field = field; this.getter = getter; - this.setter = setter; - } - - TypeElement getOwnerElement() { - return this.ownerElement; - } - - ExecutableElement getFactoryMethod() { - return this.factoryMethod; - } - - S getSource() { - return this.source; } + /** + * Return the name of the property. + * @return the property name + */ String getName() { return this.name; } + /** + * Return the type of the property. + * @return the property type + */ TypeMirror getType() { return this.type; } - VariableElement getField() { - return this.field; + /** + * Return the element that declared the property. + * @return the declaring element + */ + protected final TypeElement getDeclaringElement() { + return this.declaringElement; } - ExecutableElement getGetter() { + /** + * Return the getter for the property. + * @return the getter or {@code null} + */ + protected final ExecutableElement getGetter() { return this.getter; } - ExecutableElement getSetter() { - return this.setter; - } - - protected abstract boolean isProperty(MetadataGenerationEnvironment environment); - - protected abstract Object resolveDefaultValue(MetadataGenerationEnvironment environment); - - protected ItemDeprecation resolveItemDeprecation(MetadataGenerationEnvironment environment) { - boolean deprecated = environment.isDeprecated(getGetter()) || environment.isDeprecated(getSetter()) - || environment.isDeprecated(getField()) || environment.isDeprecated(getFactoryMethod()); - return deprecated ? environment.resolveItemDeprecation(getGetter()) : null; - } - - protected boolean isNested(MetadataGenerationEnvironment environment) { - Element typeElement = environment.getTypeUtils().asElement(getType()); - if (!(typeElement instanceof TypeElement) || typeElement.getKind() == ElementKind.ENUM) { - return false; - } - if (environment.getConfigurationPropertiesAnnotation(getGetter()) != null) { - return false; - } - if (environment.getNestedConfigurationPropertyAnnotation(getField()) != null) { - return true; - } - if (isCyclePresent(typeElement, getOwnerElement())) { - return false; - } - return isParentTheSame(environment, typeElement, getOwnerElement()); - } - - ItemMetadata resolveItemMetadata(String prefix, MetadataGenerationEnvironment environment) { + /** + * Resolve the {@link ItemMetadata} for this property. + * @param prefix the property prefix + * @param environment the metadata generation environment + * @return the item metadata or {@code null} + */ + final ItemMetadata resolveItemMetadata(String prefix, MetadataGenerationEnvironment environment) { if (isNested(environment)) { return resolveItemMetadataGroup(prefix, environment); } - else if (isProperty(environment)) { + if (isProperty(environment)) { return resolveItemMetadataProperty(prefix, environment); } return null; } - private ItemMetadata resolveItemMetadataProperty(String prefix, MetadataGenerationEnvironment environment) { - String dataType = resolveType(environment); - String ownerType = environment.getTypeUtils().getQualifiedName(getOwnerElement()); - String description = resolveDescription(environment); - Object defaultValue = resolveDefaultValue(environment); - ItemDeprecation deprecation = resolveItemDeprecation(environment); - return ItemMetadata.newProperty(prefix, getName(), dataType, ownerType, null, description, defaultValue, - deprecation); - } - - private ItemMetadata resolveItemMetadataGroup(String prefix, MetadataGenerationEnvironment environment) { - Element propertyElement = environment.getTypeUtils().asElement(getType()); - String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix, getName()); - String dataType = environment.getTypeUtils().getQualifiedName(propertyElement); - String ownerType = environment.getTypeUtils().getQualifiedName(getOwnerElement()); - String sourceMethod = (getGetter() != null) ? getGetter().toString() : null; - return ItemMetadata.newGroup(nestedPrefix, dataType, ownerType, sourceMethod); - } - - private String resolveType(MetadataGenerationEnvironment environment) { - return environment.getTypeUtils().getType(getOwnerElement(), getType()); + /** + * Return if this is a nested property. + * @param environment the metadata generation environment + * @return if the property is nested + * @see #isMarkedAsNested(MetadataGenerationEnvironment) + */ + boolean isNested(MetadataGenerationEnvironment environment) { + Element typeElement = environment.getTypeUtils().asElement(getType()); + if (!(typeElement instanceof TypeElement) || typeElement.getKind() == ElementKind.ENUM + || environment.getConfigurationPropertiesAnnotation(getGetter()) != null) { + return false; + } + if (isMarkedAsNested(environment)) { + return true; + } + return !isCyclePresent(typeElement, getDeclaringElement()) + && isParentTheSame(environment, typeElement, getDeclaringElement()); } - private String resolveDescription(MetadataGenerationEnvironment environment) { - return environment.getTypeUtils().getJavaDoc(getField()); - } + /** + * Return if this property has been explicitly marked as nested (for example using an + * annotation}. + * @param environment the metadata generation environment + * @return if the property has been marked as nested + */ + protected abstract boolean isMarkedAsNested(MetadataGenerationEnvironment environment); private boolean isCyclePresent(Element returnType, Element element) { if (!(element.getEnclosingElement() instanceof TypeElement)) { @@ -192,4 +166,60 @@ private Element getTopLevelType(Element element) { return getTopLevelType(element.getEnclosingElement()); } + private ItemMetadata resolveItemMetadataGroup(String prefix, MetadataGenerationEnvironment environment) { + Element propertyElement = environment.getTypeUtils().asElement(getType()); + String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix, getName()); + String dataType = environment.getTypeUtils().getQualifiedName(propertyElement); + String ownerType = environment.getTypeUtils().getQualifiedName(getDeclaringElement()); + String sourceMethod = (getGetter() != null) ? getGetter().toString() : null; + return ItemMetadata.newGroup(nestedPrefix, dataType, ownerType, sourceMethod); + } + + private ItemMetadata resolveItemMetadataProperty(String prefix, MetadataGenerationEnvironment environment) { + String dataType = resolveType(environment); + String ownerType = environment.getTypeUtils().getQualifiedName(getDeclaringElement()); + String description = resolveDescription(environment); + Object defaultValue = resolveDefaultValue(environment); + ItemDeprecation deprecation = resolveItemDeprecation(environment); + return ItemMetadata.newProperty(prefix, getName(), dataType, ownerType, null, description, defaultValue, + deprecation); + } + + private String resolveType(MetadataGenerationEnvironment environment) { + return environment.getTypeUtils().getType(getDeclaringElement(), getType()); + } + + private ItemDeprecation resolveItemDeprecation(MetadataGenerationEnvironment environment) { + boolean deprecated = getDeprecatableElements().stream().anyMatch(environment::isDeprecated); + return deprecated ? environment.resolveItemDeprecation(getGetter()) : null; + } + + /** + * Resolve the property description. + * @param environment the metadata generation environment + * @return the property description + */ + protected abstract String resolveDescription(MetadataGenerationEnvironment environment); + + /** + * Resolve the default value for this property. + * @param environment the metadata generation environment + * @return the default value or {@code null} + */ + protected abstract Object resolveDefaultValue(MetadataGenerationEnvironment environment); + + /** + * Return all the elements that should be considered when checking for deprecation + * annotations. + * @return the deprecatable elements + */ + protected abstract List getDeprecatableElements(); + + /** + * Return true if this descriptor is for a property. + * @param environment the metadata generation environment + * @return if this is a property + */ + abstract boolean isProperty(MetadataGenerationEnvironment environment); + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java index 31e5630a9541..51fe3016ec23 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.NestingKind; +import javax.lang.model.element.RecordComponentElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; @@ -36,6 +37,7 @@ * * @author Stephane Nicoll * @author Phillip Webb + * @author Pavel Anisimov */ class PropertyDescriptorResolver { @@ -54,66 +56,74 @@ class PropertyDescriptorResolver { * or {@code null} * @return the candidate properties for metadata generation */ - Stream> resolve(TypeElement type, ExecutableElement factoryMethod) { + Stream resolve(TypeElement type, ExecutableElement factoryMethod) { TypeElementMembers members = new TypeElementMembers(this.environment, type); if (factoryMethod != null) { - return resolveJavaBeanProperties(type, factoryMethod, members); + return resolveJavaBeanProperties(type, members, factoryMethod); } - return resolve(ConfigurationPropertiesTypeElement.of(type, this.environment), members); + return resolve(Bindable.of(type, this.environment), members); } - private Stream> resolve(ConfigurationPropertiesTypeElement type, TypeElementMembers members) { - if (type.isConstructorBindingEnabled()) { - ExecutableElement constructor = type.getBindConstructor(); - if (constructor != null) { - return resolveConstructorProperties(type.getType(), members, constructor); - } - return Stream.empty(); + private Stream resolve(Bindable bindable, TypeElementMembers members) { + if (bindable.isConstructorBindingEnabled()) { + ExecutableElement bindConstructor = bindable.getBindConstructor(); + return (bindConstructor != null) + ? resolveConstructorBoundProperties(bindable.getType(), members, bindConstructor) : Stream.empty(); } - return resolveJavaBeanProperties(type.getType(), null, members); + return resolveJavaBeanProperties(bindable.getType(), members, null); } - Stream> resolveConstructorProperties(TypeElement type, TypeElementMembers members, - ExecutableElement constructor) { - Map> candidates = new LinkedHashMap<>(); - constructor.getParameters().forEach((parameter) -> { - String name = getParameterName(parameter); - TypeMirror propertyType = parameter.asType(); - ExecutableElement getter = members.getPublicGetter(name, propertyType); - ExecutableElement setter = members.getPublicSetter(name, propertyType); - VariableElement field = members.getFields().get(name); - register(candidates, new ConstructorParameterPropertyDescriptor(type, null, parameter, name, propertyType, - field, getter, setter)); + private Stream resolveConstructorBoundProperties(TypeElement declaringElement, + TypeElementMembers members, ExecutableElement bindConstructor) { + Map candidates = new LinkedHashMap<>(); + bindConstructor.getParameters().forEach((parameter) -> { + PropertyDescriptor descriptor = extracted(declaringElement, members, parameter); + register(candidates, descriptor); }); return candidates.values().stream(); } + private PropertyDescriptor extracted(TypeElement declaringElement, TypeElementMembers members, + VariableElement parameter) { + String name = getParameterName(parameter); + TypeMirror type = parameter.asType(); + ExecutableElement getter = members.getPublicGetter(name, type); + ExecutableElement setter = members.getPublicSetter(name, type); + VariableElement field = members.getFields().get(name); + RecordComponentElement recordComponent = members.getRecordComponents().get(name); + return (recordComponent != null) + ? new RecordParameterPropertyDescriptor(name, type, parameter, declaringElement, getter, + recordComponent) + : new ConstructorParameterPropertyDescriptor(name, type, parameter, declaringElement, getter, setter, + field); + } + private String getParameterName(VariableElement parameter) { AnnotationMirror nameAnnotation = this.environment.getNameAnnotation(parameter); if (nameAnnotation != null) { - return (String) this.environment.getAnnotationElementValues(nameAnnotation).get("value"); + return this.environment.getAnnotationElementStringValue(nameAnnotation, "value"); } return parameter.getSimpleName().toString(); } - Stream> resolveJavaBeanProperties(TypeElement type, ExecutableElement factoryMethod, - TypeElementMembers members) { + private Stream resolveJavaBeanProperties(TypeElement declaringElement, + TypeElementMembers members, ExecutableElement factoryMethod) { // First check if we have regular java bean properties there - Map> candidates = new LinkedHashMap<>(); + Map candidates = new LinkedHashMap<>(); members.getPublicGetters().forEach((name, getters) -> { VariableElement field = members.getFields().get(name); ExecutableElement getter = findMatchingGetter(members, getters, field); TypeMirror propertyType = getter.getReturnType(); - register(candidates, new JavaBeanPropertyDescriptor(type, factoryMethod, getter, name, propertyType, field, - members.getPublicSetter(name, propertyType))); + register(candidates, new JavaBeanPropertyDescriptor(name, propertyType, declaringElement, getter, + members.getPublicSetter(name, propertyType), field, factoryMethod)); }); // Then check for Lombok ones members.getFields().forEach((name, field) -> { TypeMirror propertyType = field.asType(); ExecutableElement getter = members.getPublicGetter(name, propertyType); ExecutableElement setter = members.getPublicSetter(name, propertyType); - register(candidates, - new LombokPropertyDescriptor(type, factoryMethod, field, name, propertyType, getter, setter)); + register(candidates, new LombokPropertyDescriptor(name, propertyType, declaringElement, getter, setter, + field, factoryMethod)); }); return candidates.values().stream(); } @@ -126,20 +136,20 @@ private ExecutableElement findMatchingGetter(TypeElementMembers members, List> candidates, PropertyDescriptor descriptor) { + private void register(Map candidates, PropertyDescriptor descriptor) { if (!candidates.containsKey(descriptor.getName()) && isCandidate(descriptor)) { candidates.put(descriptor.getName(), descriptor); } } - private boolean isCandidate(PropertyDescriptor descriptor) { + private boolean isCandidate(PropertyDescriptor descriptor) { return descriptor.isProperty(this.environment) || descriptor.isNested(this.environment); } /** * Wrapper around a {@link TypeElement} that could be bound. */ - private static class ConfigurationPropertiesTypeElement { + private static class Bindable { private final TypeElement type; @@ -147,8 +157,7 @@ private static class ConfigurationPropertiesTypeElement { private final List boundConstructors; - ConfigurationPropertiesTypeElement(TypeElement type, List constructors, - List boundConstructors) { + Bindable(TypeElement type, List constructors, List boundConstructors) { this.type = type; this.constructors = constructors; this.boundConstructors = boundConstructors; @@ -185,10 +194,10 @@ private ExecutableElement findBoundConstructor() { return boundConstructor; } - static ConfigurationPropertiesTypeElement of(TypeElement type, MetadataGenerationEnvironment env) { + static Bindable of(TypeElement type, MetadataGenerationEnvironment env) { List constructors = ElementFilter.constructorsIn(type.getEnclosedElements()); List boundConstructors = getBoundConstructors(type, env, constructors); - return new ConfigurationPropertiesTypeElement(type, constructors, boundConstructors); + return new Bindable(type, constructors, boundConstructors); } private static List getBoundConstructors(TypeElement type, MetadataGenerationEnvironment env, @@ -204,7 +213,7 @@ private static ExecutableElement deduceBindConstructor(TypeElement type, List 0 && !env.hasAutowiredAnnotation(candidate)) { + if (!candidate.getParameters().isEmpty() && !env.hasAutowiredAnnotation(candidate)) { if (type.getNestingKind() == NestingKind.MEMBER && candidate.getModifiers().contains(Modifier.PRIVATE)) { return null; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/RecordParameterPropertyDescriptor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/RecordParameterPropertyDescriptor.java new file mode 100644 index 000000000000..a2ced12c8aba --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/RecordParameterPropertyDescriptor.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationprocessor; + +import java.util.Arrays; +import java.util.List; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.RecordComponentElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + +/** + * A {@link PropertyDescriptor} for a record parameter. + * + * @author Stephane Nicoll + * @author Pavel Anisimov + * @author Phillip Webb + */ +class RecordParameterPropertyDescriptor extends ParameterPropertyDescriptor { + + private final RecordComponentElement recordComponent; + + RecordParameterPropertyDescriptor(String name, TypeMirror type, VariableElement parameter, + TypeElement declaringElement, ExecutableElement getter, RecordComponentElement recordComponent) { + super(name, type, parameter, declaringElement, getter); + this.recordComponent = recordComponent; + } + + @Override + protected List getDeprecatableElements() { + return Arrays.asList(getGetter()); + } + + @Override + protected boolean isMarkedAsNested(MetadataGenerationEnvironment environment) { + return environment.getNestedConfigurationPropertyAnnotation(this.recordComponent) != null; + } + + @Override + protected String resolveDescription(MetadataGenerationEnvironment environment) { + return environment.getTypeUtils().getJavaDoc(this.recordComponent); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeElementMembers.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeElementMembers.java index d7b073e7110a..5aa3b921d60f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeElementMembers.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeElementMembers.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; +import javax.lang.model.element.RecordComponentElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeKind; @@ -39,12 +40,13 @@ * @author Stephane Nicoll * @author Phillip Webb * @author Moritz Halbritter + * @author Pavel Anisimov */ class TypeElementMembers { private static final String OBJECT_CLASS_NAME = Object.class.getName(); - private static final String RECORD_CLASS_NAME = "java.lang.Record"; + private static final String RECORD_CLASS_NAME = Record.class.getName(); private final MetadataGenerationEnvironment env; @@ -54,6 +56,8 @@ class TypeElementMembers { private final Map fields = new LinkedHashMap<>(); + private final Map recordComponents = new LinkedHashMap<>(); + private final Map> publicGetters = new LinkedHashMap<>(); private final Map> publicSetters = new LinkedHashMap<>(); @@ -69,6 +73,9 @@ private void process(TypeElement element) { for (VariableElement field : ElementFilter.fieldsIn(element.getEnclosedElements())) { processField(field); } + for (RecordComponentElement recordComponent : ElementFilter.recordComponentsIn(element.getEnclosedElements())) { + processRecordComponent(recordComponent); + } for (ExecutableElement method : ElementFilter.methodsIn(element.getEnclosedElements())) { processMethod(method); } @@ -189,10 +196,19 @@ private void processField(VariableElement field) { this.fields.putIfAbsent(name, field); } + private void processRecordComponent(RecordComponentElement recordComponent) { + String name = recordComponent.getSimpleName().toString(); + this.recordComponents.putIfAbsent(name, recordComponent); + } + Map getFields() { return Collections.unmodifiableMap(this.fields); } + Map getRecordComponents() { + return Collections.unmodifiableMap(this.recordComponents); + } + Map> getPublicGetters() { return Collections.unmodifiableMap(this.publicGetters); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java index 7f94f1667ba9..d5096cb33dd5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,11 +24,13 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; +import javax.lang.model.element.RecordComponentElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; @@ -44,6 +46,7 @@ * * @author Stephane Nicoll * @author Phillip Webb + * @author Pavel Anisimov */ class TypeUtils { @@ -64,8 +67,6 @@ class TypeUtils { private static final Map WRAPPER_TO_PRIMITIVE; - private static final Pattern NEW_LINE_PATTERN = Pattern.compile("[\r\n]+"); - static { Map primitives = new HashMap<>(); PRIMITIVE_WRAPPERS.forEach((kind, wrapperClass) -> primitives.put(wrapperClass.getName(), kind)); @@ -176,10 +177,11 @@ boolean isCollectionOrMap(TypeMirror type) { } String getJavaDoc(Element element) { - String javadoc = (element != null) ? this.env.getElementUtils().getDocComment(element) : null; - if (javadoc != null) { - javadoc = NEW_LINE_PATTERN.matcher(javadoc).replaceAll("").trim(); + if (element instanceof RecordComponentElement) { + return getJavaDoc((RecordComponentElement) element); } + String javadoc = (element != null) ? this.env.getElementUtils().getDocComment(element) : null; + javadoc = (javadoc != null) ? cleanUpJavaDoc(javadoc) : null; return (javadoc == null || javadoc.isEmpty()) ? null : javadoc; } @@ -247,6 +249,38 @@ private void process(TypeDescriptor descriptor, TypeMirror type) { } } + private String getJavaDoc(RecordComponentElement recordComponent) { + String recordJavadoc = this.env.getElementUtils().getDocComment(recordComponent.getEnclosingElement()); + if (recordJavadoc != null) { + Pattern paramJavadocPattern = paramJavadocPattern(recordComponent.getSimpleName().toString()); + Matcher paramJavadocMatcher = paramJavadocPattern.matcher(recordJavadoc); + if (paramJavadocMatcher.find()) { + String paramJavadoc = cleanUpJavaDoc(paramJavadocMatcher.group()); + return paramJavadoc.isEmpty() ? null : paramJavadoc; + } + } + return null; + } + + private Pattern paramJavadocPattern(String paramName) { + String pattern = String.format("(?<=@param +%s).*?(?=([\r\n]+ *@)|$)", paramName); + return Pattern.compile(pattern, Pattern.DOTALL); + } + + private String cleanUpJavaDoc(String javadoc) { + StringBuilder result = new StringBuilder(javadoc.length()); + char lastChar = '.'; + for (int i = 0; i < javadoc.length(); i++) { + char ch = javadoc.charAt(i); + boolean repeatedSpace = ch == ' ' && lastChar == ' '; + if (ch != '\r' && ch != '\n' && !repeatedSpace) { + result.append(ch); + lastChar = ch; + } + } + return result.toString().trim(); + } + /** * A visitor that extracts the fully qualified name of a type, including generic * information. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ConfigurationMetadata.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ConfigurationMetadata.java index 1281954f591c..a062ba4ca90f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ConfigurationMetadata.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ConfigurationMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -136,6 +136,9 @@ protected void mergeItemMetadata(ItemMetadata metadata) { if (deprecation.getLevel() != null) { matchingDeprecation.setLevel(deprecation.getLevel()); } + if (deprecation.getSince() != null) { + matchingDeprecation.setSince(deprecation.getSince()); + } } } } @@ -182,7 +185,7 @@ private boolean nullSafeEquals(Object o1, Object o2) { public static String nestedPrefix(String prefix, String name) { String nestedPrefix = (prefix != null) ? prefix : ""; String dashedName = toDashedCase(name); - nestedPrefix += (nestedPrefix == null || nestedPrefix.isEmpty()) ? dashedName : "." + dashedName; + nestedPrefix += nestedPrefix.isEmpty() ? dashedName : "." + dashedName; return nestedPrefix; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemDeprecation.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemDeprecation.java index 2947e94f1554..e684edf73c6f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemDeprecation.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemDeprecation.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ * Describe an item deprecation. * * @author Stephane Nicoll + * @author Scott Frederick * @since 1.3.0 */ public class ItemDeprecation { @@ -28,19 +29,22 @@ public class ItemDeprecation { private String replacement; + private String since; + private String level; public ItemDeprecation() { - this(null, null); + this(null, null, null); } - public ItemDeprecation(String reason, String replacement) { - this(reason, replacement, null); + public ItemDeprecation(String reason, String replacement, String since) { + this(reason, replacement, since, null); } - public ItemDeprecation(String reason, String replacement, String level) { + public ItemDeprecation(String reason, String replacement, String since, String level) { this.reason = reason; this.replacement = replacement; + this.since = since; this.level = level; } @@ -60,6 +64,14 @@ public void setReplacement(String replacement) { this.replacement = replacement; } + public String getSince() { + return this.since; + } + + public void setSince(String since) { + this.since = since; + } + public String getLevel() { return this.level; } @@ -78,7 +90,7 @@ public boolean equals(Object o) { } ItemDeprecation other = (ItemDeprecation) o; return nullSafeEquals(this.reason, other.reason) && nullSafeEquals(this.replacement, other.replacement) - && nullSafeEquals(this.level, other.level); + && nullSafeEquals(this.level, other.level) && nullSafeEquals(this.since, other.since); } @Override @@ -86,13 +98,14 @@ public int hashCode() { int result = nullSafeHashCode(this.reason); result = 31 * result + nullSafeHashCode(this.replacement); result = 31 * result + nullSafeHashCode(this.level); + result = 31 * result + nullSafeHashCode(this.since); return result; } @Override public String toString() { return "ItemDeprecation{reason='" + this.reason + '\'' + ", replacement='" + this.replacement + '\'' - + ", level='" + this.level + '\'' + '}'; + + ", level='" + this.level + '\'' + ", since='" + this.since + '\'' + '}'; } private boolean nullSafeEquals(Object o1, Object o2) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemMetadata.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemMetadata.java index f2f1e7e5e2fb..70ec0f3dc771 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemMetadata.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -196,7 +196,7 @@ public String toString() { return string.toString(); } - protected void buildToStringProperty(StringBuilder string, String property, Object value) { + private void buildToStringProperty(StringBuilder string, String property, Object value) { if (value != null) { string.append(" ").append(property).append(":").append(value); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonConverter.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonConverter.java index af740a80c24b..bd6239e19df1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonConverter.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonConverter.java @@ -83,6 +83,9 @@ JSONObject toJsonObject(ItemMetadata item) throws Exception { if (deprecation.getReplacement() != null) { deprecationJsonObject.put("replacement", deprecation.getReplacement()); } + if (deprecation.getSince() != null) { + deprecationJsonObject.put("since", deprecation.getSince()); + } jsonObject.put("deprecation", deprecationJsonObject); } return jsonObject; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshaller.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshaller.java index 53370badb6da..e7d6e84e8a26 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshaller.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshaller.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,30 +18,31 @@ import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import org.springframework.boot.configurationprocessor.json.JSONArray; import org.springframework.boot.configurationprocessor.json.JSONObject; import org.springframework.boot.configurationprocessor.metadata.ItemMetadata.ItemType; /** - * Marshaller to write {@link ConfigurationMetadata} as JSON. + * Marshaller to read and write {@link ConfigurationMetadata} as JSON. * * @author Stephane Nicoll * @author Phillip Webb + * @author Moritz Halbritter * @since 1.2.0 */ public class JsonMarshaller { - private static final int BUFFER_SIZE = 4098; - public void write(ConfigurationMetadata metadata, OutputStream outputStream) throws IOException { try { JSONObject object = new JSONObject(); @@ -65,77 +66,92 @@ public void write(ConfigurationMetadata metadata, OutputStream outputStream) thr public ConfigurationMetadata read(InputStream inputStream) throws Exception { ConfigurationMetadata metadata = new ConfigurationMetadata(); JSONObject object = new JSONObject(toString(inputStream)); + JsonPath path = JsonPath.root(); + checkAllowedKeys(object, path, "groups", "properties", "hints"); JSONArray groups = object.optJSONArray("groups"); if (groups != null) { for (int i = 0; i < groups.length(); i++) { - metadata.add(toItemMetadata((JSONObject) groups.get(i), ItemType.GROUP)); + metadata + .add(toItemMetadata((JSONObject) groups.get(i), path.resolve("groups").index(i), ItemType.GROUP)); } } JSONArray properties = object.optJSONArray("properties"); if (properties != null) { for (int i = 0; i < properties.length(); i++) { - metadata.add(toItemMetadata((JSONObject) properties.get(i), ItemType.PROPERTY)); + metadata.add(toItemMetadata((JSONObject) properties.get(i), path.resolve("properties").index(i), + ItemType.PROPERTY)); } } JSONArray hints = object.optJSONArray("hints"); if (hints != null) { for (int i = 0; i < hints.length(); i++) { - metadata.add(toItemHint((JSONObject) hints.get(i))); + metadata.add(toItemHint((JSONObject) hints.get(i), path.resolve("hints").index(i))); } } return metadata; } - private ItemMetadata toItemMetadata(JSONObject object, ItemType itemType) throws Exception { + private ItemMetadata toItemMetadata(JSONObject object, JsonPath path, ItemType itemType) throws Exception { + switch (itemType) { + case GROUP -> checkAllowedKeys(object, path, "name", "type", "description", "sourceType", "sourceMethod"); + case PROPERTY -> checkAllowedKeys(object, path, "name", "type", "description", "sourceType", "defaultValue", + "deprecation", "deprecated"); + } String name = object.getString("name"); String type = object.optString("type", null); String description = object.optString("description", null); String sourceType = object.optString("sourceType", null); String sourceMethod = object.optString("sourceMethod", null); Object defaultValue = readItemValue(object.opt("defaultValue")); - ItemDeprecation deprecation = toItemDeprecation(object); + ItemDeprecation deprecation = toItemDeprecation(object, path); return new ItemMetadata(itemType, name, null, type, sourceType, sourceMethod, description, defaultValue, deprecation); } - private ItemDeprecation toItemDeprecation(JSONObject object) throws Exception { + private ItemDeprecation toItemDeprecation(JSONObject object, JsonPath path) throws Exception { if (object.has("deprecation")) { JSONObject deprecationJsonObject = object.getJSONObject("deprecation"); + checkAllowedKeys(deprecationJsonObject, path.resolve("deprecation"), "level", "reason", "replacement", + "since"); ItemDeprecation deprecation = new ItemDeprecation(); deprecation.setLevel(deprecationJsonObject.optString("level", null)); deprecation.setReason(deprecationJsonObject.optString("reason", null)); deprecation.setReplacement(deprecationJsonObject.optString("replacement", null)); + deprecation.setSince(deprecationJsonObject.optString("since", null)); return deprecation; } return object.optBoolean("deprecated") ? new ItemDeprecation() : null; } - private ItemHint toItemHint(JSONObject object) throws Exception { + private ItemHint toItemHint(JSONObject object, JsonPath path) throws Exception { + checkAllowedKeys(object, path, "name", "values", "providers"); String name = object.getString("name"); List values = new ArrayList<>(); if (object.has("values")) { JSONArray valuesArray = object.getJSONArray("values"); for (int i = 0; i < valuesArray.length(); i++) { - values.add(toValueHint((JSONObject) valuesArray.get(i))); + values.add(toValueHint((JSONObject) valuesArray.get(i), path.resolve("values").index(i))); } } List providers = new ArrayList<>(); if (object.has("providers")) { JSONArray providersObject = object.getJSONArray("providers"); for (int i = 0; i < providersObject.length(); i++) { - providers.add(toValueProvider((JSONObject) providersObject.get(i))); + providers.add(toValueProvider((JSONObject) providersObject.get(i), path.resolve("providers").index(i))); } } return new ItemHint(name, values, providers); } - private ItemHint.ValueHint toValueHint(JSONObject object) throws Exception { + private ItemHint.ValueHint toValueHint(JSONObject object, JsonPath path) throws Exception { + checkAllowedKeys(object, path, "value", "description"); Object value = readItemValue(object.get("value")); String description = object.optString("description", null); return new ItemHint.ValueHint(value, description); } - private ItemHint.ValueProvider toValueProvider(JSONObject object) throws Exception { + private ItemHint.ValueProvider toValueProvider(JSONObject object, JsonPath path) throws Exception { + checkAllowedKeys(object, path, "name", "parameters"); String name = object.getString("name"); Map parameters = new HashMap<>(); if (object.has("parameters")) { @@ -161,14 +177,48 @@ private Object readItemValue(Object value) throws Exception { } private String toString(InputStream inputStream) throws IOException { - StringBuilder out = new StringBuilder(); - InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); - char[] buffer = new char[BUFFER_SIZE]; - int bytesRead; - while ((bytesRead = reader.read(buffer)) != -1) { - out.append(buffer, 0, bytesRead); - } - return out.toString(); + return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + } + + @SuppressWarnings("unchecked") + private void checkAllowedKeys(JSONObject object, JsonPath path, String... allowedKeys) { + Set availableKeys = new TreeSet<>(); + object.keys().forEachRemaining((key) -> availableKeys.add((String) key)); + Arrays.stream(allowedKeys).forEach(availableKeys::remove); + if (!availableKeys.isEmpty()) { + throw new IllegalStateException("Expected only keys %s, but found additional keys %s. Path: %s" + .formatted(new TreeSet<>(Arrays.asList(allowedKeys)), availableKeys, path)); + } + } + + private static final class JsonPath { + + private final String path; + + private JsonPath(String path) { + this.path = path; + } + + JsonPath resolve(String path) { + if (this.path.endsWith(".")) { + return new JsonPath(this.path + path); + } + return new JsonPath(this.path + "." + path); + } + + JsonPath index(int index) { + return resolve("[%d]".formatted(index)); + } + + @Override + public String toString() { + return this.path; + } + + static JsonPath root() { + return new JsonPath("."); + } + } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java index 93227ecf6196..070cc69bbd7c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,10 @@ import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; import org.springframework.boot.configurationprocessor.metadata.ItemMetadata; import org.springframework.boot.configurationprocessor.metadata.Metadata; +import org.springframework.boot.configurationsample.deprecation.Dbcp2Configuration; +import org.springframework.boot.configurationsample.method.NestedPropertiesMethod; +import org.springframework.boot.configurationsample.record.ExampleRecord; +import org.springframework.boot.configurationsample.record.NestedPropertiesRecord; import org.springframework.boot.configurationsample.record.RecordWithGetter; import org.springframework.boot.configurationsample.recursive.RecursiveProperties; import org.springframework.boot.configurationsample.simple.ClassWithNestedProperties; @@ -42,6 +46,7 @@ import org.springframework.boot.configurationsample.specific.BoxingPojo; import org.springframework.boot.configurationsample.specific.BuilderPojo; import org.springframework.boot.configurationsample.specific.DeprecatedLessPreciseTypePojo; +import org.springframework.boot.configurationsample.specific.DeprecatedSimplePojo; import org.springframework.boot.configurationsample.specific.DeprecatedUnrelatedMethodPojo; import org.springframework.boot.configurationsample.specific.DoubleRegistrationProperties; import org.springframework.boot.configurationsample.specific.EmptyDefaultValueProperties; @@ -104,12 +109,12 @@ void simpleProperties() { .fromSource(SimpleProperties.class) .withDescription("The name of this simple properties.") .withDefaultValue("boot") - .withDeprecation(null, null)); + .withDeprecation()); assertThat(metadata).has(Metadata.withProperty("simple.flag", Boolean.class) .withDefaultValue(false) .fromSource(SimpleProperties.class) .withDescription("A simple flag.") - .withDeprecation(null, null)); + .withDeprecation()); assertThat(metadata).has(Metadata.withProperty("simple.comparator")); assertThat(metadata).doesNotHave(Metadata.withProperty("simple.counter")); assertThat(metadata).doesNotHave(Metadata.withProperty("simple.size")); @@ -188,10 +193,9 @@ void deprecatedProperties() { ConfigurationMetadata metadata = compile(type); assertThat(metadata).has(Metadata.withGroup("deprecated").fromSource(type)); assertThat(metadata) - .has(Metadata.withProperty("deprecated.name", String.class).fromSource(type).withDeprecation(null, null)); - assertThat(metadata).has(Metadata.withProperty("deprecated.description", String.class) - .fromSource(type) - .withDeprecation(null, null)); + .has(Metadata.withProperty("deprecated.name", String.class).fromSource(type).withDeprecation()); + assertThat(metadata) + .has(Metadata.withProperty("deprecated.description", String.class).fromSource(type).withDeprecation()); } @Test @@ -202,7 +206,7 @@ void singleDeprecatedProperty() { assertThat(metadata).has(Metadata.withProperty("singledeprecated.new-name", String.class).fromSource(type)); assertThat(metadata).has(Metadata.withProperty("singledeprecated.name", String.class) .fromSource(type) - .withDeprecation("renamed", "singledeprecated.new-name")); + .withDeprecation("renamed", "singledeprecated.new-name", "1.2.3")); } @Test @@ -210,9 +214,8 @@ void singleDeprecatedFieldProperty() { Class type = DeprecatedFieldSingleProperty.class; ConfigurationMetadata metadata = compile(type); assertThat(metadata).has(Metadata.withGroup("singlefielddeprecated").fromSource(type)); - assertThat(metadata).has(Metadata.withProperty("singlefielddeprecated.name", String.class) - .fromSource(type) - .withDeprecation(null, null)); + assertThat(metadata) + .has(Metadata.withProperty("singlefielddeprecated.name", String.class).fromSource(type).withDeprecation()); } @Test @@ -246,7 +249,7 @@ void deprecatedPropertyOnRecord() { assertThat(metadata).has(Metadata.withGroup("deprecated-record").fromSource(type)); assertThat(metadata).has(Metadata.withProperty("deprecated-record.alpha", String.class) .fromSource(type) - .withDeprecation("some-reason", null)); + .withDeprecation("some-reason", null, null)); assertThat(metadata).has(Metadata.withProperty("deprecated-record.bravo", String.class).fromSource(type)); } @@ -335,6 +338,10 @@ void innerClassProperties() { assertThat(metadata).has(Metadata.withProperty("config.third.value")); assertThat(metadata).has(Metadata.withProperty("config.fourth")); assertThat(metadata).isNotEqualTo(Metadata.withGroup("config.fourth")); + assertThat(metadata).has(Metadata.withGroup("config.fifth") + .ofType(DeprecatedSimplePojo.class) + .fromSource(InnerClassProperties.class)); + assertThat(metadata).has(Metadata.withProperty("config.fifth.value").withDeprecation()); } @Test @@ -357,6 +364,15 @@ void innerClassAnnotatedGetterConfig() { assertThat(metadata).isNotEqualTo(Metadata.withProperty("specific.foo")); } + @Test + void nestedClassMethod() { + ConfigurationMetadata metadata = compile(NestedPropertiesMethod.class); + assertThat(metadata).has(Metadata.withGroup("method-nested.nested")); + assertThat(metadata).has(Metadata.withProperty("method-nested.nested.my-nested-property")); + assertThat(metadata).has(Metadata.withGroup("method-nested.inner.nested")); + assertThat(metadata).has(Metadata.withProperty("method-nested.inner.nested.my-nested-property")); + } + @Test void nestedClassChildProperties() { ConfigurationMetadata metadata = compile(ClassWithNestedProperties.class); @@ -510,4 +526,35 @@ void recordWithGetter() { assertThat(metadata).doesNotHave(Metadata.withProperty("record-with-getter.bravo")); } + @Test + void recordNested() { + ConfigurationMetadata metadata = compile(NestedPropertiesRecord.class); + assertThat(metadata).has(Metadata.withGroup("record-nested.nested")); + assertThat(metadata).has(Metadata.withProperty("record-nested.nested.my-nested-property")); + assertThat(metadata).has(Metadata.withGroup("record-nested.inner.nested")); + assertThat(metadata).has(Metadata.withProperty("record-nested.inner.nested.my-nested-property")); + } + + @Test + void shouldNotMarkDbcp2UsernameOrPasswordAsDeprecated() { + ConfigurationMetadata metadata = compile(Dbcp2Configuration.class); + assertThat(metadata).has(Metadata.withProperty("spring.datasource.dbcp2.username").withNoDeprecation()); + assertThat(metadata).has(Metadata.withProperty("spring.datasource.dbcp2.password").withNoDeprecation()); + } + + @Test + void recordPropertiesWithDescriptions() { + ConfigurationMetadata metadata = compile(ExampleRecord.class); + assertThat(metadata).has(Metadata.withProperty("record.descriptions.some-string", String.class) + .withDescription("very long description that doesn't fit single line and is indented")); + assertThat(metadata).has(Metadata.withProperty("record.descriptions.some-integer", Integer.class) + .withDescription("description with @param and @ pitfalls")); + assertThat(metadata).has(Metadata.withProperty("record.descriptions.some-boolean", Boolean.class) + .withDescription("description with extra spaces")); + assertThat(metadata).has(Metadata.withProperty("record.descriptions.some-long", Long.class) + .withDescription("description without space after asterisk")); + assertThat(metadata).has(Metadata.withProperty("record.descriptions.some-byte", Byte.class) + .withDescription("last description in Javadoc")); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptorTests.java index 01284252bad5..4faed490cd08 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,7 +48,7 @@ void constructorParameterSimpleProperty() { TypeElement ownerElement = roundEnv.getRootElement(ImmutableSimpleProperties.class); ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(ownerElement, "theName"); assertThat(property.getName()).isEqualTo("theName"); - assertThat(property.getSource()).hasToString("theName"); + assertThat(property.getParameter()).hasToString("theName"); assertThat(property.getGetter().getSimpleName()).hasToString("getTheName"); assertThat(property.isProperty(metadataEnv)).isTrue(); assertThat(property.isNested(metadataEnv)).isFalse(); @@ -61,7 +61,7 @@ void constructorParameterNestedPropertySameClass() { TypeElement ownerElement = roundEnv.getRootElement(ImmutableInnerClassProperties.class); ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(ownerElement, "first"); assertThat(property.getName()).isEqualTo("first"); - assertThat(property.getSource()).hasToString("first"); + assertThat(property.getParameter()).hasToString("first"); assertThat(property.getGetter().getSimpleName()).hasToString("getFirst"); assertThat(property.isProperty(metadataEnv)).isFalse(); assertThat(property.isNested(metadataEnv)).isTrue(); @@ -74,7 +74,7 @@ void constructorParameterNestedPropertyWithAnnotation() { TypeElement ownerElement = roundEnv.getRootElement(ImmutableInnerClassProperties.class); ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(ownerElement, "third"); assertThat(property.getName()).isEqualTo("third"); - assertThat(property.getSource()).hasToString("third"); + assertThat(property.getParameter()).hasToString("third"); assertThat(property.getGetter().getSimpleName()).hasToString("getThird"); assertThat(property.isProperty(metadataEnv)).isFalse(); assertThat(property.isNested(metadataEnv)).isTrue(); @@ -87,7 +87,7 @@ void constructorParameterSimplePropertyWithNoAccessorShouldBeExposed() { TypeElement ownerElement = roundEnv.getRootElement(ImmutableSimpleProperties.class); ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(ownerElement, "counter"); assertThat(property.getName()).isEqualTo("counter"); - assertThat(property.getSource()).hasToString("counter"); + assertThat(property.getParameter()).hasToString("counter"); assertThat(property.getGetter()).isNull(); assertThat(property.isProperty(metadataEnv)).isTrue(); assertThat(property.isNested(metadataEnv)).isFalse(); @@ -130,8 +130,8 @@ void constructorParameterDeprecatedPropertyOnGetter() { ExecutableElement getter = getMethod(ownerElement, "isFlag"); VariableElement field = getField(ownerElement, "flag"); VariableElement constructorParameter = getConstructorParameter(ownerElement, "flag"); - ConstructorParameterPropertyDescriptor property = new ConstructorParameterPropertyDescriptor(ownerElement, - null, constructorParameter, "flag", field.asType(), field, getter, null); + ConstructorParameterPropertyDescriptor property = new ConstructorParameterPropertyDescriptor("flag", + field.asType(), constructorParameter, ownerElement, getter, null, field); assertItemMetadata(metadataEnv, property).isProperty().isDeprecatedWithNoInformation(); }); } @@ -222,8 +222,8 @@ protected ConstructorParameterPropertyDescriptor createPropertyDescriptor(TypeEl VariableElement field = getField(ownerElement, name); ExecutableElement getter = getMethod(ownerElement, createAccessorMethodName("get", name)); ExecutableElement setter = getMethod(ownerElement, createAccessorMethodName("set", name)); - return new ConstructorParameterPropertyDescriptor(ownerElement, null, constructorParameter, name, - field.asType(), field, getter, setter); + return new ConstructorParameterPropertyDescriptor(name, field.asType(), constructorParameter, ownerElement, + getter, setter, field); } private VariableElement getConstructorParameter(TypeElement ownerElement, String name) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/EndpointMetadataGenerationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/EndpointMetadataGenerationTests.java index 4c77c1794b66..e9d88b28ba09 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/EndpointMetadataGenerationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/EndpointMetadataGenerationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,16 +27,20 @@ import org.springframework.boot.configurationsample.endpoint.DisabledEndpoint; import org.springframework.boot.configurationsample.endpoint.EnabledEndpoint; import org.springframework.boot.configurationsample.endpoint.SimpleEndpoint; +import org.springframework.boot.configurationsample.endpoint.SimpleEndpoint2; +import org.springframework.boot.configurationsample.endpoint.SimpleEndpoint3; import org.springframework.boot.configurationsample.endpoint.SpecificEndpoint; import org.springframework.boot.configurationsample.endpoint.incremental.IncrementalEndpoint; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatRuntimeException; /** * Metadata generation tests for Actuator endpoints. * * @author Stephane Nicoll * @author Scott Frederick + * @author Moritz Halbritter */ class EndpointMetadataGenerationTests extends AbstractMetadataGenerationTests { @@ -148,6 +152,24 @@ void incrementalEndpointBuildEnableSpecificEndpoint() { assertThat(metadata.getItems()).hasSize(3); } + @Test + void shouldTolerateEndpointWithSameId() { + ConfigurationMetadata metadata = compile(SimpleEndpoint.class, SimpleEndpoint2.class); + assertThat(metadata).has(Metadata.withGroup("management.endpoint.simple").fromSource(SimpleEndpoint.class)); + assertThat(metadata).has(enabledFlag("simple", "simple", true)); + assertThat(metadata).has(cacheTtl("simple")); + assertThat(metadata.getItems()).hasSize(3); + } + + @Test + void shouldFailIfEndpointWithSameIdButWithConflictingEnabledByDefaultSetting() { + assertThatRuntimeException().isThrownBy(() -> compile(SimpleEndpoint.class, SimpleEndpoint3.class)) + .havingRootCause() + .isInstanceOf(IllegalStateException.class) + .withMessage( + "Existing property 'management.endpoint.simple.enabled' from type org.springframework.boot.configurationsample.endpoint.SimpleEndpoint has a conflicting value. Existing value: true, new value from type org.springframework.boot.configurationsample.endpoint.SimpleEndpoint3: false"); + } + private Metadata.MetadataItemCondition enabledFlag(String endpointId, String endpointSuffix, Boolean defaultValue) { return Metadata.withEnabledFlag("management.endpoint." + endpointSuffix + ".enabled") .withDefaultValue(defaultValue) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ImmutablePropertiesMetadataGenerationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ImmutablePropertiesMetadataGenerationTests.java index 6e3571539ff3..5b87c0137ffe 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ImmutablePropertiesMetadataGenerationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ImmutablePropertiesMetadataGenerationTests.java @@ -43,7 +43,7 @@ void immutableSimpleProperties() { .withDefaultValue(false) .fromSource(ImmutableSimpleProperties.class) .withDescription("A simple flag.") - .withDeprecation(null, null)); + .withDeprecation()); assertThat(metadata).has(Metadata.withProperty("immutable.comparator")); assertThat(metadata).has(Metadata.withProperty("immutable.counter")); assertThat(metadata.getItems()).hasSize(5); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/JavaBeanPropertyDescriptorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/JavaBeanPropertyDescriptorTests.java index d763c13ca422..5c1f2b74f03b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/JavaBeanPropertyDescriptorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/JavaBeanPropertyDescriptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,6 @@ void javaBeanSimpleProperty() { TypeElement ownerElement = roundEnv.getRootElement(SimpleTypeProperties.class); JavaBeanPropertyDescriptor property = createPropertyDescriptor(ownerElement, "myString"); assertThat(property.getName()).isEqualTo("myString"); - assertThat(property.getSource()).isSameAs(property.getGetter()); assertThat(property.getGetter().getSimpleName()).hasToString("getMyString"); assertThat(property.getSetter().getSimpleName()).hasToString("setMyString"); assertThat(property.isProperty(metadataEnv)).isTrue(); @@ -96,10 +95,9 @@ void javaBeanSimplePropertyWithOnlyGetterShouldNotBeExposed() { TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class); ExecutableElement getter = getMethod(ownerElement, "getSize"); VariableElement field = getField(ownerElement, "size"); - JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor(ownerElement, getter, getter, "size", - field.asType(), field, null); + JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor("size", field.asType(), ownerElement, + getter, null, field, getter); assertThat(property.getName()).isEqualTo("size"); - assertThat(property.getSource()).isSameAs(property.getGetter()); assertThat(property.getGetter().getSimpleName()).hasToString("getSize"); assertThat(property.getSetter()).isNull(); assertThat(property.isProperty(metadataEnv)).isFalse(); @@ -112,10 +110,9 @@ void javaBeanSimplePropertyWithOnlySetterShouldNotBeExposed() { process(SimpleProperties.class, (roundEnv, metadataEnv) -> { TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class); VariableElement field = getField(ownerElement, "counter"); - JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor(ownerElement, null, null, "counter", - field.asType(), field, getMethod(ownerElement, "setCounter")); + JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor("counter", field.asType(), + ownerElement, null, getMethod(ownerElement, "setCounter"), field, null); assertThat(property.getName()).isEqualTo("counter"); - assertThat(property.getSource()).isSameAs(property.getGetter()); assertThat(property.getGetter()).isNull(); assertThat(property.getSetter().getSimpleName()).hasToString("setCounter"); assertThat(property.isProperty(metadataEnv)).isFalse(); @@ -171,8 +168,8 @@ void javaBeanMetadataNotACandidatePropertyShouldReturnNull() { process(SimpleProperties.class, (roundEnv, metadataEnv) -> { TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class); VariableElement field = getField(ownerElement, "counter"); - JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor(ownerElement, null, null, "counter", - field.asType(), field, getMethod(ownerElement, "setCounter")); + JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor("counter", field.asType(), + ownerElement, null, getMethod(ownerElement, "setCounter"), field, null); assertThat(property.resolveItemMetadata("test", metadataEnv)).isNull(); }); } @@ -247,7 +244,7 @@ protected JavaBeanPropertyDescriptor createPropertyDescriptor(TypeElement ownerE ExecutableElement getter = getMethod(ownerElement, getterName); ExecutableElement setter = getMethod(ownerElement, setterName); VariableElement field = getField(ownerElement, name); - return new JavaBeanPropertyDescriptor(ownerElement, null, getter, name, getter.getReturnType(), field, setter); + return new JavaBeanPropertyDescriptor(name, getter.getReturnType(), ownerElement, getter, setter, field, null); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/LombokMetadataGenerationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/LombokMetadataGenerationTests.java index 69f0bb871842..2721f0c28a52 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/LombokMetadataGenerationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/LombokMetadataGenerationTests.java @@ -139,10 +139,8 @@ private void assertSimpleLombokProperties(ConfigurationMetadata metadata, Class< .withDescription("Name description.")); assertThat(metadata).has(Metadata.withProperty(prefix + ".description")); assertThat(metadata).has(Metadata.withProperty(prefix + ".counter")); - assertThat(metadata).has(Metadata.withProperty(prefix + ".number") - .fromSource(source) - .withDefaultValue(0) - .withDeprecation(null, null)); + assertThat(metadata) + .has(Metadata.withProperty(prefix + ".number").fromSource(source).withDefaultValue(0).withDeprecation()); assertThat(metadata).has(Metadata.withProperty(prefix + ".items")); assertThat(metadata).doesNotHave(Metadata.withProperty(prefix + ".ignored")); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/LombokPropertyDescriptorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/LombokPropertyDescriptorTests.java index 99f260cdf1dd..ccde69102bd4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/LombokPropertyDescriptorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/LombokPropertyDescriptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,6 @@ void lombokSimpleProperty() { TypeElement ownerElement = roundEnv.getRootElement(LombokSimpleProperties.class); LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, "name"); assertThat(property.getName()).isEqualTo("name"); - assertThat(property.getSource()).isSameAs(property.getField()); assertThat(property.getField().getSimpleName()).hasToString("name"); assertThat(property.isProperty(metadataEnv)).isTrue(); assertThat(property.isNested(metadataEnv)).isFalse(); @@ -60,7 +59,6 @@ void lombokCollectionProperty() { TypeElement ownerElement = roundEnv.getRootElement(LombokSimpleProperties.class); LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, "items"); assertThat(property.getName()).isEqualTo("items"); - assertThat(property.getSource()).isSameAs(property.getField()); assertThat(property.getField().getSimpleName()).hasToString("items"); assertThat(property.isProperty(metadataEnv)).isTrue(); assertThat(property.isNested(metadataEnv)).isFalse(); @@ -73,7 +71,6 @@ void lombokNestedPropertySameClass() { TypeElement ownerElement = roundEnv.getRootElement(LombokInnerClassProperties.class); LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, "first"); assertThat(property.getName()).isEqualTo("first"); - assertThat(property.getSource()).isSameAs(property.getField()); assertThat(property.getField().getSimpleName()).hasToString("first"); assertThat(property.isProperty(metadataEnv)).isFalse(); assertThat(property.isNested(metadataEnv)).isTrue(); @@ -86,7 +83,6 @@ void lombokNestedPropertyWithAnnotation() { TypeElement ownerElement = roundEnv.getRootElement(LombokInnerClassProperties.class); LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, "third"); assertThat(property.getName()).isEqualTo("third"); - assertThat(property.getSource()).isSameAs(property.getField()); assertThat(property.getField().getSimpleName()).hasToString("third"); assertThat(property.isProperty(metadataEnv)).isFalse(); assertThat(property.isNested(metadataEnv)).isTrue(); @@ -177,8 +173,8 @@ void lombokMetadataNestedGroup() { TypeElement ownerElement = roundEnv.getRootElement(LombokInnerClassProperties.class); VariableElement field = getField(ownerElement, "third"); ExecutableElement getter = getMethod(ownerElement, "getThird"); - LombokPropertyDescriptor property = new LombokPropertyDescriptor(ownerElement, null, field, "third", - field.asType(), getter, null); + LombokPropertyDescriptor property = new LombokPropertyDescriptor("third", field.asType(), ownerElement, + getter, null, field, null); assertItemMetadata(metadataEnv, property).isGroup() .hasName("test.third") .hasType("org.springframework.boot.configurationsample.lombok.SimpleLombokPojo") @@ -276,7 +272,7 @@ protected LombokPropertyDescriptor createPropertyDescriptor(TypeElement ownerEle VariableElement field = getField(ownerElement, name); ExecutableElement getter = getMethod(ownerElement, createAccessorMethodName("get", name)); ExecutableElement setter = getMethod(ownerElement, createAccessorMethodName("set", name)); - return new LombokPropertyDescriptor(ownerElement, null, field, name, field.asType(), getter, setter); + return new LombokPropertyDescriptor(name, field.asType(), ownerElement, getter, setter, field, null); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MergeMetadataGenerationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MergeMetadataGenerationTests.java index 167feb22f99e..c35e74755c85 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MergeMetadataGenerationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MergeMetadataGenerationTests.java @@ -74,7 +74,7 @@ void mergeExistingPropertyDefaultValue() throws Exception { assertThat(metadata).has(Metadata.withProperty("simple.flag", Boolean.class) .fromSource(SimpleProperties.class) .withDescription("A simple flag.") - .withDeprecation(null, null) + .withDeprecation() .withDefaultValue(true)); assertThat(metadata.getItems()).hasSize(4); } @@ -125,36 +125,36 @@ void mergeExistingPropertyDescription() throws Exception { @Test void mergeExistingPropertyDeprecation() throws Exception { ItemMetadata property = ItemMetadata.newProperty("simple", "comparator", null, null, null, null, null, - new ItemDeprecation("Don't use this.", "simple.complex-comparator", "error")); + new ItemDeprecation("Don't use this.", "simple.complex-comparator", "1.2.3", "error")); String additionalMetadata = buildAdditionalMetadata(property); ConfigurationMetadata metadata = compile(additionalMetadata, SimpleProperties.class); assertThat(metadata).has(Metadata.withProperty("simple.comparator", "java.util.Comparator") .fromSource(SimpleProperties.class) - .withDeprecation("Don't use this.", "simple.complex-comparator", "error")); + .withDeprecation("Don't use this.", "simple.complex-comparator", "1.2.3", "error")); assertThat(metadata.getItems()).hasSize(4); } @Test void mergeExistingPropertyDeprecationOverride() throws Exception { ItemMetadata property = ItemMetadata.newProperty("singledeprecated", "name", null, null, null, null, null, - new ItemDeprecation("Don't use this.", "single.name")); + new ItemDeprecation("Don't use this.", "single.name", "1.2.3")); String additionalMetadata = buildAdditionalMetadata(property); ConfigurationMetadata metadata = compile(additionalMetadata, DeprecatedSingleProperty.class); assertThat(metadata).has(Metadata.withProperty("singledeprecated.name", String.class.getName()) .fromSource(DeprecatedSingleProperty.class) - .withDeprecation("Don't use this.", "single.name")); + .withDeprecation("Don't use this.", "single.name", "1.2.3")); assertThat(metadata.getItems()).hasSize(3); } @Test void mergeExistingPropertyDeprecationOverrideLevel() throws Exception { ItemMetadata property = ItemMetadata.newProperty("singledeprecated", "name", null, null, null, null, null, - new ItemDeprecation(null, null, "error")); + new ItemDeprecation(null, null, null, "error")); String additionalMetadata = buildAdditionalMetadata(property); ConfigurationMetadata metadata = compile(additionalMetadata, DeprecatedSingleProperty.class); assertThat(metadata).has(Metadata.withProperty("singledeprecated.name", String.class.getName()) .fromSource(DeprecatedSingleProperty.class) - .withDeprecation("renamed", "singledeprecated.new-name", "error")); + .withDeprecation("renamed", "singledeprecated.new-name", "1.2.3", "error")); assertThat(metadata.getItems()).hasSize(3); } @@ -175,7 +175,7 @@ void mergingOfSimpleHint() throws Exception { .fromSource(SimpleProperties.class) .withDescription("The name of this simple properties.") .withDefaultValue("boot") - .withDeprecation(null, null)); + .withDeprecation()); assertThat(metadata) .has(Metadata.withHint("simple.the-name").withValue(0, "boot", "Bla bla").withValue(1, "spring", null)); } @@ -189,7 +189,7 @@ void mergingOfHintWithNonCanonicalName() throws Exception { .fromSource(SimpleProperties.class) .withDescription("The name of this simple properties.") .withDefaultValue("boot") - .withDeprecation(null, null)); + .withDeprecation()); assertThat(metadata).has(Metadata.withHint("simple.the-name").withValue(0, "boot", "Bla bla")); } @@ -203,18 +203,19 @@ void mergingOfHintWithProvider() throws Exception { .fromSource(SimpleProperties.class) .withDescription("The name of this simple properties.") .withDefaultValue("boot") - .withDeprecation(null, null)); + .withDeprecation()); assertThat(metadata).has( Metadata.withHint("simple.the-name").withProvider("first", "target", "org.foo").withProvider("second")); } @Test void mergingOfAdditionalDeprecation() throws Exception { - String deprecations = buildPropertyDeprecations(ItemMetadata.newProperty("simple", "wrongName", - "java.lang.String", null, null, null, null, new ItemDeprecation("Lame name.", "simple.the-name"))); + String deprecations = buildPropertyDeprecations( + ItemMetadata.newProperty("simple", "wrongName", "java.lang.String", null, null, null, null, + new ItemDeprecation("Lame name.", "simple.the-name", "1.2.3"))); ConfigurationMetadata metadata = compile(deprecations, SimpleProperties.class); assertThat(metadata).has(Metadata.withProperty("simple.wrong-name", String.class) - .withDeprecation("Lame name.", "simple.the-name")); + .withDeprecation("Lame name.", "simple.the-name", "1.2.3")); } @Test @@ -268,6 +269,9 @@ private String buildPropertyDeprecations(ItemMetadata... items) throws Exception if (deprecation.getReplacement() != null) { deprecationJson.put("replacement", deprecation.getReplacement()); } + if (deprecation.getSince() != null) { + deprecationJson.put("since", deprecation.getSince()); + } jsonObject.put("deprecation", deprecationJson); } propertiesArray.put(jsonObject); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MethodBasedMetadataGenerationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MethodBasedMetadataGenerationTests.java index e7391bf98080..591c410b16b0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MethodBasedMetadataGenerationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MethodBasedMetadataGenerationTests.java @@ -114,11 +114,11 @@ void deprecatedMethodConfig() { assertThat(metadata).has(Metadata.withGroup("foo").fromSource(type)); assertThat(metadata).has(Metadata.withProperty("foo.name", String.class) .fromSource(DeprecatedMethodConfig.Foo.class) - .withDeprecation(null, null)); + .withDeprecation()); assertThat(metadata).has(Metadata.withProperty("foo.flag", Boolean.class) .withDefaultValue(false) .fromSource(DeprecatedMethodConfig.Foo.class) - .withDeprecation(null, null)); + .withDeprecation()); } @Test @@ -129,11 +129,11 @@ void deprecatedMethodConfigOnClass() { assertThat(metadata).has(Metadata.withGroup("foo").fromSource(type)); assertThat(metadata).has(Metadata.withProperty("foo.name", String.class) .fromSource(org.springframework.boot.configurationsample.method.DeprecatedClassMethodConfig.Foo.class) - .withDeprecation(null, null)); + .withDeprecation()); assertThat(metadata).has(Metadata.withProperty("foo.flag", Boolean.class) .withDefaultValue(false) .fromSource(org.springframework.boot.configurationsample.method.DeprecatedClassMethodConfig.Foo.class) - .withDeprecation(null, null)); + .withDeprecation()); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolverTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolverTests.java index 9f85011de406..686cdbb2f5b3 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolverTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -111,15 +111,16 @@ void propertiesWithLombokValueClass() { void propertiesWithDeducedConstructorBinding() { process(ImmutableDeducedConstructorBindingProperties.class, propertyNames((stream) -> assertThat(stream).containsExactly("theName", "flag"))); - process(ImmutableDeducedConstructorBindingProperties.class, properties((stream) -> assertThat(stream) - .allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor))); + process(ImmutableDeducedConstructorBindingProperties.class, + properties((stream) -> assertThat(stream).isNotEmpty() + .allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor))); } @Test void propertiesWithConstructorWithConstructorBinding() { process(ImmutableSimpleProperties.class, propertyNames( (stream) -> assertThat(stream).containsExactly("theName", "flag", "comparator", "counter"))); - process(ImmutableSimpleProperties.class, properties((stream) -> assertThat(stream) + process(ImmutableSimpleProperties.class, properties((stream) -> assertThat(stream).isNotEmpty() .allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor))); } @@ -127,14 +128,14 @@ void propertiesWithConstructorWithConstructorBinding() { void propertiesWithConstructorAndClassConstructorBinding() { process(ImmutableClassConstructorBindingProperties.class, propertyNames((stream) -> assertThat(stream).containsExactly("name", "description"))); - process(ImmutableClassConstructorBindingProperties.class, properties((stream) -> assertThat(stream) + process(ImmutableClassConstructorBindingProperties.class, properties((stream) -> assertThat(stream).isNotEmpty() .allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor))); } @Test void propertiesWithAutowiredConstructor() { process(AutowiredProperties.class, propertyNames((stream) -> assertThat(stream).containsExactly("theName"))); - process(AutowiredProperties.class, properties((stream) -> assertThat(stream) + process(AutowiredProperties.class, properties((stream) -> assertThat(stream).isNotEmpty() .allMatch((predicate) -> predicate instanceof JavaBeanPropertyDescriptor))); } @@ -142,21 +143,10 @@ void propertiesWithAutowiredConstructor() { void propertiesWithMultiConstructor() { process(ImmutableMultiConstructorProperties.class, propertyNames((stream) -> assertThat(stream).containsExactly("name", "description"))); - process(ImmutableMultiConstructorProperties.class, properties((stream) -> assertThat(stream) + process(ImmutableMultiConstructorProperties.class, properties((stream) -> assertThat(stream).isNotEmpty() .allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor))); } - @Test - @Deprecated(since = "3.0.0", forRemoval = true) - @SuppressWarnings("removal") - void propertiesWithMultiConstructorAndDeprecatedAnnotation() { - process(org.springframework.boot.configurationsample.immutable.DeprecatedImmutableMultiConstructorProperties.class, - propertyNames((stream) -> assertThat(stream).containsExactly("name", "description"))); - process(org.springframework.boot.configurationsample.immutable.DeprecatedImmutableMultiConstructorProperties.class, - properties((stream) -> assertThat(stream) - .allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor))); - } - @Test void propertiesWithMultiConstructorNoDirective() { process(TwoConstructorsExample.class, propertyNames((stream) -> assertThat(stream).containsExactly("name"))); @@ -171,7 +161,7 @@ void propertiesWithNameAnnotationParameter() { } private BiConsumer properties( - Consumer>> stream) { + Consumer> stream) { return (element, metadataEnv) -> { PropertyDescriptorResolver resolver = new PropertyDescriptorResolver(metadataEnv); stream.accept(resolver.resolve(element, null)); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorTests.java index c2d5b8fac616..92ba92aeb6c1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,7 +61,7 @@ protected VariableElement getField(TypeElement element, String name) { } protected ItemMetadataAssert assertItemMetadata(MetadataGenerationEnvironment metadataEnv, - PropertyDescriptor property) { + PropertyDescriptor property) { return new ItemMetadataAssert(property.resolveItemMetadata("test", metadataEnv)); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshallerTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshallerTests.java index 2cbda570e8a0..00252fc252e4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshallerTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshallerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,12 +20,14 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatException; /** * Tests for {@link JsonMarshaller}. @@ -38,14 +40,15 @@ class JsonMarshallerTests { @Test void marshallAndUnmarshal() throws Exception { ConfigurationMetadata metadata = new ConfigurationMetadata(); - metadata.add(ItemMetadata.newProperty("a", "b", StringBuffer.class.getName(), InputStream.class.getName(), - "sourceMethod", "desc", "x", new ItemDeprecation("Deprecation comment", "b.c.d"))); + metadata.add(ItemMetadata.newProperty("a", "b", StringBuffer.class.getName(), InputStream.class.getName(), null, + "desc", "x", new ItemDeprecation("Deprecation comment", "b.c.d", "1.2.3"))); metadata.add(ItemMetadata.newProperty("b.c.d", null, null, null, null, null, null, null)); metadata.add(ItemMetadata.newProperty("c", null, null, null, null, null, 123, null)); metadata.add(ItemMetadata.newProperty("d", null, null, null, null, null, true, null)); metadata.add(ItemMetadata.newProperty("e", null, null, null, null, null, new String[] { "y", "n" }, null)); metadata.add(ItemMetadata.newProperty("f", null, null, null, null, null, new Boolean[] { true, false }, null)); metadata.add(ItemMetadata.newGroup("d", null, null, null)); + metadata.add(ItemMetadata.newGroup("e", null, null, "sourceMethod")); metadata.add(ItemHint.newHint("a.b")); metadata.add(ItemHint.newHint("c", new ItemHint.ValueHint(123, "hey"), new ItemHint.ValueHint(456, null))); metadata.add(new ItemHint("d", null, @@ -59,13 +62,14 @@ void marshallAndUnmarshal() throws Exception { .fromSource(InputStream.class) .withDescription("desc") .withDefaultValue("x") - .withDeprecation("Deprecation comment", "b.c.d")); + .withDeprecation("Deprecation comment", "b.c.d", "1.2.3")); assertThat(read).has(Metadata.withProperty("b.c.d")); assertThat(read).has(Metadata.withProperty("c").withDefaultValue(123)); assertThat(read).has(Metadata.withProperty("d").withDefaultValue(true)); assertThat(read).has(Metadata.withProperty("e").withDefaultValue(new String[] { "y", "n" })); assertThat(read).has(Metadata.withProperty("f").withDefaultValue(new Object[] { true, false })); assertThat(read).has(Metadata.withGroup("d")); + assertThat(read).has(Metadata.withGroup("e").fromSourceMethod("sourceMethod")); assertThat(read).has(Metadata.withHint("a.b")); assertThat(read).has(Metadata.withHint("c").withValue(0, 123, "hey").withValue(1, 456, null)); assertThat(read).has(Metadata.withHint("d").withProvider("first", "target", "foo").withProvider("second")); @@ -96,10 +100,10 @@ void marshallPutDeprecatedItemsAtTheEnd() throws IOException { ConfigurationMetadata metadata = new ConfigurationMetadata(); metadata.add(ItemMetadata.newProperty("com.example.bravo", "bbb", null, null, null, null, null, null)); metadata.add(ItemMetadata.newProperty("com.example.bravo", "aaa", null, null, null, null, null, - new ItemDeprecation(null, null, "warning"))); + new ItemDeprecation(null, null, null, "warning"))); metadata.add(ItemMetadata.newProperty("com.example.alpha", "ddd", null, null, null, null, null, null)); metadata.add(ItemMetadata.newProperty("com.example.alpha", "ccc", null, null, null, null, null, - new ItemDeprecation(null, null, "warning"))); + new ItemDeprecation(null, null, null, "warning"))); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); JsonMarshaller marshaller = new JsonMarshaller(); marshaller.write(metadata, outputStream); @@ -170,4 +174,159 @@ void orderingForSamePropertyNamesWithNullSourceType() throws IOException { "\"java.lang.Boolean\"", "\"com.example.bravo.aaa\"", "\"java.lang.Integer\"", "\"com.example.Bar"); } + @Test + void shouldCheckRootFields() { + String json = """ + { + "groups": [], "properties": [], "hints": [], "dummy": [] + }"""; + assertThatException().isThrownBy(() -> read(json)) + .withMessage("Expected only keys [groups, hints, properties], but found additional keys [dummy]. Path: ."); + } + + @Test + void shouldCheckGroupFields() { + String json = """ + { + "groups": [ + { + "name": "g", + "type": "java.lang.String", + "description": "Some description", + "sourceType": "java.lang.String", + "sourceMethod": "some()", + "dummy": "dummy" + } + ], "properties": [], "hints": [] + }"""; + assertThatException().isThrownBy(() -> read(json)) + .withMessage( + "Expected only keys [description, name, sourceMethod, sourceType, type], but found additional keys [dummy]. Path: .groups.[0]"); + } + + @Test + void shouldCheckPropertyFields() { + String json = """ + { + "groups": [], "properties": [ + { + "name": "name", + "type": "java.lang.String", + "description": "Some description", + "sourceType": "java.lang.String", + "defaultValue": "value", + "deprecation": { + "level": "warning", + "reason": "some reason", + "replacement": "name-new", + "since": "v17" + }, + "deprecated": true, + "dummy": "dummy" + } + ], "hints": [] + }"""; + assertThatException().isThrownBy(() -> read(json)) + .withMessage( + "Expected only keys [defaultValue, deprecated, deprecation, description, name, sourceType, type], but found additional keys [dummy]. Path: .properties.[0]"); + } + + @Test + void shouldCheckPropertyDeprecationFields() { + String json = """ + { + "groups": [], "properties": [ + { + "name": "name", + "type": "java.lang.String", + "description": "Some description", + "sourceType": "java.lang.String", + "defaultValue": "value", + "deprecation": { + "level": "warning", + "reason": "some reason", + "replacement": "name-new", + "since": "v17", + "dummy": "dummy" + }, + "deprecated": true + } + ], "hints": [] + }"""; + assertThatException().isThrownBy(() -> read(json)) + .withMessage( + "Expected only keys [level, reason, replacement, since], but found additional keys [dummy]. Path: .properties.[0].deprecation"); + } + + @Test + void shouldCheckHintFields() { + String json = """ + { + "groups": [], "properties": [], "hints": [ + { + "name": "name", + "values": [], + "providers": [], + "dummy": "dummy" + } + ] + }"""; + assertThatException().isThrownBy(() -> read(json)) + .withMessage( + "Expected only keys [name, providers, values], but found additional keys [dummy]. Path: .hints.[0]"); + } + + @Test + void shouldCheckHintValueFields() { + String json = """ + { + "groups": [], "properties": [], "hints": [ + { + "name": "name", + "values": [ + { + "value": "value", + "description": "some description", + "dummy": "dummy" + } + ], + "providers": [] + } + ] + }"""; + assertThatException().isThrownBy(() -> read(json)) + .withMessage( + "Expected only keys [description, value], but found additional keys [dummy]. Path: .hints.[0].values.[0]"); + } + + @Test + void shouldCheckHintProviderFields() { + String json = """ + { + "groups": [], "properties": [], "hints": [ + { + "name": "name", + "values": [], + "providers": [ + { + "name": "name", + "parameters": { + "target": "jakarta.servlet.http.HttpServlet" + }, + "dummy": "dummy" + } + ] + } + ] + }"""; + assertThatException().isThrownBy(() -> read(json)) + .withMessage( + "Expected only keys [name, parameters], but found additional keys [dummy]. Path: .hints.[0].providers.[0]"); + } + + private void read(String json) throws Exception { + JsonMarshaller marshaller = new JsonMarshaller(); + marshaller.read(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8))); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/Metadata.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/Metadata.java index 39be910daf52..0f4fcb10fd8c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/Metadata.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/Metadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -157,10 +157,7 @@ public boolean matches(ConfigurationMetadata value) { if (this.deprecation == null && itemMetadata.getDeprecation() != null) { return false; } - if (this.deprecation != null && !this.deprecation.equals(itemMetadata.getDeprecation())) { - return false; - } - return true; + return this.deprecation == null || this.deprecation.equals(itemMetadata.getDeprecation()); } public MetadataItemCondition ofType(Class dataType) { @@ -193,13 +190,17 @@ public MetadataItemCondition withDefaultValue(Object defaultValue) { this.description, defaultValue, this.deprecation); } - public MetadataItemCondition withDeprecation(String reason, String replacement) { - return withDeprecation(reason, replacement, null); + public MetadataItemCondition withDeprecation() { + return withDeprecation(null, null, null, null); + } + + public MetadataItemCondition withDeprecation(String reason, String replacement, String since) { + return withDeprecation(reason, replacement, since, null); } - public MetadataItemCondition withDeprecation(String reason, String replacement, String level) { + public MetadataItemCondition withDeprecation(String reason, String replacement, String since, String level) { return new MetadataItemCondition(this.itemType, this.name, this.type, this.sourceType, this.sourceMethod, - this.description, this.defaultValue, new ItemDeprecation(reason, replacement, level)); + this.description, this.defaultValue, new ItemDeprecation(reason, replacement, since, level)); } public MetadataItemCondition withNoDeprecation() { @@ -344,10 +345,7 @@ public boolean matches(ItemHint value) { if (this.value != null && !this.value.equals(valueHint.getValue())) { return false; } - if (this.description != null && !this.description.equals(valueHint.getDescription())) { - return false; - } - return true; + return this.description == null || this.description.equals(valueHint.getDescription()); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DeprecatedConfigurationProperty.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DeprecatedConfigurationProperty.java index 0853090e9388..3aaf2a6540de 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DeprecatedConfigurationProperty.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DeprecatedConfigurationProperty.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,4 +50,10 @@ */ String replacement() default ""; + /** + * The version in which the property became deprecated. + * @return the version + */ + String since() default ""; + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DeprecatedConstructorBinding.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DeprecatedConstructorBinding.java deleted file mode 100644 index ee1ae440f81c..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DeprecatedConstructorBinding.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.configurationsample; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Alternative to Spring Boot's deprecated - * {@code @org.springframework.boot.context.properties.ConstructorBinding} for testing - * (removes the need for a dependency on the real annotation). - * - * @author Stephane Nicoll - */ -@Target(ElementType.CONSTRUCTOR) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@ConstructorBinding -@Deprecated(since = "3.0.0", forRemoval = true) -public @interface DeprecatedConstructorBinding { - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/NestedConfigurationProperty.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/NestedConfigurationProperty.java index 599c7339bcb6..c16fab5ef6a2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/NestedConfigurationProperty.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/NestedConfigurationProperty.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ * @author Phillip Webb * @since 1.2.0 */ -@Target(ElementType.FIELD) +@Target({ ElementType.FIELD, ElementType.RECORD_COMPONENT, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface NestedConfigurationProperty { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/deprecation/Dbcp2Configuration.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/deprecation/Dbcp2Configuration.java new file mode 100644 index 000000000000..556fd05531a4 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/deprecation/Dbcp2Configuration.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationsample.deprecation; + +import org.apache.commons.dbcp2.BasicDataSource; + +import org.springframework.boot.configurationsample.ConfigurationProperties; + +/** + * Test configuration for DBCP2 {@link BasicDataSource}. + * + * @author Moritz Halbritter + */ +public class Dbcp2Configuration { + + @ConfigurationProperties(prefix = "spring.datasource.dbcp2") + BasicDataSource basicDataSource() { + return new BasicDataSource(); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/SimpleEndpoint2.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/SimpleEndpoint2.java new file mode 100644 index 000000000000..971064af4489 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/SimpleEndpoint2.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationsample.endpoint; + +import org.springframework.boot.configurationsample.Endpoint; +import org.springframework.boot.configurationsample.ReadOperation; + +/** + * A simple endpoint with no default override, with the same id as {@link SimpleEndpoint}. + * + * @author Moritz Halbritter + */ +@Endpoint(id = "simple") +public class SimpleEndpoint2 { + + @ReadOperation + public String invoke() { + return "test"; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/SimpleEndpoint3.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/SimpleEndpoint3.java new file mode 100644 index 000000000000..48c88f16f410 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/SimpleEndpoint3.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationsample.endpoint; + +import org.springframework.boot.configurationsample.Endpoint; +import org.springframework.boot.configurationsample.ReadOperation; + +/** + * A simple endpoint with no default override, with the same id as {@link SimpleEndpoint}, + * but not enabled by default. + * + * @author Moritz Halbritter + */ +@Endpoint(id = "simple", enableByDefault = false) +public class SimpleEndpoint3 { + + @ReadOperation + public String invoke() { + return "test"; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/DeprecatedImmutableMultiConstructorProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/DeprecatedImmutableMultiConstructorProperties.java deleted file mode 100644 index d2e0305fc149..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/DeprecatedImmutableMultiConstructorProperties.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.configurationsample.immutable; - -/** - * Simple immutable properties with several constructors. - * - * @author Stephane Nicoll - */ -@SuppressWarnings("unused") -@Deprecated(since = "3.0.0", forRemoval = true) -public class DeprecatedImmutableMultiConstructorProperties { - - private final String name; - - /** - * Test description. - */ - private final String description; - - public DeprecatedImmutableMultiConstructorProperties(String name) { - this(name, null); - } - - @SuppressWarnings("removal") - @org.springframework.boot.configurationsample.DeprecatedConstructorBinding - public DeprecatedImmutableMultiConstructorProperties(String name, String description) { - this.name = name; - this.description = description; - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/method/NestedPropertiesMethod.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/method/NestedPropertiesMethod.java new file mode 100644 index 000000000000..2139e830dc1c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/method/NestedPropertiesMethod.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationsample.method; + +import org.springframework.boot.configurationsample.ConfigurationProperties; +import org.springframework.boot.configurationsample.NestedConfigurationProperty; + +@ConfigurationProperties("method-nested") +public class NestedPropertiesMethod { + + private String myProperty; + + private final NestedProperty nested = new NestedProperty(); + + private final Inner inner = new Inner(); + + public String getMyProperty() { + return this.myProperty; + } + + public void setMyProperty(String myProperty) { + this.myProperty = myProperty; + } + + @NestedConfigurationProperty + public NestedProperty getNested() { + return this.nested; + } + + public Inner getInner() { + return this.inner; + } + + public static class Inner { + + private String myInnerProperty; + + private final NestedProperty nested = new NestedProperty(); + + public String getMyInnerProperty() { + return this.myInnerProperty; + } + + public void setMyInnerProperty(String myInnerProperty) { + this.myInnerProperty = myInnerProperty; + } + + @NestedConfigurationProperty + public NestedProperty getNested() { + return this.nested; + } + + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/method/NestedProperty.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/method/NestedProperty.java new file mode 100644 index 000000000000..fde146e40593 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/method/NestedProperty.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationsample.method; + +public class NestedProperty { + + private String myNestedProperty; + + public String getMyNestedProperty() { + return this.myNestedProperty; + } + + public void setMyNestedProperty(String myNestedProperty) { + this.myNestedProperty = myNestedProperty; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/record/ExampleRecord.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/record/ExampleRecord.java new file mode 100644 index 000000000000..41814a407497 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/record/ExampleRecord.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationsample.record; + +// @formatter:off + +/** + * Example Record Javadoc sample + * + * @param someString very long description that + * doesn't fit single line and is indented + * @param someInteger description with @param and @ pitfalls + * @param someBoolean description with extra spaces + * @param someLong description without space after asterisk + * @param someByte last description in Javadoc + * @since 1.0.0 + * @author Pavel Anisimov + */ +@org.springframework.boot.configurationsample.ConfigurationProperties("record.descriptions") +public record ExampleRecord(String someString, Integer someInteger, Boolean someBoolean, Long someLong, Byte someByte) { +} + +//@formatter:on diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/record/NestedPropertiesRecord.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/record/NestedPropertiesRecord.java new file mode 100644 index 000000000000..c8ebbe0c4d04 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/record/NestedPropertiesRecord.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationsample.record; + +import org.springframework.boot.configurationsample.ConfigurationProperties; +import org.springframework.boot.configurationsample.NestedConfigurationProperty; + +@ConfigurationProperties("record-nested") +public record NestedPropertiesRecord(String myProperty, @NestedConfigurationProperty NestedRecord nested, + InnerPropertiesRecord inner) { + + public record InnerPropertiesRecord(String myInnerProperty, @NestedConfigurationProperty NestedRecord nested) { + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/record/NestedRecord.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/record/NestedRecord.java new file mode 100644 index 000000000000..bdd6385442db --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/record/NestedRecord.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationsample.record; + +public record NestedRecord(String myNestedProperty) { +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/simple/DeprecatedSingleProperty.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/simple/DeprecatedSingleProperty.java index f8df3f7c4aeb..984c98764386 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/simple/DeprecatedSingleProperty.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/simple/DeprecatedSingleProperty.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ public class DeprecatedSingleProperty { private String newName; @Deprecated - @DeprecatedConfigurationProperty(reason = "renamed", replacement = "singledeprecated.new-name") + @DeprecatedConfigurationProperty(reason = "renamed", replacement = "singledeprecated.new-name", since = "1.2.3") public String getName() { return getNewName(); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/DeprecatedSimplePojo.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/DeprecatedSimplePojo.java new file mode 100644 index 000000000000..2beb61749fd9 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/DeprecatedSimplePojo.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationsample.specific; + +/** + * POJO for use with samples needing a deprecated value. + * + * @author Jared Bates + */ +public class DeprecatedSimplePojo { + + private int value; + + @Deprecated + public int getValue() { + return this.value; + } + + public void setValue(int value) { + this.value = value; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InnerClassProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InnerClassProperties.java index 1534568faacd..049371b45fd7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InnerClassProperties.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InnerClassProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,8 @@ public class InnerClassProperties { private Fourth fourth; + private final DeprecatedSimplePojo fifth = new DeprecatedSimplePojo(); + public Foo getFirst() { return this.first; } @@ -60,6 +62,11 @@ public void setFourth(Fourth fourth) { this.fourth = fourth; } + @NestedConfigurationProperty + public DeprecatedSimplePojo getFifth() { + return this.fifth; + } + public static class Foo { private String name; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/build.gradle index 48e174123a43..02430dcba4bc 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/build.gradle @@ -1,8 +1,13 @@ +import org.gradle.plugins.ide.eclipse.EclipsePlugin +import org.gradle.plugins.ide.eclipse.model.Classpath +import org.gradle.plugins.ide.eclipse.model.Library + plugins { id "java-gradle-plugin" id "maven-publish" - id "org.asciidoctor.jvm.convert" + id "org.antora" id "org.springframework.boot.conventions" + id "org.springframework.boot.docker-test" id "org.springframework.boot.maven-repository" id "org.springframework.boot.optional-dependencies" } @@ -10,26 +15,21 @@ plugins { description = "Spring Boot Gradle Plugins" configurations { - documentation + antoraContent "testCompileClasspath" { // Downgrade SLF4J is required for tests to run in Eclipse resolutionStrategy.force("org.slf4j:slf4j-api:1.7.36") } - all { - resolutionStrategy { - eachDependency { dependency -> - // Downgrade Jackson as Gradle cannot cope with 2.15.0's multi-version - // jar files with bytecode in META-INF/versions/19 - if (dependency.requested.group.startsWith("com.fasterxml.jackson")) { - dependency.useVersion("2.14.2") - } - } - } - } } dependencies { - asciidoctorExtensions("io.spring.asciidoctor:spring-asciidoctor-extensions-section-ids") + dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-gradle-test-support")) + dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker")) + dockerTestImplementation(gradleTestKit()) + dockerTestImplementation("org.assertj:assertj-core") + dockerTestImplementation("org.junit.jupiter:junit-jupiter") + dockerTestImplementation("org.testcontainers:junit-jupiter") + dockerTestImplementation("org.testcontainers:testcontainers") implementation(project(":spring-boot-project:spring-boot-tools:spring-boot-buildpack-platform")) implementation(project(":spring-boot-project:spring-boot-tools:spring-boot-loader-tools")) @@ -38,18 +38,27 @@ dependencies { implementation("org.springframework:spring-core") optional("org.graalvm.buildtools:native-gradle-plugin") + optional("org.cyclonedx:cyclonedx-gradle-plugin") { + exclude(group: "org.apache.maven", module: "maven-core") + } optional("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") { exclude(group: "commons-logging", module: "commons-logging") } - testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-gradle-test-support")) + testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation("com.tngtech.archunit:archunit-junit5:0.22.0") testImplementation("org.assertj:assertj-core") testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.mockito:mockito-core") - testImplementation("org.testcontainers:junit-jupiter") - testImplementation("org.testcontainers:testcontainers") +} + +repositories { + gradlePluginPortal() { + content { + includeGroup("org.cyclonedx") + } + } } gradlePlugin { @@ -81,36 +90,8 @@ validatePlugins { enableStricterValidation = true } -task dependencyVersions(type: org.springframework.boot.build.constraints.ExtractVersionConstraints) { - enforcedPlatform(":spring-boot-project:spring-boot-dependencies") -} - -tasks.withType(org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask) { - dependsOn dependencyVersions - inputs.dir('src/docs/gradle').withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName('buildScripts') - doFirst { - attributes "dependency-management-plugin-version": dependencyVersions.versionConstraints["io.spring.gradle:dependency-management-plugin"] - } -} - tasks.named('test') { - inputs.dir('src/docs/gradle').withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName('buildScripts') -} - -asciidoctor { - sources { - include "index.adoc" - } -} - -task asciidoctorPdf(type: org.asciidoctor.gradle.jvm.AsciidoctorTask) { - sources { - include "index.adoc" - } -} - -tasks.withType(org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask) { - attributes "native-build-tools-version": nativeBuildToolsVersion + inputs.dir('src/docs/antora/modules/gradle-plugin/examples').withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName('buildScripts') } javadoc { @@ -128,25 +109,52 @@ javadoc { } } -task zip(type: Zip) { - dependsOn asciidoctor, asciidoctorPdf - duplicatesStrategy "fail" - from(asciidoctorPdf.outputDir) { - into "reference/pdf" - rename "index.pdf", "${project.name}-reference.pdf" - } - from(asciidoctor.outputDir) { - into "reference/htmlsingle" +def antoraGradlePluginLocalAggregateContent = tasks.register("antoraGradlePluginLocalAggregateContent", Zip) { + destinationDirectory = layout.buildDirectory.dir('generated/docs/antora-content') + archiveClassifier = "gradle-plugin-local-aggregate-content" + from(tasks.getByName("generateAntoraYml")) { + into "modules" } +} + +def antoraGradlePluginCatalogContent = tasks.register("antoraGradlePluginCatalogContent", Zip) { + destinationDirectory = layout.buildDirectory.dir('generated/docs/antora-content') + archiveClassifier = "gradle-plugin-catalog-content" from(javadoc) { - into "api" + into "api/java" } } +tasks.named("generateAntoraPlaybook") { + xrefStubs = ["appendix:.*", "api:.*", "reference:.*"] + alwaysInclude = [name: "gradle-plugin", classifier: "local-aggregate-content"] + dependsOn antoraGradlePluginLocalAggregateContent +} + +tasks.named("antora") { + inputs.files(antoraGradlePluginLocalAggregateContent, antoraGradlePluginCatalogContent) +} + artifacts { - "documentation" zip + antoraContent antoraGradlePluginCatalogContent } toolchain { maximumCompatibleJavaVersion = JavaLanguageVersion.of(20) } + +plugins.withType(EclipsePlugin) { + eclipse { + classpath.file { merger -> + merger.whenMerged { content -> + if (content instanceof Classpath) { + content.entries.each { entry -> + if (entry instanceof Library && (entry.path.contains("gradle-api-") || entry.path.contains("groovy-"))) { + entry.entryAttributes.remove("test") + } + } + } + } + } + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java similarity index 88% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java index 3ef2e777f2f2..f34eff25a4a1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,7 @@ import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.OS; import org.springframework.boot.buildpack.platform.docker.DockerApi; @@ -47,9 +48,10 @@ import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.boot.buildpack.platform.io.FilePermissions; import org.springframework.boot.gradle.junit.GradleCompatibility; +import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import org.springframework.boot.testsupport.junit.DisabledOnOs; -import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable; +import org.springframework.util.FileSystemUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -76,6 +78,23 @@ void buildsImageWithDefaultBuilder() throws IOException { String projectName = this.gradleBuild.getProjectDir().getName(); assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); + assertThat(result.getOutput()).contains("Running detector"); + assertThat(result.getOutput()).contains("Running builder"); + assertThat(result.getOutput()).contains("---> Test Info buildpack building"); + assertThat(result.getOutput()).contains("Network status: HTTP/2 200"); + assertThat(result.getOutput()).contains("---> Test Info buildpack done"); + removeImages(projectName); + } + + @TestTemplate + void buildsImageWithTrustBuilder() throws IOException { + writeMainClass(); + writeLongNameResource(); + BuildResult result = this.gradleBuild.build("bootBuildImage"); + String projectName = this.gradleBuild.getProjectDir().getName(); + assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()).contains("docker.io/library/" + projectName); + assertThat(result.getOutput()).contains("Running creator"); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("Network status: HTTP/2 200"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); @@ -144,10 +163,11 @@ void buildsImageWithCommandLineOptions() throws IOException { writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage", "--pullPolicy=IF_NOT_PRESENT", "--imageName=example/test-image-cmd", "--builder=ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1", - "--runImage=paketobuildpacks/run-jammy-tiny", "--createdDate=2020-07-01T12:34:56Z", + "--trustBuilder", "--runImage=paketobuildpacks/run-jammy-tiny", "--createdDate=2020-07-01T12:34:56Z", "--applicationDirectory=/application"); assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("example/test-image-cmd"); + assertThat(result.getOutput()).contains("Running creator"); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); Image image = new DockerApi().image().inspect(ImageReference.of("example/test-image-cmd")); @@ -296,6 +316,37 @@ void buildsImageWithVolumeCaches() throws IOException { deleteVolumes("cache-" + projectName + ".build", "cache-" + projectName + ".launch"); } + @TestTemplate + @EnabledOnOs(value = OS.LINUX, disabledReason = "Works with Docker Engine on Linux but is not reliable with " + + "Docker Desktop on other OSs") + void buildsImageWithBindCaches() throws IOException { + writeMainClass(); + writeLongNameResource(); + BuildResult result = this.gradleBuild.build("bootBuildImage"); + String projectName = this.gradleBuild.getProjectDir().getName(); + assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()).contains("docker.io/library/" + projectName); + assertThat(result.getOutput()).contains("---> Test Info buildpack building"); + assertThat(result.getOutput()).contains("---> Test Info buildpack done"); + removeImages(projectName); + String tempDir = System.getProperty("java.io.tmpdir"); + Path buildCachePath = Paths.get(tempDir, "junit-image-cache-" + projectName + "-build"); + Path launchCachePath = Paths.get(tempDir, "junit-image-cache-" + projectName + "-launch"); + assertThat(buildCachePath).exists().isDirectory(); + assertThat(launchCachePath).exists().isDirectory(); + cleanupCache(buildCachePath); + cleanupCache(launchCachePath); + } + + private static void cleanupCache(Path buildCachePath) { + try { + FileSystemUtils.deleteRecursively(buildCachePath); + } + catch (Exception ex) { + // ignore + } + } + @TestTemplate void buildsImageWithCreatedDate() throws IOException { writeMainClass(); @@ -343,6 +394,19 @@ void buildsImageWithApplicationDirectory() throws IOException { removeImages(projectName); } + @TestTemplate + void buildsImageWithEmptySecurityOptions() throws IOException { + writeMainClass(); + writeLongNameResource(); + BuildResult result = this.gradleBuild.build("bootBuildImage"); + String projectName = this.gradleBuild.getProjectDir().getName(); + assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()).contains("docker.io/library/" + projectName); + assertThat(result.getOutput()).contains("---> Test Info buildpack building"); + assertThat(result.getOutput()).contains("---> Test Info buildpack done"); + removeImages(projectName); + } + @TestTemplate void failsWithInvalidCreatedDate() throws IOException { writeMainClass(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageRegistryIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageRegistryIntegrationTests.java similarity index 86% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageRegistryIntegrationTests.java rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageRegistryIntegrationTests.java index f3d4c221a2e8..8d0e8dd00f10 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageRegistryIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageRegistryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,14 +20,12 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; -import java.time.Duration; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.TestTemplate; -import org.testcontainers.containers.GenericContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -36,8 +34,9 @@ import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.gradle.junit.GradleCompatibility; +import org.springframework.boot.testsupport.container.RegistryContainer; +import org.springframework.boot.testsupport.container.TestImage; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; -import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import static org.assertj.core.api.Assertions.assertThat; @@ -52,8 +51,7 @@ class BootBuildImageRegistryIntegrationTests { @Container - static final RegistryContainer registry = new RegistryContainer().withStartupAttempts(5) - .withStartupTimeout(Duration.ofMinutes(3)); + static final RegistryContainer registry = TestImage.container(RegistryContainer.class); String registryAddress; @@ -103,14 +101,4 @@ private void writeMainClass() { } } - private static class RegistryContainer extends GenericContainer { - - RegistryContainer() { - super(DockerImageNames.registry()); - addExposedPorts(5000); - addEnv("SERVER_NAME", "localhost"); - } - - } - } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithApplicationDirectory.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithApplicationDirectory.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithApplicationDirectory.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithApplicationDirectory.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBindCaches.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBindCaches.gradle new file mode 100644 index 000000000000..2ae1293f2eb7 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBindCaches.gradle @@ -0,0 +1,24 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootBuildImage { + builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1" + pullPolicy = "IF_NOT_PRESENT" + buildWorkspace { + bind { + source = System.getProperty('java.io.tmpdir') + "/junit-image-pack-${rootProject.name}-work" + } + } + buildCache { + bind { + source = System.getProperty('java.io.tmpdir') + "/junit-image-cache-${rootProject.name}-build" + } + } + launchCache { + bind { + source = System.getProperty('java.io.tmpdir') + "/junit-image-cache-${rootProject.name}-launch" + } + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBinding.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBinding.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBinding.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBinding.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpackFromBuilder.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpackFromBuilder.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpackFromBuilder.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpackFromBuilder.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpackFromDirectory.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpackFromDirectory.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpackFromDirectory.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpackFromDirectory.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpackFromTarGzip.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpackFromTarGzip.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpackFromTarGzip.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpackFromTarGzip.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpacksFromImages.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpacksFromImages.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpacksFromImages.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpacksFromImages.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCommandLineOptions.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCommandLineOptions.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCommandLineOptions.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCommandLineOptions.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCreatedDate.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCreatedDate.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCreatedDate.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCreatedDate.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCurrentCreatedDate.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCurrentCreatedDate.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCurrentCreatedDate.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCurrentCreatedDate.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomBuilderAndRunImage.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomBuilderAndRunImage.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomBuilderAndRunImage.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomBuilderAndRunImage.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomName.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomName.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomName.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomName.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithEmptySecurityOptions.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithEmptySecurityOptions.gradle new file mode 100644 index 000000000000..ca92d1b2d2c8 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithEmptySecurityOptions.gradle @@ -0,0 +1,10 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootBuildImage { + builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1" + pullPolicy = "IF_NOT_PRESENT" + securityOptions = [] +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithLaunchScript.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithLaunchScript.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithLaunchScript.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithLaunchScript.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithNetworkModeNone.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithNetworkModeNone.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithNetworkModeNone.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithNetworkModeNone.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithPullPolicy.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithPullPolicy.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithPullPolicy.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithPullPolicy.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithTag.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithTag.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithTag.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithTag.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithTrustBuilder.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithTrustBuilder.gradle new file mode 100644 index 000000000000..27136227b0a1 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithTrustBuilder.gradle @@ -0,0 +1,14 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +if (project.hasProperty('applyWarPlugin')) { + apply plugin: 'war' +} + +bootBuildImage { + builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1" + trustBuilder = true + pullPolicy = "IF_NOT_PRESENT" +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithVolumeCaches.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithVolumeCaches.gradle similarity index 81% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithVolumeCaches.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithVolumeCaches.gradle index 2de6151f89fb..0073c9a5d85f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithVolumeCaches.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithVolumeCaches.gradle @@ -6,6 +6,11 @@ plugins { bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1" pullPolicy = "IF_NOT_PRESENT" + buildWorkspace { + volume { + name = "pack-${rootProject.name}.work" + } + } buildCache { volume { name = "cache-${rootProject.name}.build" diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithWarPackagingAndJarConfiguration.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithWarPackagingAndJarConfiguration.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithWarPackagingAndJarConfiguration.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithWarPackagingAndJarConfiguration.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWhenCachesAreConfiguredTwice.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWhenCachesAreConfiguredTwice.gradle new file mode 100644 index 000000000000..8b2051aac77e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWhenCachesAreConfiguredTwice.gradle @@ -0,0 +1,16 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootBuildImage { + builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1" + buildCache { + volume { + name = "build-cache-volume" + } + bind { + name = "/tmp/build-cache-bind" + } + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithBuilderError.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithBuilderError.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithBuilderError.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithBuilderError.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithBuildpackNotInBuilder.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithBuildpackNotInBuilder.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithBuildpackNotInBuilder.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithBuildpackNotInBuilder.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithInvalidCreatedDate.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithInvalidCreatedDate.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithInvalidCreatedDate.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithInvalidCreatedDate.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithInvalidTag.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithInvalidTag.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithInvalidTag.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithInvalidTag.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageRegistryIntegrationTests.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageRegistryIntegrationTests.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageRegistryIntegrationTests.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageRegistryIntegrationTests.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/antora.yml b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/antora.yml new file mode 100644 index 000000000000..a0830e8b6a62 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/antora.yml @@ -0,0 +1,9 @@ +name: boot +version: true +ext: + zip_contents_collector: + include: + - name: gradle-plugin + classifier: catalog-content + module: gradle-plugin + destination: content-catalog diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/local-nav.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/local-nav.adoc new file mode 100644 index 000000000000..dd5f26ff4f77 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/local-nav.adoc @@ -0,0 +1 @@ +include::gradle-plugin:partial$nav-gradle-plugin.adoc[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/aot/apply-native-image-plugin.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/aot/apply-native-image-plugin.gradle new file mode 100644 index 000000000000..5af481d10b7a --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/aot/apply-native-image-plugin.gradle @@ -0,0 +1,5 @@ +plugins { + id 'org.springframework.boot' version '{version-spring-boot}' + id 'org.graalvm.buildtools.native' version '{version-native-build-tools}' + id 'java' +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/aot/apply-native-image-plugin.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/aot/apply-native-image-plugin.gradle.kts new file mode 100644 index 000000000000..6ef5f11b8c13 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/aot/apply-native-image-plugin.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("org.springframework.boot") version "{version-spring-boot}" + id("org.graalvm.buildtools.native") version "{version-native-build-tools}" + java +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/apply-plugin-release.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/apply-plugin-release.gradle new file mode 100644 index 000000000000..10a15d634003 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/apply-plugin-release.gradle @@ -0,0 +1,3 @@ +plugins { + id 'org.springframework.boot' version '{version-spring-boot}' +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/apply-plugin-release.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/apply-plugin-release.gradle.kts new file mode 100644 index 000000000000..f0f80a6dec38 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/apply-plugin-release.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("org.springframework.boot") version "{version-spring-boot}" +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/apply-plugin-snapshot.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/apply-plugin-snapshot.gradle similarity index 89% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/apply-plugin-snapshot.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/apply-plugin-snapshot.gradle index 5ffae4676a74..345752a50233 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/apply-plugin-snapshot.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/apply-plugin-snapshot.gradle @@ -4,7 +4,7 @@ buildscript { } dependencies { - classpath 'org.springframework.boot:spring-boot-gradle-plugin:{gradle-project-version}' + classpath 'org.springframework.boot:spring-boot-gradle-plugin:{version-spring-boot}' } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/milestone-settings.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/milestone-settings.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/milestone-settings.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/milestone-settings.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/milestone-settings.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/milestone-settings.gradle.kts similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/milestone-settings.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/milestone-settings.gradle.kts diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/snapshot-settings.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/snapshot-settings.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/snapshot-settings.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/snapshot-settings.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/snapshot-settings.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/snapshot-settings.gradle.kts similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/snapshot-settings.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/snapshot-settings.gradle.kts diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/typical-plugins.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/typical-plugins.gradle similarity index 81% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/typical-plugins.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/typical-plugins.gradle index b044017fc6d1..ea8a658adc77 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/typical-plugins.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/typical-plugins.gradle @@ -1,7 +1,7 @@ // tag::apply[] plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } apply plugin: 'io.spring.dependency-management' diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/typical-plugins.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/typical-plugins.gradle.kts similarity index 81% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/typical-plugins.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/typical-plugins.gradle.kts index 754080730e32..199164e4f39b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/typical-plugins.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/typical-plugins.gradle.kts @@ -1,7 +1,7 @@ // tag::apply[] plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } apply(plugin = "io.spring.dependency-management") diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-additional.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-additional.gradle new file mode 100644 index 000000000000..d8b193e0adba --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-additional.gradle @@ -0,0 +1,17 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version-spring-boot}' +} + +// tag::additional[] +springBoot { + buildInfo { + properties { + additional = [ + 'a': 'alpha', + 'b': 'bravo' + ] + } + } +} +// end::additional[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-additional.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-additional.gradle.kts new file mode 100644 index 000000000000..460dbd77620d --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-additional.gradle.kts @@ -0,0 +1,18 @@ +plugins { + java + id("org.springframework.boot") version "{version-spring-boot}" +} + +// tag::additional[] +springBoot { + buildInfo { + properties { + additional.set(mapOf( + "a" to "alpha", + "b" to "bravo" + )) + } + } +} +// end::additional[] + diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-basic.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-basic.gradle new file mode 100644 index 000000000000..36f3bf0a137e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-basic.gradle @@ -0,0 +1,10 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version-spring-boot}' +} + +// tag::build-info[] +springBoot { + buildInfo() +} +// end::build-info[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-basic.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-basic.gradle.kts new file mode 100644 index 000000000000..0c9165fcf64a --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-basic.gradle.kts @@ -0,0 +1,10 @@ +plugins { + java + id("org.springframework.boot") version "{version-spring-boot}" +} + +// tag::build-info[] +springBoot { + buildInfo() +} +// end::build-info[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-custom-values.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-custom-values.gradle similarity index 77% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-custom-values.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-custom-values.gradle index 961789da4b45..49a0f153c38f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-custom-values.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-custom-values.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } // tag::custom-values[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-custom-values.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-custom-values.gradle.kts similarity index 77% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-custom-values.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-custom-values.gradle.kts index 1c0d3ce98f49..5c95f8e2b04f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-custom-values.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-custom-values.gradle.kts @@ -1,6 +1,6 @@ plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } // tag::custom-values[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-exclude-time.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-exclude-time.gradle new file mode 100644 index 000000000000..486c56fc9c40 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-exclude-time.gradle @@ -0,0 +1,12 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version-spring-boot}' +} + +// tag::exclude-time[] +springBoot { + buildInfo { + excludes = ['time'] + } +} +// end::exclude-time[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-exclude-time.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-exclude-time.gradle.kts new file mode 100644 index 000000000000..57c491764550 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-exclude-time.gradle.kts @@ -0,0 +1,12 @@ +plugins { + java + id("org.springframework.boot") version "{version-spring-boot}" +} + +// tag::exclude-time[] +springBoot { + buildInfo { + excludes.set(setOf("time")) + } +} +// end::exclude-time[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-bom-with-plugins.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-bom-with-plugins.gradle.kts new file mode 100644 index 000000000000..85988331381c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-bom-with-plugins.gradle.kts @@ -0,0 +1,31 @@ +import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension + +// tag::configure-bom[] +plugins { + java + id("org.springframework.boot") version "{version-spring-boot}" apply false + id("io.spring.dependency-management") version "{version-dependency-management-plugin}" +} + +dependencyManagement { + imports { + mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) + } +} +// end::configure-bom[] + +the().apply { + resolutionStrategy { + eachDependency { + if (requested.group == "org.springframework.boot") { + useVersion("TEST-SNAPSHOT") + } + } + } +} + +repositories { + maven { + url = uri("repository") + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/configure-bom.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-bom.gradle similarity index 81% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/configure-bom.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-bom.gradle index 2c11b81900b1..bb6549b367ac 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/configure-bom.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-bom.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } // tag::configure-bom[] @@ -24,5 +24,5 @@ dependencyManagement { } repositories { - maven { url 'file:repository' } + maven { url 'repository' } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/configure-bom.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-bom.gradle.kts similarity index 85% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/configure-bom.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-bom.gradle.kts index 43dd49deed86..beb0582dbf7b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/configure-bom.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-bom.gradle.kts @@ -2,7 +2,7 @@ import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } // tag::configure-bom[] @@ -27,6 +27,6 @@ the().apply { repositories { maven { - url = uri("file:repository") + url = uri("repository") } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/configure-platform.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-platform.gradle similarity index 82% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/configure-platform.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-platform.gradle index 25a265efe187..00da8c306455 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/configure-platform.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-platform.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } // tag::configure-platform[] @@ -14,7 +14,7 @@ dependencies { } repositories { - maven { url 'file:repository' } + maven { url 'repository' } } configurations.all { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/configure-platform.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-platform.gradle.kts similarity index 82% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/configure-platform.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-platform.gradle.kts index 30cfc8ab044b..168d758dd9f7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/configure-platform.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-platform.gradle.kts @@ -1,6 +1,6 @@ plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } // tag::configure-platform[] @@ -15,7 +15,7 @@ dependencies { repositories { maven { - url = uri("file:repository") + url = uri("repository") } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/custom-version-with-platform.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/custom-version-with-platform.gradle similarity index 85% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/custom-version-with-platform.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/custom-version-with-platform.gradle index d877c3df16df..5cb4d2f742db 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/custom-version-with-platform.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/custom-version-with-platform.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } dependencies { @@ -9,7 +9,7 @@ dependencies { } repositories { - maven { url 'file:repository' } + maven { url 'repository' } } configurations.all { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/custom-version-with-platform.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/custom-version-with-platform.gradle.kts similarity index 85% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/custom-version-with-platform.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/custom-version-with-platform.gradle.kts index a0262204e945..0a25ae948cbb 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/custom-version-with-platform.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/custom-version-with-platform.gradle.kts @@ -1,6 +1,6 @@ plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } dependencies { @@ -10,7 +10,7 @@ dependencies { repositories { maven { - url = uri("file:repository") + url = uri("repository") } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/custom-version.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/custom-version.gradle similarity index 81% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/custom-version.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/custom-version.gradle index ab3d25436f4f..017b04e2028a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/custom-version.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/custom-version.gradle @@ -1,5 +1,5 @@ plugins { - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } apply plugin: 'io.spring.dependency-management' @@ -19,7 +19,7 @@ ext['slf4j.version'] = '1.7.20' // end::custom-version[] repositories { - maven { url 'file:repository' } + maven { url 'repository' } } task slf4jVersion { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/custom-version.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/custom-version.gradle.kts similarity index 85% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/custom-version.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/custom-version.gradle.kts index a8359facf42d..a589529ef833 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/custom-version.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/custom-version.gradle.kts @@ -1,7 +1,7 @@ import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension plugins { - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } apply(plugin = "io.spring.dependency-management") @@ -12,7 +12,7 @@ extra["slf4j.version"] = "1.7.20" repositories { maven { - url = uri("file:repository") + url = uri("repository") } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/depend-on-plugin-milestone.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-milestone.gradle similarity index 87% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/depend-on-plugin-milestone.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-milestone.gradle index 7aa042b4c673..cfafad11759e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/depend-on-plugin-milestone.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-milestone.gradle @@ -4,6 +4,6 @@ buildscript { } dependencies { - classpath 'org.springframework.boot:spring-boot-gradle-plugin:{gradle-project-version}' + classpath 'org.springframework.boot:spring-boot-gradle-plugin:{version-spring-boot}' } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-release.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-release.gradle new file mode 100644 index 000000000000..93b9e3036d24 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-release.gradle @@ -0,0 +1,3 @@ +plugins { + id 'org.springframework.boot' version '{version-spring-boot}' apply false +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-release.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-release.gradle.kts new file mode 100644 index 000000000000..66f15eee2f46 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-release.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("org.springframework.boot") version "{version-spring-boot}" apply false +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/depend-on-plugin-snapshot.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-snapshot.gradle similarity index 87% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/depend-on-plugin-snapshot.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-snapshot.gradle index 82b97382ce66..94b846d23da5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/depend-on-plugin-snapshot.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-snapshot.gradle @@ -4,6 +4,6 @@ buildscript { } dependencies { - classpath 'org.springframework.boot:spring-boot-gradle-plugin:{gradle-project-version}' + classpath 'org.springframework.boot:spring-boot-gradle-plugin:{version-spring-boot}' } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/dependencies.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/dependencies.gradle similarity index 80% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/dependencies.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/dependencies.gradle index 64e49cc30790..1efe1a62f169 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/dependencies.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/dependencies.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } apply plugin: 'io.spring.dependency-management' diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/dependencies.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/dependencies.gradle.kts similarity index 80% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/dependencies.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/dependencies.gradle.kts index c1392e1cfe2e..bcdca3fca518 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/dependencies.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/dependencies.gradle.kts @@ -1,6 +1,6 @@ plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } apply(plugin = "io.spring.dependency-management") diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/application-plugin-main-class.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/application-plugin-main-class.gradle new file mode 100644 index 000000000000..5c9d59a7fed9 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/application-plugin-main-class.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java' + id 'application' + id 'org.springframework.boot' version '{version-spring-boot}' +} + +// tag::main-class[] +application { + mainClass = 'com.example.ExampleApplication' +} +// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/application-plugin-main-class.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/application-plugin-main-class.gradle.kts new file mode 100644 index 000000000000..5bfa89ec08a1 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/application-plugin-main-class.gradle.kts @@ -0,0 +1,11 @@ +plugins { + java + application + id("org.springframework.boot") version "{version-spring-boot}" +} + +// tag::main-class[] +application { + mainClass.set("com.example.ExampleApplication") +} +// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-bind-caches.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-bind-caches.gradle new file mode 100644 index 000000000000..34bf4bc0eade --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-bind-caches.gradle @@ -0,0 +1,36 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version-spring-boot}' +} + +tasks.named("bootJar") { + mainClass = 'com.example.ExampleApplication' +} + +// tag::caches[] +tasks.named("bootBuildImage") { + buildWorkspace { + bind { + source = "/tmp/cache-${rootProject.name}.work" + } + } + buildCache { + bind { + source = "/tmp/cache-${rootProject.name}.build" + } + } + launchCache { + bind { + source = "/tmp/cache-${rootProject.name}.launch" + } + } +} +// end::caches[] + +tasks.register("bootBuildImageCaches") { + doFirst { + bootBuildImage.buildWorkspace.asCache().with { print "buildWorkspace=$source" } + bootBuildImage.buildCache.asCache().with { println "buildCache=$source" } + bootBuildImage.launchCache.asCache().with { println "launchCache=$source" } + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-bind-caches.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-bind-caches.gradle.kts new file mode 100644 index 000000000000..4b7dac528828 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-bind-caches.gradle.kts @@ -0,0 +1,34 @@ +import org.springframework.boot.gradle.tasks.bundling.BootBuildImage + +plugins { + java + id("org.springframework.boot") version "{version-spring-boot}" +} + +// tag::caches[] +tasks.named("bootBuildImage") { + buildWorkspace { + bind { + source.set("/tmp/cache-${rootProject.name}.work") + } + } + buildCache { + bind { + source.set("/tmp/cache-${rootProject.name}.build") + } + } + launchCache { + bind { + source.set("/tmp/cache-${rootProject.name}.launch") + } + } +} +// end::caches[] + +tasks.register("bootBuildImageCaches") { + doFirst { + println("buildWorkspace=" + tasks.getByName("bootBuildImage").buildWorkspace.asCache().bind.source) + println("buildCache=" + tasks.getByName("bootBuildImage").buildCache.asCache().bind.source) + println("launchCache=" + tasks.getByName("bootBuildImage").launchCache.asCache().bind.source) + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-builder.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-builder.gradle similarity index 86% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-builder.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-builder.gradle index cf0edb18f6da..9ccbb2b1689e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-builder.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-builder.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-builder.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-builder.gradle.kts similarity index 90% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-builder.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-builder.gradle.kts index 7e195dbd346d..7f301c454b89 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-builder.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-builder.gradle.kts @@ -3,7 +3,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-buildpacks.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-buildpacks.gradle similarity index 83% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-buildpacks.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-buildpacks.gradle index 9d50b545e765..fe40ca14a3c6 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-buildpacks.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-buildpacks.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } // tag::buildpacks[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-buildpacks.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-buildpacks.gradle.kts similarity index 87% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-buildpacks.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-buildpacks.gradle.kts index be45f176c917..e3a6631098ea 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-buildpacks.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-buildpacks.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } // tag::buildpacks[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-caches.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-caches.gradle similarity index 88% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-caches.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-caches.gradle index 9ce86470cf0e..448be195c1a0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-caches.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-caches.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-caches.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-caches.gradle.kts similarity index 90% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-caches.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-caches.gradle.kts index e5b1254567ff..6b7c7e3c01dd 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-caches.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-caches.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } // tag::caches[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-token.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-auth-token.gradle similarity index 85% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-token.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-auth-token.gradle index 9f3bf0ed3371..7e30529f8265 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-token.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-auth-token.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-token.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-auth-token.gradle.kts similarity index 89% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-token.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-auth-token.gradle.kts index 169c98e26b7d..223855bfc490 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-token.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-auth-token.gradle.kts @@ -3,7 +3,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-user.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-auth-user.gradle similarity index 91% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-user.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-auth-user.gradle index 7222f938019f..4c965307af49 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-user.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-auth-user.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-user.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-auth-user.gradle.kts similarity index 93% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-user.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-auth-user.gradle.kts index 92f8c51446c2..4e36e9c5e739 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-user.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-auth-user.gradle.kts @@ -3,7 +3,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-host-colima.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host-colima.gradle similarity index 85% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-host-colima.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host-colima.gradle index bf21d5a1de4d..5ac63833d1af 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-host-colima.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host-colima.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-host-colima.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host-colima.gradle.kts similarity index 89% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-host-colima.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host-colima.gradle.kts index 2a227248d237..37e44ea9ac88 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-host-colima.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host-colima.gradle.kts @@ -3,7 +3,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-host-podman.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host-podman.gradle similarity index 87% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-host-podman.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host-podman.gradle index c6c129cc9bb8..6d23f625833d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-host-podman.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host-podman.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-host-podman.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host-podman.gradle.kts similarity index 91% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-host-podman.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host-podman.gradle.kts index b46e24abe433..130c9225efd7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-host-podman.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host-podman.gradle.kts @@ -3,7 +3,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-host.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host.gradle similarity index 89% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-host.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host.gradle index de196f7d26cf..2d6b15cb3001 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-host.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-host.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host.gradle.kts similarity index 92% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-host.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host.gradle.kts index 804498bcb22d..8bad8550fd44 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-host.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host.gradle.kts @@ -3,7 +3,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env-proxy.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env-proxy.gradle similarity index 83% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env-proxy.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env-proxy.gradle index 8ad32e87a79c..250bffe042d2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env-proxy.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env-proxy.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } // tag::env[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env-proxy.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env-proxy.gradle.kts similarity index 75% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env-proxy.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env-proxy.gradle.kts index 26f62d6b78ed..c99d5d71e69c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env-proxy.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env-proxy.gradle.kts @@ -2,12 +2,12 @@ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } // tag::env[] tasks.named("bootBuildImage") { - environment.set(mapOf("HTTP_PROXY" to "http://proxy.example.com", + environment.putAll(mapOf("HTTP_PROXY" to "http://proxy.example.com", "HTTPS_PROXY" to "https://proxy.example.com")) } // end::env[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env-runtime.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env-runtime.gradle similarity index 87% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env-runtime.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env-runtime.gradle index b58a1089d956..92e19500f320 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env-runtime.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env-runtime.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env-runtime.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env-runtime.gradle.kts similarity index 84% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env-runtime.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env-runtime.gradle.kts index f4ebbe4e2cda..8683d952f079 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env-runtime.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env-runtime.gradle.kts @@ -2,12 +2,12 @@ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } // tag::env-runtime[] tasks.named("bootBuildImage") { - environment.set(mapOf( + environment.putAll(mapOf( "BPE_DELIM_JAVA_TOOL_OPTIONS" to " ", "BPE_APPEND_JAVA_TOOL_OPTIONS" to "-XX:+HeapDumpOnOutOfMemoryError" )) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env.gradle similarity index 80% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env.gradle index 4f6f6db2eb8a..3a77c7aa45b6 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } // tag::env[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env.gradle.kts new file mode 100644 index 000000000000..06b6b6c498a5 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env.gradle.kts @@ -0,0 +1,21 @@ +import org.springframework.boot.gradle.tasks.bundling.BootBuildImage + +plugins { + java + id("org.springframework.boot") version "{version-spring-boot}" +} + +// tag::env[] +tasks.named("bootBuildImage") { + environment.put("BP_JVM_VERSION", "17") +} +// end::env[] + +tasks.register("bootBuildImageEnvironment") { + doFirst { + for((name, value) in tasks.getByName("bootBuildImage").environment.get()) { + print(name + "=" + value) + } + } +} + diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-name.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-name.gradle similarity index 79% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-name.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-name.gradle index 7d87b38d0e3b..24bcdcc19dec 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-name.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-name.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } // tag::image-name[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-name.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-name.gradle.kts similarity index 84% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-name.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-name.gradle.kts index 01f67fe9532d..2508b4079567 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-name.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-name.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } // tag::image-name[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-publish.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-publish.gradle similarity index 86% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-publish.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-publish.gradle index 40631d0fc098..290b346881ef 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-publish.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-publish.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-publish.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-publish.gradle.kts similarity index 90% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-publish.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-publish.gradle.kts index bcc856efa2ca..8c5ac611a12a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-publish.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-publish.gradle.kts @@ -3,7 +3,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-and-jar-classifiers.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-and-jar-classifiers.gradle similarity index 78% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-and-jar-classifiers.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-and-jar-classifiers.gradle index 024266d70125..e1da8d495fe1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-and-jar-classifiers.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-and-jar-classifiers.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } // tag::classifiers[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-and-jar-classifiers.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-and-jar-classifiers.gradle.kts similarity index 83% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-and-jar-classifiers.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-and-jar-classifiers.gradle.kts index f54362da9c40..c124c49d4bb1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-and-jar-classifiers.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-and-jar-classifiers.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } // tag::classifiers[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-custom-launch-script.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-custom-launch-script.gradle similarity index 78% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-custom-launch-script.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-custom-launch-script.gradle index da6918c0762f..8d17c55d263b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-custom-launch-script.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-custom-launch-script.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-custom-launch-script.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-custom-launch-script.gradle.kts similarity index 82% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-custom-launch-script.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-custom-launch-script.gradle.kts index a3e15f1f5eea..daeba8a2360f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-custom-launch-script.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-custom-launch-script.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-include-launch-script.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-include-launch-script.gradle similarity index 75% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-include-launch-script.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-include-launch-script.gradle index c1f3d348a844..f90dfd2e5ceb 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-include-launch-script.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-include-launch-script.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-include-launch-script.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-include-launch-script.gradle.kts similarity index 80% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-include-launch-script.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-include-launch-script.gradle.kts index 7ffdf42e4f96..fa10543b3737 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-include-launch-script.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-include-launch-script.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-launch-script-properties.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-launch-script-properties.gradle similarity index 79% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-launch-script-properties.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-launch-script-properties.gradle index 6f1df662beb4..dc5abae19551 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-launch-script-properties.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-launch-script-properties.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-launch-script-properties.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-launch-script-properties.gradle.kts similarity index 83% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-launch-script-properties.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-launch-script-properties.gradle.kts index b3e4206ca958..ec549aebd5b4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-launch-script-properties.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-launch-script-properties.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-custom.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-custom.gradle similarity index 90% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-custom.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-custom.gradle index ba0dde4dc051..bfc5b33c6d1a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-custom.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-custom.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-custom.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-custom.gradle.kts similarity index 91% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-custom.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-custom.gradle.kts index d5d78a40811b..af7b0418d8aa 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-custom.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-custom.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-disabled.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-disabled.gradle new file mode 100644 index 000000000000..f2558ec771b1 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-disabled.gradle @@ -0,0 +1,16 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version-spring-boot}' +} + +tasks.named("bootJar") { + mainClass = 'com.example.ExampleApplication' +} + +// tag::layered[] +tasks.named("bootJar") { + layered { + enabled = false + } +} +// end::layered[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-disabled.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-disabled.gradle.kts similarity index 80% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-disabled.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-disabled.gradle.kts index 65d2c9477b32..9c17e6265dfe 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-disabled.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-disabled.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-exclude-tools.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-exclude-tools.gradle new file mode 100644 index 000000000000..609bfddfdf67 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-exclude-tools.gradle @@ -0,0 +1,14 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version-spring-boot}' +} + +tasks.named("bootJar") { + mainClass = 'com.example.ExampleApplication' +} + +// tag::layered[] +tasks.named("bootJar") { + includeTools = false +} +// end::layered[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-exclude-tools.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-exclude-tools.gradle.kts new file mode 100644 index 000000000000..a524deb13087 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-exclude-tools.gradle.kts @@ -0,0 +1,16 @@ +import org.springframework.boot.gradle.tasks.bundling.BootJar + +plugins { + java + id("org.springframework.boot") version "{version-spring-boot}" +} + +tasks.named("bootJar") { + mainClass.set("com.example.ExampleApplication") +} + +// tag::layered[] +tasks.named("bootJar") { + includeTools.set(false) +} +// end::layered[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-main-class.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-main-class.gradle new file mode 100644 index 000000000000..e53d37652441 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-main-class.gradle @@ -0,0 +1,10 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version-spring-boot}' +} + +// tag::main-class[] +tasks.named("bootJar") { + mainClass = 'com.example.ExampleApplication' +} +// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-main-class.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-main-class.gradle.kts similarity index 75% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-main-class.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-main-class.gradle.kts index 48aea9b011a4..0e9bcedc3b9d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-main-class.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-main-class.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } // tag::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-manifest-main-class.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-manifest-main-class.gradle new file mode 100644 index 000000000000..5da518696ab6 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-manifest-main-class.gradle @@ -0,0 +1,12 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version-spring-boot}' +} + +// tag::main-class[] +tasks.named("bootJar") { + manifest { + attributes 'Start-Class': 'com.example.ExampleApplication' + } +} +// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-manifest-main-class.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-manifest-main-class.gradle.kts similarity index 78% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-manifest-main-class.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-manifest-main-class.gradle.kts index a79bc048cb7f..5b5aea356075 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-manifest-main-class.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-manifest-main-class.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } // tag::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-requires-unpack.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-requires-unpack.gradle similarity index 82% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-requires-unpack.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-requires-unpack.gradle index 3274bdb59461..c5dabf9615c1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-requires-unpack.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-requires-unpack.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } repositories { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-requires-unpack.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-requires-unpack.gradle.kts similarity index 85% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-requires-unpack.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-requires-unpack.gradle.kts index 4f68e068e6a3..9cac7486301a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-requires-unpack.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-requires-unpack.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } repositories { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-war-include-devtools.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-war-include-devtools.gradle similarity index 82% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-war-include-devtools.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-war-include-devtools.gradle index 1f12601ca74f..1b70a6df9906 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-war-include-devtools.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-war-include-devtools.gradle @@ -1,6 +1,6 @@ plugins { id 'war' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootWar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-war-include-devtools.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-war-include-devtools.gradle.kts similarity index 85% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-war-include-devtools.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-war-include-devtools.gradle.kts index c151efe9e5ec..6d2dc3aa27a4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-war-include-devtools.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-war-include-devtools.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootWar plugins { war - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootWar") { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-war-properties-launcher.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-war-properties-launcher.gradle new file mode 100644 index 000000000000..73e6b0a7339c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-war-properties-launcher.gradle @@ -0,0 +1,16 @@ +plugins { + id 'war' + id 'org.springframework.boot' version '{version-spring-boot}' +} + +tasks.named("bootWar") { + mainClass = 'com.example.ExampleApplication' +} + +// tag::properties-launcher[] +tasks.named("bootWar") { + manifest { + attributes 'Main-Class': 'org.springframework.boot.loader.launch.PropertiesLauncher' + } +} +// end::properties-launcher[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-war-properties-launcher.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-war-properties-launcher.gradle.kts new file mode 100644 index 000000000000..ae5e7fb7395a --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-war-properties-launcher.gradle.kts @@ -0,0 +1,18 @@ +import org.springframework.boot.gradle.tasks.bundling.BootWar + +plugins { + war + id("org.springframework.boot") version "{version-spring-boot}" +} + +tasks.named("bootWar") { + mainClass.set("com.example.ExampleApplication") +} + +// tag::properties-launcher[] +tasks.named("bootWar") { + manifest { + attributes("Main-Class" to "org.springframework.boot.loader.launch.PropertiesLauncher") + } +} +// end::properties-launcher[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/only-boot-jar.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/only-boot-jar.gradle new file mode 100644 index 000000000000..d13d367a18c3 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/only-boot-jar.gradle @@ -0,0 +1,14 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version-spring-boot}' +} + +// tag::disable-jar[] +tasks.named("jar") { + enabled = false +} +// end::disable-jar[] + +tasks.named("bootJar") { + mainClass = 'com.example.Application' +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/only-boot-jar.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/only-boot-jar.gradle.kts similarity index 78% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/only-boot-jar.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/only-boot-jar.gradle.kts index c15f189fdca4..efac6cda5df6 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/only-boot-jar.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/only-boot-jar.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } // tag::disable-jar[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/spring-boot-dsl-main-class.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/spring-boot-dsl-main-class.gradle new file mode 100644 index 000000000000..d352f290070b --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/spring-boot-dsl-main-class.gradle @@ -0,0 +1,10 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version-spring-boot}' +} + +// tag::main-class[] +springBoot { + mainClass = 'com.example.ExampleApplication' +} +// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/spring-boot-dsl-main-class.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/spring-boot-dsl-main-class.gradle.kts new file mode 100644 index 000000000000..decaa3c07038 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/spring-boot-dsl-main-class.gradle.kts @@ -0,0 +1,10 @@ +plugins { + java + id("org.springframework.boot") version "{version-spring-boot}" +} + +// tag::main-class[] +springBoot { + mainClass.set("com.example.ExampleApplication") +} +// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/war-container-dependency.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/war-container-dependency.gradle similarity index 80% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/war-container-dependency.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/war-container-dependency.gradle index ad0f0b53e0fa..b89a09c0a33a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/war-container-dependency.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/war-container-dependency.gradle @@ -1,6 +1,6 @@ plugins { id 'war' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } apply plugin: 'io.spring.dependency-management' diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/war-container-dependency.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/war-container-dependency.gradle.kts similarity index 80% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/war-container-dependency.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/war-container-dependency.gradle.kts index 7ea6688bcb05..2463bb906268 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/war-container-dependency.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/war-container-dependency.gradle.kts @@ -1,6 +1,6 @@ plugins { war - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } apply(plugin = "io.spring.dependency-management") diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/publishing/maven-publish.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/publishing/maven-publish.gradle similarity index 85% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/publishing/maven-publish.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/publishing/maven-publish.gradle index 418c5f101fa6..fa4ad521a4e4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/publishing/maven-publish.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/publishing/maven-publish.gradle @@ -1,7 +1,7 @@ plugins { id 'java' id 'maven-publish' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } // tag::publishing[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/publishing/maven-publish.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/publishing/maven-publish.gradle.kts similarity index 87% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/publishing/maven-publish.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/publishing/maven-publish.gradle.kts index f11d2344cf65..9e8365a2a239 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/publishing/maven-publish.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/publishing/maven-publish.gradle.kts @@ -1,7 +1,7 @@ plugins { java `maven-publish` - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } // tag::publishing[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/application-plugin-main-class-name.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/application-plugin-main-class-name.gradle new file mode 100644 index 000000000000..43320567a2f3 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/application-plugin-main-class-name.gradle @@ -0,0 +1,12 @@ +plugins { + id 'java' + id 'application' + id 'org.springframework.boot' version '{version-spring-boot}' +} + +// tag::main-class[] +application { + mainClass = 'com.example.ExampleApplication' +} +// end::main-class[] + diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/application-plugin-main-class-name.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/application-plugin-main-class-name.gradle.kts new file mode 100644 index 000000000000..5bfa89ec08a1 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/application-plugin-main-class-name.gradle.kts @@ -0,0 +1,11 @@ +plugins { + java + application + id("org.springframework.boot") version "{version-spring-boot}" +} + +// tag::main-class[] +application { + mainClass.set("com.example.ExampleApplication") +} +// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-disable-optimized-launch.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-disable-optimized-launch.gradle similarity index 75% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-disable-optimized-launch.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-disable-optimized-launch.gradle index fc3811dba155..6e42344189e2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-disable-optimized-launch.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-disable-optimized-launch.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } // tag::launch[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-disable-optimized-launch.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-disable-optimized-launch.gradle.kts similarity index 81% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-disable-optimized-launch.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-disable-optimized-launch.gradle.kts index 2b8af1dbde48..19ee65434467 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-disable-optimized-launch.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-disable-optimized-launch.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.tasks.run.BootRun plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } // tag::launch[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-main.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-main.gradle similarity index 76% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-main.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-main.gradle index 292a53689777..a986be93c48c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-main.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-main.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } // tag::main[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-main.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-main.gradle.kts similarity index 82% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-main.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-main.gradle.kts index 14039f002f72..7a9eb36dda2b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-main.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-main.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.tasks.run.BootRun plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } // tag::main[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-source-resources.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-source-resources.gradle similarity index 77% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-source-resources.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-source-resources.gradle index e09dac63f809..43d773f77c5f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-source-resources.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-source-resources.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' + id 'org.springframework.boot' version '{version-spring-boot}' } // tag::source-resources[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-source-resources.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-source-resources.gradle.kts similarity index 82% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-source-resources.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-source-resources.gradle.kts index 73a907b4f35c..8db0971184ce 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-source-resources.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-source-resources.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.tasks.run.BootRun plugins { java - id("org.springframework.boot") version "{gradle-project-version}" + id("org.springframework.boot") version "{version-spring-boot}" } // tag::source-resources[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-system-property.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-system-property.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-system-property.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-system-property.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-system-property.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-system-property.gradle.kts similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/boot-run-system-property.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-system-property.gradle.kts diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/spring-boot-dsl-main-class-name.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/spring-boot-dsl-main-class-name.gradle new file mode 100644 index 000000000000..a8ff734d92ad --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/spring-boot-dsl-main-class-name.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java' + id 'application' + id 'org.springframework.boot' version '{version-spring-boot}' +} + +// tag::main-class[] +springBoot { + mainClass = 'com.example.ExampleApplication' +} +// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/spring-boot-dsl-main-class-name.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/spring-boot-dsl-main-class-name.gradle.kts new file mode 100644 index 000000000000..23f44c99d344 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/spring-boot-dsl-main-class-name.gradle.kts @@ -0,0 +1,11 @@ +plugins { + java + application + id("org.springframework.boot") version "{version-spring-boot}" +} + +// tag::main-class[] +springBoot { + mainClass.set("com.example.ExampleApplication") +} +// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/aot.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/aot.adoc new file mode 100644 index 000000000000..95eac8a0e0bb --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/aot.adoc @@ -0,0 +1,61 @@ +[[aot]] += Ahead-of-Time Processing + +Spring AOT is a process that analyzes your code at build-time in order to generate an optimized version of it. +It is most often used to help generate GraalVM native images. + +The Spring Boot Gradle plugin provides tasks that can be used to perform AOT processing on both application and test code. +The tasks are configured automatically when the {url-native-build-tools-docs-gradle-plugin}[GraalVM Native Image plugin] is applied: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$aot/apply-native-image-plugin.gradle[] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$aot/apply-native-image-plugin.gradle.kts[] +---- +====== + + +.Groovy +---- +---- + +.Kotlin +---- +---- + + + +[[aot.processing-applications]] +== Processing Applications + +Based on your `@SpringBootApplication`-annotated main class, the `processAot` task generates a persistent view of the beans that are going to be contributed at runtime in a way that bean instantiation is as straightforward as possible. +Additional post-processing of the factory is possible using callbacks. +For instance, these are used to generate the necessary reflection configuration that GraalVM needs to initialize the context in a native image. + +As the `BeanFactory` is fully prepared at build-time, conditions are also evaluated. +This has an important difference compared to what a regular Spring Boot application does at runtime. +For instance, if you want to opt-in or opt-out for certain features, you need to configure the environment used at build time to do so. +To this end, the `processAot` task is a {url-gradle-dsl}/org.gradle.api.tasks.JavaExec.html[`JavaExec`] task and can be configured with environment variables, system properties, and arguments as needed. + +The `nativeCompile` task of the GraalVM Native Image plugin is automatically configured to use the output of the `processAot` task. + + + +[[aot.processing-tests]] +== Processing Tests + +The AOT engine can be applied to JUnit 5 tests that use Spring's Test Context Framework. +Suitable tests are processed by the `processTestAot` task to generate `ApplicationContextInitializer` code. +As with application AOT processing, the `BeanFactory` is fully prepared at build-time. +As with `processAot`, the `processTestAot` task is `JavaExec` subclass and can be configured as needed to influence this processing. + +The `nativeTest` task of the GraalVM Native Image plugin is automatically configured to use the output of the `processAot` and `processTestAot` tasks. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/getting-started.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/getting-started.adoc new file mode 100644 index 000000000000..bc55314e63f0 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/getting-started.adoc @@ -0,0 +1,128 @@ +[[getting-started]] += Getting Started + +To get started with the plugin it needs to be applied to your project. + +ifeval::["{artifact-release-type}" == "release"] +The plugin is https://plugins.gradle.org/plugin/org.springframework.boot[published to Gradle's plugin portal] and can be applied using the `plugins` block: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$getting-started/apply-plugin-release.gradle[] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$getting-started/apply-plugin-release.gradle.kts[] +---- +====== +endif::[] + +ifeval::["{artifact-release-type}" == "milestone"] +The plugin is published to the Spring milestones repository. +Gradle can be configured to use the milestones repository and the plugin can then be applied using the `plugins` block. +To configure Gradle to use the milestones repository, add the following to your `settings.gradle` (Groovy) or `settings.gradle.kts` (Kotlin): + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$getting-started/milestone-settings.gradle[] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$getting-started/milestone-settings.gradle.kts[] +---- +====== + +The plugin can then be applied using the `plugins` block: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$getting-started/apply-plugin-release.gradle[] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$getting-started/apply-plugin-release.gradle.kts[] +---- +====== +endif::[] + +ifeval::["{artifact-release-type}" == "snapshot"] +The plugin is published to the Spring snapshots repository. +Gradle can be configured to use the snapshots repository and the plugin can then be applied using the `plugins` block. +To configure Gradle to use the snapshots repository, add the following to your `settings.gradle` (Groovy) or `settings.gradle.kts` (Kotlin): + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$getting-started/snapshot-settings.gradle[] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$getting-started/snapshot-settings.gradle.kts[] +---- +====== + +The plugin can then be applied using the `plugins` block: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$getting-started/apply-plugin-release.gradle[] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$getting-started/apply-plugin-release.gradle.kts[] +---- +====== +endif::[] + +Applied in isolation the plugin makes few changes to a project. +Instead, the plugin detects when certain other plugins are applied and reacts accordingly. +For example, when the `java` plugin is applied a task for building an executable jar is automatically configured. +A typical Spring Boot project will apply the {url-gradle-docs-groovy-plugin}[`groovy`], {url-gradle-docs-java-plugin}[`java`], or {url-kotlin-docs-kotlin-plugin}[`org.jetbrains.kotlin.jvm`] plugin as a minimum and also use the {url-dependency-management-plugin-site}[`io.spring.dependency-management`] plugin or Gradle's native bom support for dependency management. +For example: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$getting-started/typical-plugins.gradle[tags=apply] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$getting-started/typical-plugins.gradle.kts[tags=apply] +---- +====== + +To learn more about how the Spring Boot plugin behaves when other plugins are applied please see the section on xref:reacting.adoc[reacting to other plugins]. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/index.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/index.adoc new file mode 100644 index 000000000000..9dc14356b5b6 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/index.adoc @@ -0,0 +1,8 @@ +[[gradle-plugin]] += Gradle Plugin + +The Spring Boot Gradle Plugin provides Spring Boot support in https://gradle.org[Gradle]. +It allows you to package executable jar or war archives, run Spring Boot applications, and use the dependency management provided by `spring-boot-dependencies`. +Spring Boot's Gradle plugin requires Gradle 7.x (7.5 or later) or 8.x and can be used with Gradle's {url-gradle-docs}/configuration_cache.html[configuration cache]. + +In addition to this user guide, xref:api/java/index.html[API documentation] is also available. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/integrating-with-actuator.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/integrating-with-actuator.adoc new file mode 100644 index 000000000000..4cf5ea38402d --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/integrating-with-actuator.adoc @@ -0,0 +1,115 @@ +[[integrating-with-actuator]] += Integrating with Actuator + + + +[[integrating-with-actuator.build-info]] +== Generating Build Information + +Spring Boot Actuator's `info` endpoint automatically publishes information about your build in the presence of a `META-INF/build-info.properties` file. +A {apiref-gradle-plugin-boot-build-info}[`BuildInfo`] task is provided to generate this file. +The easiest way to use the task is through the plugin's DSL: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$integrating-with-actuator/build-info-basic.gradle[tags=build-info] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$integrating-with-actuator/build-info-basic.gradle.kts[tags=build-info] +---- +====== + +This will configure a {apiref-gradle-plugin-boot-build-info}[`BuildInfo`] task named `bootBuildInfo` and, if it exists, make the Java plugin's `classes` task depend upon it. +The task's destination directory will be `META-INF` in the output directory of the main source set's resources (typically `build/resources/main`). + +By default, the generated build information is derived from the project: + +|=== +| Property | Default value + +| `build.artifact` +| The base name of the `bootJar` or `bootWar` task + +| `build.group` +| The group of the project + +| `build.name` +| The name of the project + +| `build.version` +| The version of the project + +| `build.time` +| The time at which the project is being built + +|=== + +The properties can be customized using the DSL: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$integrating-with-actuator/build-info-custom-values.gradle[tags=custom-values] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$integrating-with-actuator/build-info-custom-values.gradle.kts[tags=custom-values] +---- +====== + +To exclude any of the default properties from the generated build information, add its name to the excludes. +For example, the `time` property can be excluded as follows: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$integrating-with-actuator/build-info-exclude-time.gradle[tags=exclude-time] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$integrating-with-actuator/build-info-exclude-time.gradle.kts[tags=exclude-time] +---- +====== + +The default value for `build.time` is the instant at which the project is being built. +A side-effect of this is that the task will never be up-to-date. +As a result, builds will take longer as more tasks, including the project's tests, will have to be executed. +Another side-effect is that the task's output will always change and, therefore, the build will not be truly repeatable. +If you value build performance or repeatability more highly than the accuracy of the `build.time` property, exclude the `time` property as shown in the preceding example. + +Additional properties can also be added to the build information: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$integrating-with-actuator/build-info-additional.gradle[tags=additional] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$integrating-with-actuator/build-info-additional.gradle.kts[tags=additional] +---- +====== + +An additional property's value can be computed lazily by using a `Provider`. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/introduction.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/introduction.adoc new file mode 100644 index 000000000000..62735dcc6e1a --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/introduction.adoc @@ -0,0 +1,8 @@ +[[introduction]] += Introduction + +The Spring Boot Gradle Plugin provides Spring Boot support in https://gradle.org[Gradle]. +It allows you to package executable jar or war archives, run Spring Boot applications, and use the dependency management provided by `spring-boot-dependencies`. +Spring Boot's Gradle plugin requires Gradle 7.x (7.6.4 or later) or 8.x (8.3 or later) and can be used with Gradle's {url-gradle-docs}/configuration_cache.html[configuration cache]. + +In addition to this user guide, xref:api/java/index.html[API documentation] is also available. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/managing-dependencies.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/managing-dependencies.adoc new file mode 100644 index 000000000000..d0c9cf033a46 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/managing-dependencies.adoc @@ -0,0 +1,223 @@ +[[managing-dependencies]] += Managing Dependencies + +To manage dependencies in your Spring Boot application, you can either apply the {url-dependency-management-plugin-site}[`io.spring.dependency-management`] plugin or use Gradle's native bom support. +The primary benefit of the former is that it offers property-based customization of managed versions, while using the latter will likely result in faster builds. + + + +[[managing-dependencies.dependency-management-plugin]] +== Managing Dependencies with the Dependency Management Plugin + +When you apply the {url-dependency-management-plugin-site}[`io.spring.dependency-management`] plugin, Spring Boot's plugin will automatically xref:reacting.adoc#reacting-to-other-plugins.dependency-management[import the `spring-boot-dependencies` bom] from the version of Spring Boot that you are using. +This provides a similar dependency management experience to the one that's enjoyed by Maven users. +For example, it allows you to omit version numbers when declaring dependencies that are managed in the bom. +To make use of this functionality, declare dependencies in the usual way but omit the version number: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/dependencies.gradle[tags=dependencies] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/dependencies.gradle.kts[tags=dependencies] +---- +====== + + + +[[managing-dependencies.dependency-management-plugin.customizing]] +=== Customizing Managed Versions + +The `spring-boot-dependencies` bom that is automatically imported when the dependency management plugin is applied uses properties to control the versions of the dependencies that it manages. +Browse the xref:appendix:dependency-versions/properties.adoc[Dependency Versions Properties] section in the Spring Boot reference for a complete list of these properties. + +To customize a managed version you set its corresponding property. +For example, to customize the version of SLF4J which is controlled by the `slf4j.version` property: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/custom-version.gradle[tags=custom-version] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/custom-version.gradle.kts[tags=custom-version] +---- +====== + +WARNING: Each Spring Boot release is designed and tested against a specific set of third-party dependencies. +Overriding versions may cause compatibility issues and should be done with care. + + + +[[managing-dependencies.dependency-management-plugin.using-in-isolation]] +=== Using Spring Boot's Dependency Management in Isolation + +Spring Boot's dependency management can be used in a project without applying Spring Boot's plugin to that project. +The `SpringBootPlugin` class provides a `BOM_COORDINATES` constant that can be used to import the bom without having to know its group ID, artifact ID, or version. + +First, configure the project to depend on the Spring Boot plugin but do not apply it: + +ifeval::["{artifact-release-type}" == "release"] +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/depend-on-plugin-release.gradle[] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/depend-on-plugin-release.gradle.kts[] +---- +====== +endif::[] + +ifeval::["{artifact-release-type}" == "milestone"] +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/depend-on-plugin-milestone.gradle[] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/depend-on-plugin-release.gradle.kts[] +---- +====== +endif::[] + +ifeval::["{artifact-release-type}" == "snapshot"] +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/depend-on-plugin-snapshot.gradle[] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/depend-on-plugin-release.gradle.kts[] +---- +====== +endif::[] + +The Spring Boot plugin's dependency on the dependency management plugin means that you can use the dependency management plugin without having to declare a dependency on it. +This also means that you will automatically use the same version of the dependency management plugin as Spring Boot uses. + +Apply the dependency management plugin and then configure it to import Spring Boot's bom: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/configure-bom.gradle[tags=configure-bom] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/configure-bom.gradle.kts[tags=configure-bom] +---- +====== + +The Kotlin code above is a bit awkward. +That's because we're using the imperative way of applying the dependency management plugin. + +We can make the code less awkward by applying the plugin from the root parent project, or by using the `plugins` block as we're doing for the Spring Boot plugin. +A downside of this method is that it forces us to specify the version of the dependency management plugin: + +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/configure-bom-with-plugins.gradle.kts[tags=configure-bom] +---- + + + +[[managing-dependencies.dependency-management-plugin.learning-more]] +=== Learning More + +To learn more about the capabilities of the dependency management plugin, please refer to its {url-dependency-management-plugin-docs}[documentation]. + + + +[[managing-dependencies.gradle-bom-support]] +== Managing Dependencies with Gradle's Bom Support + +Gradle allows a bom to be used to manage a project's versions by declaring it as a `platform` or `enforcedPlatform` dependency. +A `platform` dependency treats the versions in the bom as recommendations and other versions and constraints in the dependency graph may cause a version of a dependency other than that declared in the bom to be used. +An `enforcedPlatform` dependency treats the versions in the bom as requirements and they will override any other version found in the dependency graph. + +The `SpringBootPlugin` class provides a `BOM_COORDINATES` constant that can be used to declare a dependency upon Spring Boot's bom without having to know its group ID, artifact ID, or version, as shown in the following example: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/configure-platform.gradle[tags=configure-platform] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/configure-platform.gradle.kts[tags=configure-platform] +---- +====== + +A platform or enforced platform will only constrain the versions of the configuration in which it has been declared or that extend from the configuration in which it has been declared. +As a result, in may be necessary to declare the same dependency in more than one configuration. + + + +[[managing-dependencies.gradle-bom-support.customizing]] +=== Customizing Managed Versions + +When using Gradle's bom support, you cannot use the properties from `spring-boot-dependencies` to control the versions of the dependencies that it manages. +Instead, you must use one of the mechanisms that Gradle provides. +One such mechanism is a resolution strategy. +SLF4J's modules are all in the `org.slf4j` group so their version can be controlled by configuring every dependency in that group to use a particular version, as shown in the following example: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/custom-version-with-platform.gradle[tags=custom-version] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/custom-version-with-platform.gradle.kts[tags=custom-version] +---- +====== + +WARNING: Each Spring Boot release is designed and tested against a specific set of third-party dependencies. +Overriding versions may cause compatibility issues and should be done with care. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/packaging-oci-image.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/packaging-oci-image.adoc new file mode 100644 index 000000000000..0738112f8bcc --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/packaging-oci-image.adoc @@ -0,0 +1,672 @@ +[[build-image]] += Packaging OCI Images + +The plugin can create an https://github.com/opencontainers/image-spec[OCI image] from a jar or war file using https://buildpacks.io[Cloud Native Buildpacks] (CNB). +Images can be built using the `bootBuildImage` task. + +NOTE: For security reasons, images build and run as non-root users. +See the {url-buildpacks-docs}/reference/spec/platform-api/#users[CNB specification] for more details. + +The task is automatically created when the `java` or `war` plugin is applied and is an instance of {apiref-gradle-plugin-boot-build-image}[`BootBuildImage`]. + + + +[[build-image.docker-daemon]] +== Docker Daemon + +The `bootBuildImage` task requires access to a Docker daemon. +The task will inspect local Docker CLI https://docs.docker.com/engine/reference/commandline/cli/#configuration-files[configuration files] to determine the current https://docs.docker.com/engine/context/working-with-contexts/[context] and use the context connection information to communicate with a Docker daemon. +If the current context can not be determined or the context does not have connection information, then the task will use a default local connection. +This works with https://docs.docker.com/install/[Docker Engine] on all supported platforms without configuration. + +Environment variables can be set to configure the `bootBuildImage` task to use an alternative local or remote connection. +The following table shows the environment variables and their values: + +|=== +| Environment variable | Description + +| DOCKER_CONFIG +| Location of Docker CLI https://docs.docker.com/engine/reference/commandline/cli/#configuration-files[configuration files] used to determine the current context (defaults to `$HOME/.docker`) + +| DOCKER_CONTEXT +| Name of a https://docs.docker.com/engine/context/working-with-contexts/[context] that should be used to retrieve host information from Docker CLI configuration files (overrides `DOCKER_HOST`) + +| DOCKER_HOST +| URL containing the host and port for the Docker daemon - for example `tcp://192.168.99.100:2376` + +| DOCKER_TLS_VERIFY +| Enable secure HTTPS protocol when set to `1` (optional) + +| DOCKER_CERT_PATH +| Path to certificate and key files for HTTPS (required if `DOCKER_TLS_VERIFY=1`, ignored otherwise) +|=== + +Docker daemon connection information can also be provided using `docker` properties in the plugin configuration. +The following table summarizes the available properties: + +|=== +| Property | Description + +| `context` +| Name of a https://docs.docker.com/engine/context/working-with-contexts/[context] that should be used to retrieve host information from Docker CLI https://docs.docker.com/engine/reference/commandline/cli/#configuration-files[configuration files] + +| `host` +| URL containing the host and port for the Docker daemon - for example `tcp://192.168.99.100:2376` + +| `tlsVerify` +| Enable secure HTTPS protocol when set to `true` (optional) + +| `certPath` +| Path to certificate and key files for HTTPS (required if `tlsVerify` is `true`, ignored otherwise) + +| `bindHostToBuilder` +| When `true`, the value of the `host` property will be provided to the container that is created for the CNB builder (optional) +|=== + +For more details, see also xref:packaging-oci-image.adoc#build-image.examples.docker[examples]. + + + +[[build-image.docker-registry]] +== Docker Registry + +If the Docker images specified by the `builder` or `runImage` properties are stored in a private Docker image registry that requires authentication, the authentication credentials can be provided using `docker.builderRegistry` properties. + +If the generated Docker image is to be published to a Docker image registry, the authentication credentials can be provided using `docker.publishRegistry` properties. + +Properties are provided for user authentication or identity token authentication. +Consult the documentation for the Docker registry being used to store images for further information on supported authentication methods. + +The following table summarizes the available properties for `docker.builderRegistry` and `docker.publishRegistry`: + +|=== +| Property | Description + +| `username` +| Username for the Docker image registry user. Required for user authentication. + +| `password` +| Password for the Docker image registry user. Required for user authentication. + +| `url` +| Address of the Docker image registry. Optional for user authentication. + +| `email` +| E-mail address for the Docker image registry user. Optional for user authentication. + +| `token` +| Identity token for the Docker image registry user. Required for token authentication. +|=== + +For more details, see also xref:packaging-oci-image.adoc#build-image.examples.docker[examples]. + + + +[[build-image.customization]] +== Image Customizations + +The plugin invokes a {url-buildpacks-docs}/concepts/components/builder/[builder] to orchestrate the generation of an image. +The builder includes multiple {url-buildpacks-docs}/concepts/components/buildpack[buildpacks] that can inspect the application to influence the generated image. +By default, the plugin chooses a builder image. +The name of the generated image is deduced from project properties. + +Task properties can be used to configure how the builder should operate on the project. +The following table summarizes the available properties and their default values: + +|=== +| Property | Command-line option | Description | Default value + +| `builder` +| `--builder` +| Name of the builder image to use. +| `paketobuildpacks/builder-jammy-tiny:latest` + +| `trustBuilder` +| `--trustBuilder` +| Whether to treat the builder as https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/concepts/trusted_builders/#what-is-a-trusted-builder[trusted]. +| `true` if the builder is one of `paketobuildpacks/builder-jammy-tiny`, `paketobuildpacks/builder-jammy-base`, `paketobuildpacks/builder-jammy-full`, `paketobuildpacks/builder-jammy-buildpackless-tiny`, `paketobuildpacks/builder-jammy-buildpackless-base`, `paketobuildpacks/builder-jammy-buildpackless-full`, `gcr.io/buildpacks/builder`, `heroku/builder`; false otherwise. + +| `runImage` +| `--runImage` +| Name of the run image to use. +| No default value, indicating the run image specified in Builder metadata should be used. + +| `imageName` +| `--imageName` +| xref:api:java/org/springframework/boot/buildpack/platform/docker/type/ImageReference.html#of-java.lang.String-[Image name] for the generated image. +| `docker.io/library/${project.name}:${project.version}` + +| `pullPolicy` +| `--pullPolicy` +| xref:api:java/org/springframework/boot/buildpack/platform/build/PullPolicy.html[Policy] used to determine when to pull the builder and run images from the registry. +Acceptable values are `ALWAYS`, `NEVER`, and `IF_NOT_PRESENT`. +| `ALWAYS` + +| `environment` +| +| Environment variables that should be passed to the builder. +| Empty or `['BP_NATIVE_IMAGE': 'true']` when {url-native-build-tools-docs-gradle-plugin}[GraalVM Native Image plugin] is applied. + +| `buildpacks` +| +a|Buildpacks that the builder should use when building the image. +Only the specified buildpacks will be used, overriding the default buildpacks included in the builder. +Buildpack references must be in one of the following forms: + +* Buildpack in the builder - `[urn:cnb:builder:][@]` +* Buildpack in a directory on the file system - `[file://]` +* Buildpack in a gzipped tar (.tgz) file on the file system - `[file://]/` +* Buildpack in an OCI image - `[docker://]/[:][@]` +| None, indicating the builder should use the buildpacks included in it. + +| `bindings` +| +a|https://docs.docker.com/storage/bind-mounts/[Volume bind mounts] that should be mounted to the builder container when building the image. +The bindings will be passed unparsed and unvalidated to Docker when creating the builder container. +Bindings must be in one of the following forms: + +* `:[:]` +* `:[:]` + +Where `` can contain: + +* `ro` to mount the volume as read-only in the container +* `rw` to mount the volume as readable and writable in the container +* `volume-opt=key=value` to specify key-value pairs consisting of an option name and its value +| + +| `network` +| `--network` +| The https://docs.docker.com/network/#network-drivers[network driver] the builder container will be configured to use. +The value supplied will be passed unvalidated to Docker when creating the builder container. +| + +| `cleanCache` +| `--cleanCache` +| Whether to clean the cache before building. +| `false` + +| `verboseLogging` +| +| Enables verbose logging of builder operations. +| `false` + +| `publish` +| `--publishImage` +| Whether to publish the generated image to a Docker registry. +| `false` + +| `tags` +| +| A list of one or more additional tags to apply to the generated image. +The values provided to the `tags` option should be *full* image references. +See xref:packaging-oci-image.adoc#build-image.customization.tags[the tags section] for more details. +| + +| `buildWorkspace` +| +| A temporary workspace that will be used by the builder and buildpacks to store files during image building. +The value can be a named volume or a bind mount location. +| A named volume in the Docker daemon, with a name derived from the image name. + +| `buildCache` +| +| A cache containing layers created by buildpacks and used by the image building process. +The value can be a named volume or a bind mount location. +| A named volume in the Docker daemon, with a name derived from the image name. + +| `launchCache` +| +| A cache containing layers created by buildpacks and used by the image launching process. +The value can be a named volume or a bind mount location. +| A named volume in the Docker daemon, with a name derived from the image name. + +| `createdDate` +| `--createdDate` +| A date that will be used to set the `Created` field in the generated image's metadata. +The value must be a string in the ISO 8601 instant format, or `now` to use the current date and time. +| A fixed date that enables https://buildpacks.io/docs/features/reproducibility/[build reproducibility]. + +| `applicationDirectory` +| `--applicationDirectory` +| The path to a directory that application contents will be uploaded to in the builder image. +Application contents will also be in this location in the generated image. +| `/workspace` + +| `securityOptions` +| `--securityOptions` +| https://docs.docker.com/engine/reference/run/#security-configuration[Security options] that will be applied to the builder container, provided as an array of string values +| `["label=disable"]` on Linux and macOS, `[]` on Windows + +|=== + +NOTE: The plugin detects the target Java compatibility of the project using the JavaPlugin's `targetCompatibility` property. +When using the default Paketo builder and buildpacks, the plugin instructs the buildpacks to install the same Java version. +You can override this behavior as shown in the xref:packaging-oci-image.adoc#build-image.examples.builder-configuration[builder configuration] examples. + +NOTE: The default builder `paketobuildpacks/builder-jammy-tiny:latest` does not include a shell. +Applications that require a shell to run a start script, as might be the case when the {url-gradle-docs-application-plugin}[`application` plugin] has been applied to generate a distribution zip archive, should override the `builder` configuration to use one that includes a shell, such as `paketobuildpacks/builder-jammy-base:latest` or `paketobuildpacks/builder-jammy-full:latest`. + + + +[[build-image.customization.tags]] +=== Tags Format + +The values provided to the `tags` option should be *full* image references. +The accepted format is `[domainHost:port/][path/]name[:tag][@digest]`. + +If the domain is missing, it defaults to `docker.io`. +If the path is missing, it defaults to `library`. +If the tag is missing, it defaults to `latest`. + +Some examples: + +* `my-image` leads to the image reference `docker.io/library/my-image:latest` +* `my-repository/my-image` leads to `docker.io/my-repository/my-image:latest` +* `example.com/my-repository/my-image:1.0.0` will be used as is + + + +[[build-image.examples]] +== Examples + + + +[[build-image.examples.custom-image-builder]] +=== Custom Image Builder and Run Image + +If you need to customize the builder used to create the image or the run image used to launch the built image, configure the task as shown in the following example: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-builder.gradle[tags=builder] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-builder.gradle.kts[tags=builder] +---- +====== + +This configuration will use a builder image with the name `mine/java-cnb-builder` and the tag `latest`, and the run image named `mine/java-cnb-run` and the tag `latest`. + +The builder and run image can be specified on the command line as well, as shown in this example: + +[source,shell] +---- +$ gradle bootBuildImage --builder=mine/java-cnb-builder --runImage=mine/java-cnb-run +---- + + + +[[build-image.examples.builder-configuration]] +=== Builder Configuration + +If the builder exposes configuration options, those can be set using the `environment` property. + +The following is an example of {url-paketo-docs-java-buildpack}/#configuring-the-jvm-version[configuring the JVM version] used by the Paketo Java buildpacks at build time: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-env.gradle[tags=env] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-env.gradle.kts[tags=env] +---- +====== + +If there is a network proxy between the Docker daemon the builder runs in and network locations that buildpacks download artifacts from, you will need to configure the builder to use the proxy. +When using the Paketo builder, this can be accomplished by setting the `HTTPS_PROXY` and/or `HTTP_PROXY` environment variables as show in the following example: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-env-proxy.gradle[tags=env] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-env-proxy.gradle.kts[tags=env] +---- +====== + + + +[[build-image.examples.runtime-jvm-configuration]] +=== Runtime JVM Configuration + +Paketo Java buildpacks {url-paketo-docs-java-buildpack}/#runtime-jvm-configuration[configure the JVM runtime environment] by setting the `JAVA_TOOL_OPTIONS` environment variable. +The buildpack-provided `JAVA_TOOL_OPTIONS` value can be modified to customize JVM runtime behavior when the application image is launched in a container. + +Environment variable modifications that should be stored in the image and applied to every deployment can be set as described in the {url-paketo-docs}/buildpacks/configuration/#environment-variables[Paketo documentation] and shown in the following example: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-env-runtime.gradle[tags=env-runtime] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-env-runtime.gradle.kts[tags=env-runtime] +---- +====== + + + +[[build-image.examples.custom-image-name]] +=== Custom Image Name + +By default, the image name is inferred from the `name` and the `version` of the project, something like `docker.io/library/${project.name}:${project.version}`. +You can take control over the name by setting task properties, as shown in the following example: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-name.gradle[tags=image-name] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-name.gradle.kts[tags=image-name] +---- +====== + +Note that this configuration does not provide an explicit tag so `latest` is used. +It is possible to specify a tag as well, either using `${project.version}`, any property available in the build or a hardcoded version. + +The image name can be specified on the command line as well, as shown in this example: + +[source,shell] +---- +$ gradle bootBuildImage --imageName=example.com/library/my-app:v1 +---- + + + +[[build-image.examples.buildpacks]] +=== Buildpacks + +By default, the builder will use buildpacks included in the builder image and apply them in a pre-defined order. +An alternative set of buildpacks can be provided to apply buildpacks that are not included in the builder, or to change the order of included buildpacks. +When one or more buildpacks are provided, only the specified buildpacks will be applied. + +The following example instructs the builder to use a custom buildpack packaged in a `.tgz` file, followed by a buildpack included in the builder. + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-buildpacks.gradle[tags=buildpacks] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-buildpacks.gradle.kts[tags=buildpacks] +---- +====== + +Buildpacks can be specified in any of the forms shown below. + +A buildpack located in a CNB Builder (version may be omitted if there is only one buildpack in the builder matching the `buildpack-id`): + +* `urn:cnb:builder:buildpack-id` +* `urn:cnb:builder:buildpack-id@0.0.1` +* `buildpack-id` +* `buildpack-id@0.0.1` + +A path to a directory containing buildpack content (not supported on Windows): + +* `\file:///path/to/buildpack/` +* `/path/to/buildpack/` + +A path to a gzipped tar file containing buildpack content: + +* `\file:///path/to/buildpack.tgz` +* `/path/to/buildpack.tgz` + +An OCI image containing a https://buildpacks.io/docs/buildpack-author-guide/package-a-buildpack/[packaged buildpack]: + +* `docker://example/buildpack` +* `docker:///example/buildpack:latest` +* `docker:///example/buildpack@sha256:45b23dee08...` +* `example/buildpack` +* `example/buildpack:latest` +* `example/buildpack@sha256:45b23dee08...` + + + +[[build-image.examples.publish]] +=== Image Publishing + +The generated image can be published to a Docker registry by enabling a `publish` option. + +If the Docker registry requires authentication, the credentials can be configured using `docker.publishRegistry` properties. +If the Docker registry does not require authentication, the `docker.publishRegistry` configuration can be omitted. + +NOTE: The registry that the image will be published to is determined by the registry part of the image name (`docker.example.com` in these examples). +If `docker.publishRegistry` credentials are configured and include a `url` property, this value is passed to the registry but is not used to determine the publishing registry location. + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-publish.gradle[tags=publish] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-publish.gradle.kts[tags=publish] +---- +====== + +The publish option can be specified on the command line as well, as shown in this example: + +[source,shell] +---- +$ gradle bootBuildImage --imageName=docker.example.com/library/my-app:v1 --publishImage +---- + + + +[[build-image.examples.caches]] +=== Builder Cache and Workspace Configuration + +The CNB builder caches layers that are used when building and launching an image. +By default, these caches are stored as named volumes in the Docker daemon with names that are derived from the full name of the target image. +If the image name changes frequently, for example when the project version is used as a tag in the image name, then the caches can be invalidated frequently. + +The cache volumes can be configured to use alternative names to give more control over cache lifecycle as shown in the following example: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-caches.gradle[tags=caches] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-caches.gradle.kts[tags=caches] +---- +====== + +Builders and buildpacks need a location to store temporary files during image building. +By default, this temporary build workspace is stored in a named volume. + +The caches and the build workspace can be configured to use bind mounts instead of named volumes, as shown in the following example: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-bind-caches.gradle[tags=caches] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-bind-caches.gradle.kts[tags=caches] +---- +====== + + + +[[build-image.examples.docker]] +=== Docker Configuration + + + +[[build-image.examples.docker.minikube]] +==== Docker Configuration for minikube + +The plugin can communicate with the https://minikube.sigs.k8s.io/docs/tasks/docker_daemon/[Docker daemon provided by minikube] instead of the default local connection. + +On Linux and macOS, environment variables can be set using the command `eval $(minikube docker-env)` after minikube has been started. + +The plugin can also be configured to use the minikube daemon by providing connection details similar to those shown in the following example: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-docker-host.gradle[tags=docker-host] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-docker-host.gradle.kts[tags=docker-host] +---- +====== + + + +[[build-image.examples.docker.podman]] +==== Docker Configuration for podman + +The plugin can communicate with a https://podman.io/[podman container engine]. + +The plugin can be configured to use podman local connection by providing connection details similar to those shown in the following example: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-docker-host-podman.gradle[tags=docker-host] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-docker-host-podman.gradle.kts[tags=docker-host] +---- +====== + +TIP: With the `podman` CLI installed, the command `podman info --format='{{.Host.RemoteSocket.Path}}'` can be used to get the value for the `docker.host` configuration property shown in this example. + + + +[[build-image.examples.docker.colima]] +==== Docker Configuration for Colima + +The plugin can communicate with the Docker daemon provided by https://github.com/abiosoft/colima[Colima]. +The `DOCKER_HOST` environment variable can be set by using the command `export DOCKER_HOST=$(docker context inspect colima -f '{{.Endpoints.docker.Host}}').` + +The plugin can also be configured to use Colima daemon by providing connection details similar to those shown in the following example: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-docker-host-colima.gradle[tags=docker-host] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-docker-host-colima.gradle.kts[tags=docker-host] +---- +====== + + + +[[build-image.examples.docker.auth]] +==== Docker Configuration for Authentication + +If the builder or run image are stored in a private Docker registry that supports user authentication, authentication details can be provided using `docker.builderRegistry` properties as shown in the following example: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-docker-auth-user.gradle[tags=docker-auth-user] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-docker-auth-user.gradle.kts[tags=docker-auth-user] +---- +====== + +If the builder or run image is stored in a private Docker registry that supports token authentication, the token value can be provided using `docker.builderRegistry` as shown in the following example: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-docker-auth-token.gradle[tags=docker-auth-token] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-build-image-docker-auth-token.gradle.kts[tags=docker-auth-token] +---- +====== diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/packaging.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/packaging.adoc new file mode 100644 index 000000000000..1b2a13aa0818 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/packaging.adoc @@ -0,0 +1,453 @@ +[[packaging-executable]] += Packaging Executable Archives + +The plugin can create executable archives (jar files and war files) that contain all of an application's dependencies and can then be run with `java -jar`. + + + +[[packaging-executable.jars]] +== Packaging Executable Jars + +Executable jars can be built using the `bootJar` task. +The task is automatically created when the `java` plugin is applied and is an instance of xref:api/java/org/springframework/boot/gradle/tasks/bundling/BootJar.html[`BootJar`]. +The `assemble` task is automatically configured to depend upon the `bootJar` task so running `assemble` (or `build`) will also run the `bootJar` task. + + + +[[packaging-executable.wars]] +== Packaging Executable Wars + +Executable wars can be built using the `bootWar` task. +The task is automatically created when the `war` plugin is applied and is an instance of {apiref-gradle-plugin-boot-war}[`BootWar`]. +The `assemble` task is automatically configured to depend upon the `bootWar` task so running `assemble` (or `build`) will also run the `bootWar` task. + + + +[[packaging-executable.wars.deployable]] +=== Packaging Executable and Deployable Wars + +A war file can be packaged such that it can be executed using `java -jar` and deployed to an external container. +To do so, the embedded servlet container dependencies should be added to the `providedRuntime` configuration, for example: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/war-container-dependency.gradle[tags=dependencies] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/war-container-dependency.gradle.kts[tags=dependencies] +---- +====== + +This ensures that they are package in the war file's `WEB-INF/lib-provided` directory from where they will not conflict with the external container's own classes. + +NOTE: `providedRuntime` is preferred to Gradle's `compileOnly` configuration as, among other limitations, `compileOnly` dependencies are not on the test classpath so any web-based integration tests will fail. + + + +[[packaging-executable.and-plain-archives]] +== Packaging Executable and Plain Archives + +By default, when the `bootJar` or `bootWar` tasks are configured, the `jar` or `war` tasks are configured to use `plain` as the convention for their archive classifier. +This ensures that `bootJar` and `jar` or `bootWar` and `war` have different output locations, allowing both the executable archive and the plain archive to be built at the same time. + +If you prefer that the executable archive, rather than the plain archive, uses a classifier, configure the classifiers as shown in the following example for the `jar` and `bootJar` tasks: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-and-jar-classifiers.gradle[tags=classifiers] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-and-jar-classifiers.gradle.kts[tags=classifiers] +---- +====== + +Alternatively, if you prefer that the plain archive isn't built at all, disable its task as shown in the following example for the `jar` task: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/only-boot-jar.gradle[tags=disable-jar] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/only-boot-jar.gradle.kts[tags=disable-jar] +---- +====== + +WARNING: Do not disable the `jar` task when creating native images. +See https://github.com/spring-projects/spring-boot/issues/33238[#33238] for details. + + + +[[packaging-executable.configuring]] +== Configuring Executable Archive Packaging + +The xref:api/java/org/springframework/boot/gradle/tasks/bundling/BootJar.html[`BootJar`] and {apiref-gradle-plugin-boot-war}[`BootWar`] tasks are subclasses of Gradle's `Jar` and `War` tasks respectively. +As a result, all of the standard configuration options that are available when packaging a jar or war are also available when packaging an executable jar or war. +A number of configuration options that are specific to executable jars and wars are also provided. + + + +[[packaging-executable.configuring.main-class]] +=== Configuring the Main Class + +By default, the executable archive's main class will be configured automatically by looking for a class with a `public static void main(String[])` method in the main source set's output. + +The main class can also be configured explicitly using the task's `mainClass` property: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-main-class.gradle[tags=main-class] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-main-class.gradle.kts[tags=main-class] +---- +====== + +Alternatively, the main class name can be configured project-wide using the `mainClass` property of the Spring Boot DSL: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/spring-boot-dsl-main-class.gradle[tags=main-class] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/spring-boot-dsl-main-class.gradle.kts[tags=main-class] +---- +====== + +If the {url-gradle-docs-application-plugin}[`application` plugin] has been applied its `mainClass` property must be configured and can be used for the same purpose: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/application-plugin-main-class.gradle[tags=main-class] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/application-plugin-main-class.gradle.kts[tags=main-class] +---- +====== + +Lastly, the `Start-Class` attribute can be configured on the task's manifest: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-manifest-main-class.gradle[tags=main-class] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-manifest-main-class.gradle.kts[tags=main-class] +---- +====== + +NOTE: If the main class is written in Kotlin, the name of the generated Java class should be used. +By default, this is the name of the Kotlin class with the `Kt` suffix added. +For example, `ExampleApplication` becomes `ExampleApplicationKt`. +If another name is defined using `@JvmName` then that name should be used. + + + +[[packaging-executable.configuring.including-development-only-dependencies]] +=== Including Development-only Dependencies + +By default all dependencies declared in the `developmentOnly` configuration will be excluded from an executable jar or war. + +If you want to include dependencies declared in the `developmentOnly` configuration in your archive, configure the classpath of its task to include the configuration, as shown in the following example for the `bootWar` task: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-war-include-devtools.gradle[tags=include-devtools] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-war-include-devtools.gradle.kts[tags=include-devtools] +---- +====== + + + +[[packaging-executable.configuring.unpacking]] +=== Configuring Libraries that Require Unpacking + +Most libraries can be used directly when nested in an executable archive, however certain libraries can have problems. +For example, JRuby includes its own nested jar support which assumes that `jruby-complete.jar` is always directly available on the file system. + +To deal with any problematic libraries, an executable archive can be configured to unpack specific nested jars to a temporary directory when the executable archive is run. +Libraries can be identified as requiring unpacking using Ant-style patterns that match against the absolute path of the source jar file: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-requires-unpack.gradle[tags=requires-unpack] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-requires-unpack.gradle.kts[tags=requires-unpack] +---- +====== + +For more control a closure can also be used. +The closure is passed a `FileTreeElement` and should return a `boolean` indicating whether or not unpacking is required. + + + +[[packaging-executable.configuring.launch-script]] +=== Making an Archive Fully Executable + +Spring Boot provides support for fully executable archives. +An archive is made fully executable by prepending a shell script that knows how to launch the application. +On Unix-like platforms, this launch script allows the archive to be run directly like any other executable or to be installed as a service. + +NOTE: Currently, some tools do not accept this format so you may not always be able to use this technique. +For example, `jar -xf` may silently fail to extract a jar or war that has been made fully-executable. +It is recommended that you only enable this option if you intend to execute it directly, rather than running it with `java -jar` or deploying it to a servlet container. + +To use this feature, the inclusion of the launch script must be enabled: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-include-launch-script.gradle[tags=include-launch-script] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-include-launch-script.gradle.kts[tags=include-launch-script] +---- +====== + +This will add Spring Boot's default launch script to the archive. +The default launch script includes several properties with sensible default values. +The values can be customized using the `properties` property: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-launch-script-properties.gradle[tags=launch-script-properties] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-launch-script-properties.gradle.kts[tags=launch-script-properties] +---- +====== + +If the default launch script does not meet your needs, the `script` property can be used to provide a custom launch script: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-custom-launch-script.gradle[tags=custom-launch-script] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-custom-launch-script.gradle.kts[tags=custom-launch-script] +---- +====== + + + +[[packaging-executable.configuring.properties-launcher]] +=== Using the PropertiesLauncher + +To use the `PropertiesLauncher` to launch an executable jar or war, configure the task's manifest to set the `Main-Class` attribute: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-war-properties-launcher.gradle[tags=properties-launcher] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-war-properties-launcher.gradle.kts[tags=properties-launcher] +---- +====== + + + +[[packaging-executable.configuring.layered-archives]] +=== Packaging Layered Jar or War + +By default, the `bootJar` task builds an archive that contains the application's classes and dependencies in `BOOT-INF/classes` and `BOOT-INF/lib` respectively. +Similarly, `bootWar` builds an archive that contains the application's classes in `WEB-INF/classes` and dependencies in `WEB-INF/lib` and `WEB-INF/lib-provided`. +For cases where a docker image needs to be built from the contents of the jar, it's useful to be able to separate these directories further so that they can be written into distinct layers. + +Layered jars use the same layout as regular boot packaged jars, but include an additional meta-data file that describes each layer. + +By default, the following layers are defined: + +* `dependencies` for any non-project dependency whose version does not contain `SNAPSHOT`. +* `spring-boot-loader` for the jar loader classes. +* `snapshot-dependencies` for any non-project dependency whose version contains `SNAPSHOT`. +* `application` for project dependencies, application classes, and resources. + +The layers order is important as it determines how likely previous layers can be cached when part of the application changes. +The default order is `dependencies`, `spring-boot-loader`, `snapshot-dependencies`, `application`. +Content that is least likely to change should be added first, followed by layers that are more likely to change. + +To disable this feature, you can do so in the following manner: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-layered-disabled.gradle[tags=layered] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-layered-disabled.gradle.kts[tags=layered] +---- +====== + +When a layered jar or war is created, the `spring-boot-jarmode-tools` jar will be added as a dependency to your archive. +With this jar on the classpath, you can launch your application in a special mode which allows the bootstrap code to run something entirely different from your application, for example, something that extracts the layers. +If you wish to exclude this dependency, you can do so in the following manner: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-layered-exclude-tools.gradle[tags=layered] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-layered-exclude-tools.gradle.kts[tags=layered] +---- +====== + + + +[[packaging-executable.configuring.layered-archives.configuration]] +==== Custom Layers Configuration + +Depending on your application, you may want to tune how layers are created and add new ones. + +This can be done using configuration that describes how the jar or war can be separated into layers, and the order of those layers. +The following example shows how the default ordering described above can be defined explicitly: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-layered-custom.gradle[tags=layered] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$packaging/boot-jar-layered-custom.gradle.kts[tags=layered] +---- +====== + +The `layered` DSL is defined using three parts: + +* The `application` closure defines how the application classes and resources should be layered. +* The `dependencies` closure defines how dependencies should be layered. +* The `layerOrder` method defines the order that the layers should be written. + +Nested `intoLayer` closures are used within `application` and `dependencies` sections to claim content for a layer. +These closures are evaluated in the order that they are defined, from top to bottom. +Any content not claimed by an earlier `intoLayer` closure remains available for subsequent ones to consider. + +The `intoLayer` closure claims content using nested `include` and `exclude` calls. +The `application` closure uses Ant-style path matching for include/exclude parameters. +The `dependencies` section uses `group:artifact[:version]` patterns. +It also provides `includeProjectDependencies()` and `excludeProjectDependencies()` methods that can be used to include or exclude project dependencies. + +If no `include` call is made, then all content (not claimed by an earlier closure) is considered. + +If no `exclude` call is made, then no exclusions are applied. + +Looking at the `dependencies` closure in the example above, we can see that the first `intoLayer` will claim all project dependencies for the `application` layer. +The next `intoLayer` will claim all SNAPSHOT dependencies for the `snapshot-dependencies` layer. +The third and final `intoLayer` will claim anything left (in this case, any dependency that is not a project dependency or a SNAPSHOT) for the `dependencies` layer. + +The `application` closure has similar rules. +First claiming `org/springframework/boot/loader/**` content for the `spring-boot-loader` layer. +Then claiming any remaining classes and resources for the `application` layer. + +NOTE: The order that `intoLayer` closures are added is often different from the order that the layers are written. +For this reason the `layerOrder` method must always be called and _must_ cover all layers referenced by the `intoLayer` calls. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/publishing.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/publishing.adoc new file mode 100644 index 000000000000..ca428fc0126a --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/publishing.adoc @@ -0,0 +1,37 @@ +[[publishing-your-application]] += Publishing your Application + + + +[[publishing-your-application.maven-publish]] +== Publishing with the Maven-publish Plugin + +To publish your Spring Boot jar or war, add it to the publication using the `artifact` method on `MavenPublication`. +Pass the task that produces that artifact that you wish to publish to the `artifact` method. +For example, to publish the artifact produced by the default `bootJar` task: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$publishing/maven-publish.gradle[tags=publishing] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$publishing/maven-publish.gradle.kts[tags=publishing] +---- +====== + + + +[[publishing-your-application.distribution]] +== Distributing with the Application Plugin + +When the {url-gradle-docs-application-plugin}[`application` plugin] is applied a distribution named `boot` is created. +This distribution contains the archive produced by the `bootJar` or `bootWar` task and scripts to launch it on Unix-like platforms and Windows. +Zip and tar distributions can be built by the `bootDistZip` and `bootDistTar` tasks respectively. +To use the `application` plugin, its `mainClassName` property must be configured with the name of your application's main class. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/reacting.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/reacting.adoc new file mode 100644 index 000000000000..4094068ab6bd --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/reacting.adoc @@ -0,0 +1,94 @@ +[[reacting-to-other-plugins]] += Reacting to Other Plugins + +When another plugin is applied the Spring Boot plugin reacts by making various changes to the project's configuration. +This section describes those changes. + + + +[[reacting-to-other-plugins.java]] +== Reacting to the Java Plugin + +When Gradle's {url-gradle-docs-java-plugin}[`java` plugin] is applied to a project, the Spring Boot plugin: + +1. Creates a {apiref-gradle-plugin-boot-jar}[`BootJar`] task named `bootJar` that will create an executable, uber jar for the project. + The jar will contain everything on the runtime classpath of the main source set; classes are packaged in `BOOT-INF/classes` and jars are packaged in `BOOT-INF/lib` +2. Configures the `assemble` task to depend on the `bootJar` task. +3. Configures the `jar` task to use `plain` as the convention for its archive classifier. +4. Creates a {apiref-gradle-plugin-boot-build-image}[`BootBuildImage`] task named `bootBuildImage` that will create a OCI image using a https://buildpacks.io[buildpack]. +5. Creates a {apiref-gradle-plugin-boot-run}[`BootRun`] task named `bootRun` that can be used to run your application using the `main` source set to find its main method and provide its runtime classpath. +6. Creates a {apiref-gradle-plugin-boot-run}['BootRun`] task named `bootTestRun` that can be used to run your application using the `test` source set to find its main method and provide its runtime classpath. +7. Creates a configuration named `bootArchives` that contains the artifact produced by the `bootJar` task. +8. Creates a configuration named `developmentOnly` for dependencies that are only required at development time, such as Spring Boot's Devtools, and should not be packaged in executable jars and wars. +9. Creates a configuration named `testAndDevelopmentOnly` for dependencies that are only required at development time and when writing and running tests and that should not be packaged in executable jars and wars. +10. Creates a configuration named `productionRuntimeClasspath`. It is equivalent to `runtimeClasspath` minus any dependencies that only appear in the `developmentOnly` or `testDevelopmentOnly` configurations. +11. Configures any `JavaCompile` tasks with no configured encoding to use `UTF-8`. +12. Configures any `JavaCompile` tasks to use the `-parameters` compiler argument. + + + +[[reacting-to-other-plugins.kotlin]] +== Reacting to the Kotlin Plugin + +When {url-kotlin-docs-kotlin-plugin}[Kotlin's Gradle plugin] is applied to a project, the Spring Boot plugin: + +1. Aligns the Kotlin version used in Spring Boot's dependency management with the version of the plugin. + This is achieved by setting the `kotlin.version` property with a value that matches the version of the Kotlin plugin. +2. Configures any `KotlinCompile` tasks to use the `-java-parameters` compiler argument. + + + +[[reacting-to-other-plugins.war]] +== Reacting to the War Plugin + +When Gradle's {url-gradle-docs-war-plugin}[`war` plugin] is applied to a project, the Spring Boot plugin: + +1. Creates a {apiref-gradle-plugin-boot-war}[`BootWar`] task named `bootWar` that will create an executable, fat war for the project. + In addition to the standard packaging, everything in the `providedRuntime` configuration will be packaged in `WEB-INF/lib-provided`. +2. Configures the `assemble` task to depend on the `bootWar` task. +3. Configures the `war` task to use `plain` as the convention for its archive classifier. +4. Configures the `bootArchives` configuration to contain the artifact produced by the `bootWar` task. + + + +[[reacting-to-other-plugins.dependency-management]] +== Reacting to the Dependency Management Plugin + +When the {url-dependency-management-plugin-site}[`io.spring.dependency-management` plugin] is applied to a project, the Spring Boot plugin will automatically import the `spring-boot-dependencies` bom. + + + +[[reacting-to-other-plugins.application]] +== Reacting to the Application Plugin + +When Gradle's {url-gradle-docs-application-plugin}[`application` plugin] is applied to a project, the Spring Boot plugin: + +1. Creates a `CreateStartScripts` task named `bootStartScripts` that will create scripts that launch the artifact in the `bootArchives` configuration using `java -jar`. + The task is configured to use the `applicationDefaultJvmArgs` property as a convention for its `defaultJvmOpts` property. +2. Creates a new distribution named `boot` and configures it to contain the artifact in the `bootArchives` configuration in its `lib` directory and the start scripts in its `bin` directory. +3. Configures the `bootRun` task to use the `mainClassName` property as a convention for its `main` property. +4. Configures the `bootRun` and `bootTestRun` tasks to use the `applicationDefaultJvmArgs` property as a convention for their `jvmArgs` property. +5. Configures the `bootJar` task to use the `mainClassName` property as a convention for the `Start-Class` entry in its manifest. +6. Configures the `bootWar` task to use the `mainClassName` property as a convention for the `Start-Class` entry in its manifest. + + + +[[reacting-to-other-plugins.nbt]] +== Reacting to the GraalVM Native Image Plugin + +When the {url-native-build-tools-docs-gradle-plugin}[GraalVM Native Image plugin] is applied to a project, the Spring Boot plugin: + +. Applies the `org.springframework.boot.aot` plugin that: +.. Registers `aot` and `aotTest` source sets. +.. Registers a `ProcessAot` task named `processAot` that will generate AOT-optimized source for the application in the `aot` source set. +.. Configures the Java compilation and process resources tasks for the `aot` source set to depend upon `processAot`. +.. Registers a `ProcessTestAot` task named `processTestAot` that will generated AOT-optimized source for the application's tests in the `aotTest` source set. +.. Configures the Java compilation and process resources tasks for the `aotTest` source set to depend upon `processTestAot`. +. Adds the output of the `aot` source set to the classpath of the `main` GraalVM native binary. +. Adds the output of the `aotTest` source set to the classpath of the `test` GraalVM native binary. +. Configures the GraalVM extension to disable Toolchain detection. +. Configures each GraalVM native binary to require GraalVM 22.3 or later. +. Configures the `bootJar` task to include the reachability metadata produced by the `collectReachabilityMetadata` task in its jar. +. Configures the `bootBuildImage` task to set `BP_NATIVE_IMAGE` to `true` in its environment. + + diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/running.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/running.adoc new file mode 100644 index 000000000000..4c82fce250ad --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/running.adoc @@ -0,0 +1,181 @@ +[[running-your-application]] += Running your Application with Gradle + +To run your application without first building an archive use the `bootRun` task: + +[source,shell] +---- +$ ./gradlew bootRun +---- + +The `bootRun` task is an instance of xref:api/java/org/springframework/boot/gradle/tasks/run/BootRun.html[`BootRun`] which is a `JavaExec` subclass. +As such, all of the {url-gradle-dsl}/org.gradle.api.tasks.JavaExec.html[usual configuration options] for executing a Java process in Gradle are available to you. +The task is automatically configured to use the runtime classpath of the main source set. + +By default, the main class will be configured automatically by looking for a class with a `public static void main(String[])` method in the main source set's output. + +The main class can also be configured explicitly using the task's `main` property: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$running/boot-run-main.gradle[tags=main] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$running/boot-run-main.gradle.kts[tags=main] +---- +====== + +Alternatively, the main class name can be configured project-wide using the `mainClass` property of the Spring Boot DSL: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$running/spring-boot-dsl-main-class-name.gradle[tags=main-class] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$running/spring-boot-dsl-main-class-name.gradle.kts[tags=main-class] +---- +====== + +By default, `bootRun` will configure the JVM to optimize its launch for faster startup during development. +This behavior can be disabled by using the `optimizedLaunch` property, as shown in the following example: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$running/boot-run-disable-optimized-launch.gradle[tags=launch] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$running/boot-run-disable-optimized-launch.gradle.kts[tags=launch] +---- +====== + +If the {url-gradle-docs-application-plugin}[`application` plugin] has been applied, its `mainClass` property must be configured and can be used for the same purpose: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$running/application-plugin-main-class-name.gradle[tags=main-class] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$running/application-plugin-main-class-name.gradle.kts[tags=main-class] +---- +====== + + + +[[running-your-application.passing-arguments]] +== Passing Arguments to Your Application + +Like all `JavaExec` tasks, arguments can be passed into `bootRun` from the command line using `--args=''` when using Gradle 4.9 or later. +For example, to run your application with a profile named `dev` active the following command can be used: + +[source,shell] +---- +$ ./gradlew bootRun --args='--spring.profiles.active=dev' +---- + +See {url-gradle-javadoc}/org/gradle/api/tasks/JavaExec.html#setArgsString-java.lang.String-[the javadoc for `JavaExec.setArgsString`] for further details. + + + +[[running-your-application.passing-system-properties]] +== Passing System Properties to Your application + +Since `bootRun` is a standard `JavaExec` task, system properties can be passed to the application's JVM by specifying them in the build script. +To make that value of a system property to be configurable set its value using a {url-gradle-dsl}/org.gradle.api.Project.html#N14FE1[project property]. +To allow a project property to be optional, reference it using `findProperty`. +Doing so also allows a default value to be provided using the `?:` Elvis operator, as shown in the following example: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$running/boot-run-system-property.gradle[tags=system-property] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$running/boot-run-system-property.gradle.kts[tags=system-property] +---- +====== + +The preceding example sets that `com.example.property` system property to the value of the `example` project property. +If the `example` project property has not been set, the value of the system property will be `default`. + +Gradle allows project properties to be set in a variety of ways, including on the command line using the `-P` flag, as shown in the following example: + +[source,bash,indent=0,subs="verbatim,attributes"] +---- +$ ./gradlew bootRun -Pexample=custom +---- + +The preceding example sets the value of the `example` project property to `custom`. +`bootRun` will then use this as the value of the `com.example.property` system property. + + + +[[running-your-application.reloading-resources]] +== Reloading Resources + +If devtools has been added to your project it will automatically monitor your application's classpath for changes. +Note that modified files need to be recompiled for the classpath to update in order to trigger reloading with devtools. +For more details on using devtools, refer to xref:reference:using/devtools.adoc#using.devtools.restart[this section of the reference documentation]. + +Alternatively, you can configure `bootRun` such that your application's static resources are loaded from their source location: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$running/boot-run-source-resources.gradle[tags=source-resources] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$running/boot-run-source-resources.gradle.kts[tags=source-resources] +---- +====== + +This makes them reloadable in the live application which can be helpful at development time. + + + +[[running-your-application.using-a-test-main-class]] +== Using a Test Main Class + +In addition to `bootRun` a `bootTestRun` task is also registered. +Like `bootRun`, `bootTestRun` is an instance of `BootRun` but it's configured to use a main class found in the output of the test source set rather than the main source set. +It also uses the test source set's runtime classpath rather than the main source set's runtime classpath. +As `bootTestRun` is an instance of `BootRun`, all of the configuration options described above for `bootRun` can also be used with `bootTestRun`. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/partials/nav-gradle-plugin.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/partials/nav-gradle-plugin.adoc new file mode 100644 index 000000000000..741a8f7a0565 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/partials/nav-gradle-plugin.adoc @@ -0,0 +1,10 @@ +* xref:gradle-plugin:index.adoc[] +** xref:gradle-plugin:getting-started.adoc[] +** xref:gradle-plugin:managing-dependencies.adoc[] +** xref:gradle-plugin:packaging.adoc[] +** xref:gradle-plugin:packaging-oci-image.adoc[] +** xref:gradle-plugin:publishing.adoc[] +** xref:gradle-plugin:running.adoc[] +** xref:gradle-plugin:aot.adoc[] +** xref:gradle-plugin:integrating-with-actuator.adoc[] +** xref:gradle-plugin:reacting.adoc[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/anchor-rewrite.properties b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/anchor-rewrite.properties deleted file mode 100644 index 777eb3fcf8a2..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/anchor-rewrite.properties +++ /dev/null @@ -1,48 +0,0 @@ -integrating-with-actuator=integrating-with-actuator -integrating-with-actuator-build-info=integrating-with-actuator.build-info -managing-dependencies=managing-dependencies -managing-dependencies-dependency-management-plugin=managing-dependencies.dependency-management-plugin -managing-dependencies-dependency-management-plugin-customizing=managing-dependencies.dependency-management-plugin.customizing -managing-dependencies-dependency-management-plugin-using-in-isolation=managing-dependencies.dependency-management-plugin.using-in-isolation -managing-dependencies-dependency-management-plugin-learning-more=managing-dependencies.dependency-management-plugin.learning-more -managing-dependencies-gradle-bom-support=managing-dependencies.gradle-bom-support -managing-dependencies-gradle-bom-support-customizing=managing-dependencies.gradle-bom-support.customizing -build-image=build-image -build-image-docker-daemon=build-image.docker-daemon -build-image-docker-registry=build-image.docker-registry -build-image-customization=build-image.customization -build-image-examples=build-image.examples -build-image-example-custom-image-builder=build-image.examples.custom-image-builder -build-image-example-builder-configuration=build-image.examples.builder-configuration -build-image-example-runtime-jvm-configuration=build-image.examples.runtime-jvm-configuration -build-image-example-custom-image-name=build-image.examples.custom-image-name -build-image-example-buildpacks=build-image.examples.buildpacks -build-image-example-publish=build-image.examples.publish -build-image-example-docker=build-image.examples.docker -packaging-executable=packaging-executable -packaging-executable-jars=packaging-executable.jars -packaging-executable-wars=packaging-executable.wars -packaging-executable-wars-deployable=packaging-executable.wars.deployable -packaging-executable-and-plain=packaging-executable.and-plain-archives -packaging-executable-configuring=packaging-executable.configuring -packaging-executable-configuring-main-class=packaging-executable.configuring.main-class -packaging-executable-configuring-including-development-only-dependencies=packaging-executable.configuring.including-development-only-dependencies -packaging-executable-configuring-unpacking=packaging-executable.configuring.unpacking -packaging-executable-configuring-launch-script=packaging-executable.configuring.launch-script -packaging-executable-configuring-properties-launcher=packaging-executable.configuring.properties-launcher -packaging-layered-archives=packaging-executable.configuring.layered-archives -packaging-layers-configuration=packaging-executable.configuring.layered-archives.configuration -publishing-your-application=publishing-your-application -publishing-your-application-maven-publish=publishing-your-application.maven-publish -publishing-your-application-maven=publishing-your-application.maven -publishing-your-application-distribution=publishing-your-application.distribution -reacting-to-other-plugins=reacting-to-other-plugins -reacting-to-other-plugins-java=reacting-to-other-plugins.java -reacting-to-other-plugins-kotlin=reacting-to-other-plugins.kotlin -reacting-to-other-plugins-war=reacting-to-other-plugins.war -reacting-to-other-plugins-dependency-management=reacting-to-other-plugins.dependency-management -reacting-to-other-plugins-application=reacting-to-other-plugins.application -running-your-application=running-your-application -running-your-application-passing-arguments=running-your-application.passing-arguments -running-your-application-passing-system-properties=running-your-application.passing-system-properties -running-your-application-reloading-resources=running-your-application.reloading-resources diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/aot.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/aot.adoc deleted file mode 100644 index 96dca8bf6df1..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/aot.adoc +++ /dev/null @@ -1,43 +0,0 @@ -[[aot]] -= Ahead-of-Time Processing -Spring AOT is a process that analyzes your code at build-time in order to generate an optimized version of it. -It is most often used to help generate GraalVM native images. - -The Spring Boot Gradle plugin provides tasks that can be used to perform AOT processing on both application and test code. -The tasks are configured automatically when the {nbt-gradle-plugin}[GraalVM Native Image plugin] is applied: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/aot/apply-native-image-plugin.gradle[] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/aot/apply-native-image-plugin.gradle.kts[] ----- - - -[[aot.processing-applications]] -== Processing Applications -Based on your `@SpringBootApplication`-annotated main class, the `processAot` task generates a persistent view of the beans that are going to be contributed at runtime in a way that bean instantiation is as straightforward as possible. -Additional post-processing of the factory is possible using callbacks. -For instance, these are used to generate the necessary reflection configuration that GraalVM needs to initialize the context in a native image. - -As the `BeanFactory` is fully prepared at build-time, conditions are also evaluated. -This has an important difference compared to what a regular Spring Boot application does at runtime. -For instance, if you want to opt-in or opt-out for certain features, you need to configure the environment used at build time to do so. -To this end, the `processAot` task is a {gradle-dsl}/org.gradle.api.tasks.JavaExec.html[`JavaExec`] task and can be configured with environment variables, system properties, and arguments as needed. - -The `nativeCompile` task of the GraalVM Native Image plugin is automatically configured to use the output of the `processAot` task. - - -[[aot.processing-tests]] -== Processing Tests -The AOT engine can be applied to JUnit 5 tests that use Spring's Test Context Framework. -Suitable tests are processed by the `processTestAot` task to generate `ApplicationContextInitialzer` code. -As with application AOT processing, the `BeanFactory` is fully prepared at build-time. -As with `processAot`, the `processTestAot` task is `JavaExec` subclass and can be configured as needed to influence this processing. - -The `nativeTest` task of the GraalVM Native Image plugin is automatically configured to use the output of the `processAot` and `processTestAot` tasks. \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/getting-started.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/getting-started.adoc deleted file mode 100644 index 5828e3b8d932..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/getting-started.adoc +++ /dev/null @@ -1,100 +0,0 @@ -[[getting-started]] -= Getting Started -To get started with the plugin it needs to be applied to your project. - -ifeval::["{artifact-release-type}" == "release"] -The plugin is https://plugins.gradle.org/plugin/org.springframework.boot[published to Gradle's plugin portal] and can be applied using the `plugins` block: -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/getting-started/apply-plugin-release.gradle[] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/getting-started/apply-plugin-release.gradle.kts[] ----- -endif::[] -ifeval::["{artifact-release-type}" == "milestone"] -The plugin is published to the Spring milestones repository. -Gradle can be configured to use the milestones repository and the plugin can then be applied using the `plugins` block. -To configure Gradle to use the milestones repository, add the following to your `settings.gradle` (Groovy) or `settings.gradle.kts` (Kotlin): - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/getting-started/milestone-settings.gradle[] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/getting-started/milestone-settings.gradle.kts[] ----- - -The plugin can then be applied using the `plugins` block: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/getting-started/apply-plugin-release.gradle[] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/getting-started/apply-plugin-release.gradle.kts[] ----- -endif::[] -ifeval::["{artifact-release-type}" == "snapshot"] -The plugin is published to the Spring snapshots repository. -Gradle can be configured to use the snapshots repository and the plugin can then be applied using the `plugins` block. -To configure Gradle to use the snapshots repository, add the following to your `settings.gradle` (Groovy) or `settings.gradle.kts` (Kotlin): - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/getting-started/snapshot-settings.gradle[] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/getting-started/snapshot-settings.gradle.kts[] ----- - -The plugin can then be applied using the `plugins` block: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/getting-started/apply-plugin-release.gradle[] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/getting-started/apply-plugin-release.gradle.kts[] ----- -endif::[] - -Applied in isolation the plugin makes few changes to a project. -Instead, the plugin detects when certain other plugins are applied and reacts accordingly. -For example, when the `java` plugin is applied a task for building an executable jar is automatically configured. -A typical Spring Boot project will apply the {groovy-plugin}[`groovy`], {java-plugin}[`java`], or {kotlin-plugin}[`org.jetbrains.kotlin.jvm`] plugin as a minimum and also use the {dependency-management-plugin}[`io.spring.dependency-management`] plugin or Gradle's native bom support for dependency management. -For example: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/getting-started/typical-plugins.gradle[tags=apply] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/getting-started/typical-plugins.gradle.kts[tags=apply] ----- - -To learn more about how the Spring Boot plugin behaves when other plugins are applied please see the section on <>. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/index.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/index.adoc deleted file mode 100644 index 3b6f8ae1fd5d..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/index.adoc +++ /dev/null @@ -1,64 +0,0 @@ -[[spring-boot-gradle-plugin-documentation]] -= Spring Boot Gradle Plugin Reference Guide -Andy Wilkinson; Scott Frederick; Moritz Halbritter -v{gradle-project-version} -:!version-label: -:doctype: book -:toc: left -:toclevels: 4 -:numbered: -:sectanchors: -:icons: font -:hide-uri-scheme: -:docinfo: shared,private -:attribute-missing: warn -:dependency-management-plugin: https://github.com/spring-gradle-plugins/dependency-management-plugin -:dependency-management-plugin-documentation: https://docs.spring.io/dependency-management-plugin/docs/current/reference/html/ -:gradle-userguide: https://docs.gradle.org/current/userguide -:gradle-dsl: https://docs.gradle.org/current/dsl -:gradle-api: https://docs.gradle.org/current/javadoc -:application-plugin: {gradle-userguide}/application_plugin.html -:groovy-plugin: {gradle-userguide}/groovy_plugin.html -:java-plugin: {gradle-userguide}/java_plugin.html -:war-plugin: {gradle-userguide}/war_plugin.html -:maven-plugin: {gradle-userguide}/maven_plugin.html -:maven-publish-plugin: {gradle-userguide}/maven_publish_plugin.html -:software-component: {gradle-userguide}/software_model_extend.html -:kotlin-plugin: https://kotlinlang.org/docs/reference/using-gradle.html -:spring-boot-docs: https://docs.spring.io/spring-boot/docs/{gradle-project-version} -:api-documentation: {spring-boot-docs}/gradle-plugin/api -:spring-boot-reference: {spring-boot-docs}/reference/htmlsingle -:spring-boot-api: {spring-boot-docs}/api/org/springframework/boot -:version-properties-appendix: {spring-boot-reference}/#dependency-versions-properties -:build-info-javadoc: {api-documentation}/org/springframework/boot/gradle/tasks/buildinfo/BuildInfo.html -:boot-build-image-javadoc: {api-documentation}/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.html -:boot-jar-javadoc: {api-documentation}/org/springframework/boot/gradle/tasks/bundling/BootJar.html -:boot-war-javadoc: {api-documentation}/org/springframework/boot/gradle/tasks/bundling/BootWar.html -:boot-run-javadoc: {api-documentation}/org/springframework/boot/gradle/tasks/run/BootRun.html -:github-code: https://github.com/spring-projects/spring-boot/tree/{github-tag} -:buildpacks-reference: https://buildpacks.io/docs -:paketo-reference: https://paketo.io/docs -:paketo-java-reference: {paketo-reference}/buildpacks/language-family-buildpacks/java -:nbt-gradle-plugin: https://graalvm.github.io/native-build-tools/{native-build-tools-version}/gradle-plugin.html - - - -include::introduction.adoc[leveloffset=+1] - -include::getting-started.adoc[leveloffset=+1] - -include::managing-dependencies.adoc[leveloffset=+1] - -include::packaging.adoc[leveloffset=+1] - -include::packaging-oci-image.adoc[leveloffset=+1] - -include::publishing.adoc[leveloffset=+1] - -include::running.adoc[leveloffset=+1] - -include::aot.adoc[leveloffset=+1] - -include::integrating-with-actuator.adoc[leveloffset=+1] - -include::reacting.adoc[leveloffset=+1] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/integrating-with-actuator.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/integrating-with-actuator.adoc deleted file mode 100644 index 1d016b8a9bdf..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/integrating-with-actuator.adoc +++ /dev/null @@ -1,97 +0,0 @@ -[[integrating-with-actuator]] -= Integrating with Actuator - - - -[[integrating-with-actuator.build-info]] -== Generating Build Information -Spring Boot Actuator's `info` endpoint automatically publishes information about your build in the presence of a `META-INF/build-info.properties` file. -A {build-info-javadoc}[`BuildInfo`] task is provided to generate this file. -The easiest way to use the task is through the plugin's DSL: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/integrating-with-actuator/build-info-basic.gradle[tags=build-info] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/integrating-with-actuator/build-info-basic.gradle.kts[tags=build-info] ----- - -This will configure a {build-info-javadoc}[`BuildInfo`] task named `bootBuildInfo` and, if it exists, make the Java plugin's `classes` task depend upon it. -The task's destination directory will be `META-INF` in the output directory of the main source set's resources (typically `build/resources/main`). - -By default, the generated build information is derived from the project: - -|=== -| Property | Default value - -| `build.artifact` -| The base name of the `bootJar` or `bootWar` task - -| `build.group` -| The group of the project - -| `build.name` -| The name of the project - -| `build.version` -| The version of the project - -| `build.time` -| The time at which the project is being built - -|=== - -The properties can be customized using the DSL: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/integrating-with-actuator/build-info-custom-values.gradle[tags=custom-values] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/integrating-with-actuator/build-info-custom-values.gradle.kts[tags=custom-values] ----- - -To exclude any of the default properties from the generated build information, add its name to the excludes. -For example, the `time` property can be excluded as follows: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/integrating-with-actuator/build-info-exclude-time.gradle[tags=exclude-time] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/integrating-with-actuator/build-info-exclude-time.gradle.kts[tags=exclude-time] ----- - - -The default value for `build.time` is the instant at which the project is being built. -A side-effect of this is that the task will never be up-to-date. -As a result, builds will take longer as more tasks, including the project's tests, will have to be executed. -Another side-effect is that the task's output will always change and, therefore, the build will not be truly repeatable. -If you value build performance or repeatability more highly than the accuracy of the `build.time` property, exclude the `time` property as shown in the preceding example. - -Additional properties can also be added to the build information: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/integrating-with-actuator/build-info-additional.gradle[tags=additional] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/integrating-with-actuator/build-info-additional.gradle.kts[tags=additional] ----- diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/introduction.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/introduction.adoc deleted file mode 100644 index 429cb4966ceb..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/introduction.adoc +++ /dev/null @@ -1,7 +0,0 @@ -[[introduction]] -= Introduction -The Spring Boot Gradle Plugin provides Spring Boot support in https://gradle.org[Gradle]. -It allows you to package executable jar or war archives, run Spring Boot applications, and use the dependency management provided by `spring-boot-dependencies`. -Spring Boot's Gradle plugin requires Gradle 7.x (7.5 or later) or 8.x and can be used with Gradle's {gradle-userguide}/configuration_cache.html[configuration cache]. - -In addition to this user guide, {api-documentation}[API documentation] is also available. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/managing-dependencies.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/managing-dependencies.adoc deleted file mode 100644 index 0b6be6238f87..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/managing-dependencies.adoc +++ /dev/null @@ -1,180 +0,0 @@ -[[managing-dependencies]] -= Managing Dependencies -To manage dependencies in your Spring Boot application, you can either apply the {dependency-management-plugin}[`io.spring.dependency-management`] plugin or use Gradle's native bom support. -The primary benefit of the former is that it offers property-based customization of managed versions, while using the latter will likely result in faster builds. - - - -[[managing-dependencies.dependency-management-plugin]] -== Managing Dependencies with the Dependency Management Plugin -When you apply the {dependency-management-plugin}[`io.spring.dependency-management`] plugin, Spring Boot's plugin will automatically <> from the version of Spring Boot that you are using. -This provides a similar dependency management experience to the one that's enjoyed by Maven users. -For example, it allows you to omit version numbers when declaring dependencies that are managed in the bom. -To make use of this functionality, declare dependencies in the usual way but omit the version number: - -[source,groovy,indent=0,subs="verbatim",role="primary"] -.Groovy ----- -include::../gradle/managing-dependencies/dependencies.gradle[tags=dependencies] ----- - -[source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ----- -include::../gradle/managing-dependencies/dependencies.gradle.kts[tags=dependencies] ----- - - - -[[managing-dependencies.dependency-management-plugin.customizing]] -=== Customizing Managed Versions -The `spring-boot-dependencies` bom that is automatically imported when the dependency management plugin is applied uses properties to control the versions of the dependencies that it manages. -Browse the {version-properties-appendix}[`Dependency versions Appendix`] in the Spring Boot reference for a complete list of these properties. - -To customize a managed version you set its corresponding property. -For example, to customize the version of SLF4J which is controlled by the `slf4j.version` property: - -[source,groovy,indent=0,subs="verbatim",role="primary"] -.Groovy ----- -include::../gradle/managing-dependencies/custom-version.gradle[tags=custom-version] ----- - -[source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ----- -include::../gradle/managing-dependencies/custom-version.gradle.kts[tags=custom-version] ----- - -WARNING: Each Spring Boot release is designed and tested against a specific set of third-party dependencies. -Overriding versions may cause compatibility issues and should be done with care. - - - -[[managing-dependencies.dependency-management-plugin.using-in-isolation]] -=== Using Spring Boot's Dependency Management in Isolation -Spring Boot's dependency management can be used in a project without applying Spring Boot's plugin to that project. -The `SpringBootPlugin` class provides a `BOM_COORDINATES` constant that can be used to import the bom without having to know its group ID, artifact ID, or version. - -First, configure the project to depend on the Spring Boot plugin but do not apply it: - -ifeval::["{artifact-release-type}" == "release"] -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/managing-dependencies/depend-on-plugin-release.gradle[] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/managing-dependencies/depend-on-plugin-release.gradle.kts[] ----- -endif::[] -ifeval::["{artifact-release-type}" == "milestone"] -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/managing-dependencies/depend-on-plugin-milestone.gradle[] ----- -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/managing-dependencies/depend-on-plugin-release.gradle.kts[] ----- -endif::[] -ifeval::["{artifact-release-type}" == "snapshot"] -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/managing-dependencies/depend-on-plugin-snapshot.gradle[] ----- -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/managing-dependencies/depend-on-plugin-release.gradle.kts[] ----- -endif::[] - -The Spring Boot plugin's dependency on the dependency management plugin means that you can use the dependency management plugin without having to declare a dependency on it. -This also means that you will automatically use the same version of the dependency management plugin as Spring Boot uses. - -Apply the dependency management plugin and then configure it to import Spring Boot's bom: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/managing-dependencies/configure-bom.gradle[tags=configure-bom] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/managing-dependencies/configure-bom.gradle.kts[tags=configure-bom] ----- - -The Kotlin code above is a bit awkward. -That's because we're using the imperative way of applying the dependency management plugin. - -We can make the code less awkward by applying the plugin from the root parent project, or by using the `plugins` block as we're doing for the Spring Boot plugin. -A downside of this method is that it forces us to specify the version of the dependency management plugin: - -[source,kotlin,indent=0,subs="verbatim,attributes"] ----- -include::../gradle/managing-dependencies/configure-bom-with-plugins.gradle.kts[tags=configure-bom] ----- - - - -[[managing-dependencies.dependency-management-plugin.learning-more]] -=== Learning More -To learn more about the capabilities of the dependency management plugin, please refer to its {dependency-management-plugin-documentation}[documentation]. - - - -[[managing-dependencies.gradle-bom-support]] -== Managing Dependencies with Gradle's Bom Support -Gradle allows a bom to be used to manage a project's versions by declaring it as a `platform` or `enforcedPlatform` dependency. -A `platform` dependency treats the versions in the bom as recommendations and other versions and constraints in the dependency graph may cause a version of a dependency other than that declared in the bom to be used. -An `enforcedPlatform` dependency treats the versions in the bom as requirements and they will override any other version found in the dependency graph. - -The `SpringBootPlugin` class provides a `BOM_COORDINATES` constant that can be used to declare a dependency upon Spring Boot's bom without having to know its group ID, artifact ID, or version, as shown in the following example: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/managing-dependencies/configure-platform.gradle[tags=configure-platform] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/managing-dependencies/configure-platform.gradle.kts[tags=configure-platform] ----- - -A platform or enforced platform will only constrain the versions of the configuration in which it has been declared or that extend from the configuration in which it has been declared. -As a result, in may be necessary to declare the same dependency in more than one configuration. - - - -[[managing-dependencies.gradle-bom-support.customizing]] -=== Customizing Managed Versions -When using Gradle's bom support, you cannot use the properties from `spring-boot-dependencies` to control the versions of the dependencies that it manages. -Instead, you must use one of the mechanisms that Gradle provides. -One such mechanism is a resolution strategy. -SLF4J's modules are all in the `org.slf4j` group so their version can be controlled by configuring every dependency in that group to use a particular version, as shown in the following example: - -[source,groovy,indent=0,subs="verbatim",role="primary"] -.Groovy ----- -include::../gradle/managing-dependencies/custom-version-with-platform.gradle[tags=custom-version] ----- - -[source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ----- -include::../gradle/managing-dependencies/custom-version-with-platform.gradle.kts[tags=custom-version] ----- - -WARNING: Each Spring Boot release is designed and tested against a specific set of third-party dependencies. -Overriding versions may cause compatibility issues and should be done with care. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc deleted file mode 100644 index 4322006b5040..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc +++ /dev/null @@ -1,553 +0,0 @@ -[[build-image]] -= Packaging OCI Images -The plugin can create an https://github.com/opencontainers/image-spec[OCI image] from a jar or war file using https://buildpacks.io[Cloud Native Buildpacks] (CNB). -Images can be built using the `bootBuildImage` task. - -NOTE: For security reasons, images build and run as non-root users. -See the {buildpacks-reference}/reference/spec/platform-api/#users[CNB specification] for more details. - -The task is automatically created when the `java` or `war` plugin is applied and is an instance of {boot-build-image-javadoc}[`BootBuildImage`]. - - - -[[build-image.docker-daemon]] -== Docker Daemon -The `bootBuildImage` task requires access to a Docker daemon. -By default, it will communicate with a Docker daemon over a local connection. -This works with https://docs.docker.com/install/[Docker Engine] on all supported platforms without configuration. - -Environment variables can be set to configure the `bootBuildImage` task to use an alternative local or remote connection. -The following table shows the environment variables and their values: - -|=== -| Environment variable | Description - -| DOCKER_HOST -| URL containing the host and port for the Docker daemon - for example `tcp://192.168.99.100:2376` - -| DOCKER_TLS_VERIFY -| Enable secure HTTPS protocol when set to `1` (optional) - -| DOCKER_CERT_PATH -| Path to certificate and key files for HTTPS (required if `DOCKER_TLS_VERIFY=1`, ignored otherwise) -|=== - -Docker daemon connection information can also be provided using `docker` properties in the plugin configuration. -The following table summarizes the available properties: - -|=== -| Property | Description - -| `host` -| URL containing the host and port for the Docker daemon - for example `tcp://192.168.99.100:2376` - -| `tlsVerify` -| Enable secure HTTPS protocol when set to `true` (optional) - -| `certPath` -| Path to certificate and key files for HTTPS (required if `tlsVerify` is `true`, ignored otherwise) - -| `bindHostToBuilder` -| When `true`, the value of the `host` property will be provided to the container that is created for the CNB builder (optional) -|=== - -For more details, see also <>. - - - -[[build-image.docker-registry]] -== Docker Registry -If the Docker images specified by the `builder` or `runImage` properties are stored in a private Docker image registry that requires authentication, the authentication credentials can be provided using `docker.builderRegistry` properties. - -If the generated Docker image is to be published to a Docker image registry, the authentication credentials can be provided using `docker.publishRegistry` properties. - -Properties are provided for user authentication or identity token authentication. -Consult the documentation for the Docker registry being used to store images for further information on supported authentication methods. - -The following table summarizes the available properties for `docker.builderRegistry` and `docker.publishRegistry`: - -|=== -| Property | Description - -| `username` -| Username for the Docker image registry user. Required for user authentication. - -| `password` -| Password for the Docker image registry user. Required for user authentication. - -| `url` -| Address of the Docker image registry. Optional for user authentication. - -| `email` -| E-mail address for the Docker image registry user. Optional for user authentication. - -| `token` -| Identity token for the Docker image registry user. Required for token authentication. -|=== - -For more details, see also <>. - - - -[[build-image.customization]] -== Image Customizations -The plugin invokes a {buildpacks-reference}/concepts/components/builder/[builder] to orchestrate the generation of an image. -The builder includes multiple {buildpacks-reference}/concepts/components/buildpack[buildpacks] that can inspect the application to influence the generated image. -By default, the plugin chooses a builder image. -The name of the generated image is deduced from project properties. - -Task properties can be used to configure how the builder should operate on the project. -The following table summarizes the available properties and their default values: - -|=== -| Property | Command-line option | Description | Default value - -| `builder` -| `--builder` -| Name of the Builder image to use. -| `paketobuildpacks/builder-jammy-base:latest` or `paketobuildpacks/builder-jammy-tiny:latest` when {nbt-gradle-plugin}[GraalVM Native Image plugin] is applied. - -| `runImage` -| `--runImage` -| Name of the run image to use. -| No default value, indicating the run image specified in Builder metadata should be used. - -| `imageName` -| `--imageName` -| {spring-boot-api}/buildpack/platform/docker/type/ImageReference.html#of-java.lang.String-[Image name] for the generated image. -| `docker.io/library/${project.name}:${project.version}` - -| `pullPolicy` -| `--pullPolicy` -| {spring-boot-api}/buildpack/platform/build/PullPolicy.html[Policy] used to determine when to pull the builder and run images from the registry. -Acceptable values are `ALWAYS`, `NEVER`, and `IF_NOT_PRESENT`. -| `ALWAYS` - -| `environment` -| -| Environment variables that should be passed to the builder. -| Empty or `['BP_NATIVE_IMAGE': 'true']` when {nbt-gradle-plugin}[GraalVM Native Image plugin] is applied. - -| `buildpacks` -| -a|Buildpacks that the builder should use when building the image. -Only the specified buildpacks will be used, overriding the default buildpacks included in the builder. -Buildpack references must be in one of the following forms: - -* Buildpack in the builder - `[urn:cnb:builder:][@]` -* Buildpack in a directory on the file system - `[file://]` -* Buildpack in a gzipped tar (.tgz) file on the file system - `[file://]/` -* Buildpack in an OCI image - `[docker://]/[:][@]` -| None, indicating the builder should use the buildpacks included in it. - -| `bindings` -| -a|https://docs.docker.com/storage/bind-mounts/[Volume bind mounts] that should be mounted to the builder container when building the image. -The bindings will be passed unparsed and unvalidated to Docker when creating the builder container. -Bindings must be in one of the following forms: - -* `:[:]` -* `:[:]` - -Where `` can contain: - -* `ro` to mount the volume as read-only in the container -* `rw` to mount the volume as readable and writable in the container -* `volume-opt=key=value` to specify key-value pairs consisting of an option name and its value -| - -| `network` -| `--network` -| The https://docs.docker.com/network/#network-drivers[network driver] the builder container will be configured to use. -The value supplied will be passed unvalidated to Docker when creating the builder container. -| - -| `cleanCache` -| `--cleanCache` -| Whether to clean the cache before building. -| `false` - -| `verboseLogging` -| -| Enables verbose logging of builder operations. -| `false` - -| `publish` -| `--publishImage` -| Whether to publish the generated image to a Docker registry. -| `false` - -| `tags` -| -| A list of one or more additional tags to apply to the generated image. -The values provided to the `tags` option should be *full* image references. -See <> for more details. -| - -| `buildCache` -| -| A cache containing layers created by buildpacks and used by the image building process. -| A named volume in the Docker daemon, with a name derived from the image name. - -| `launchCache` -| -| A cache containing layers created by buildpacks and used by the image launching process. -| A named volume in the Docker daemon, with a name derived from the image name. - -| `createdDate` -| `--createdDate` -| A date that will be used to set the `Created` field in the generated image's metadata. -The value must be a string in the ISO 8601 instant format, or `now` to use the current date and time. -| A fixed date that enables https://buildpacks.io/docs/features/reproducibility/[build reproducibility]. - -| `applicationDirectory` -| `--applicationDirectory` -| The path to a directory that application contents will be uploaded to in the builder image. -Application contents will also be in this location in the generated image. -| `/workspace` - -|=== - -NOTE: The plugin detects the target Java compatibility of the project using the JavaPlugin's `targetCompatibility` property. -When using the default Paketo builder and buildpacks, the plugin instructs the buildpacks to install the same Java version. -You can override this behaviour as shown in the <> examples. - - - -[[build-image.customization.tags]] -=== Tags format - -The values provided to the `tags` option should be *full* image references. -The accepted format is `[domainHost:port/][path/]name[:tag][@digest]`. - -If the domain is missing, it defaults to `docker.io`. -If the path is missing, it defaults to `library`. -If the tag is missing, it defaults to `latest`. - -Some examples: - -* `my-image` leads to the image reference `docker.io/library/my-image:latest` -* `my-repository/my-image` leads to `docker.io/my-repository/my-image:latest` -* `example.com/my-repository/my-image:1.0.0` will be used as is - - - -[[build-image.examples]] -== Examples - - - -[[build-image.examples.custom-image-builder]] -=== Custom Image Builder and Run Image -If you need to customize the builder used to create the image or the run image used to launch the built image, configure the task as shown in the following example: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-build-image-builder.gradle[tags=builder] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-build-image-builder.gradle.kts[tags=builder] ----- - -This configuration will use a builder image with the name `mine/java-cnb-builder` and the tag `latest`, and the run image named `mine/java-cnb-run` and the tag `latest`. - -The builder and run image can be specified on the command line as well, as shown in this example: - -[indent=0] ----- - $ gradle bootBuildImage --builder=mine/java-cnb-builder --runImage=mine/java-cnb-run ----- - - - -[[build-image.examples.builder-configuration]] -=== Builder Configuration -If the builder exposes configuration options, those can be set using the `environment` property. - -The following is an example of {paketo-java-reference}/#configuring-the-jvm-version[configuring the JVM version] used by the Paketo Java buildpacks at build time: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-build-image-env.gradle[tags=env] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-build-image-env.gradle.kts[tags=env] ----- - -If there is a network proxy between the Docker daemon the builder runs in and network locations that buildpacks download artifacts from, you will need to configure the builder to use the proxy. -When using the Paketo builder, this can be accomplished by setting the `HTTPS_PROXY` and/or `HTTP_PROXY` environment variables as show in the following example: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-build-image-env-proxy.gradle[tags=env] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-build-image-env-proxy.gradle.kts[tags=env] ----- - - - -[[build-image.examples.runtime-jvm-configuration]] -=== Runtime JVM Configuration -Paketo Java buildpacks {paketo-java-reference}/#runtime-jvm-configuration[configure the JVM runtime environment] by setting the `JAVA_TOOL_OPTIONS` environment variable. -The buildpack-provided `JAVA_TOOL_OPTIONS` value can be modified to customize JVM runtime behavior when the application image is launched in a container. - -Environment variable modifications that should be stored in the image and applied to every deployment can be set as described in the {paketo-reference}/buildpacks/configuration/#environment-variables[Paketo documentation] and shown in the following example: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-build-image-env-runtime.gradle[tags=env-runtime] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-build-image-env-runtime.gradle.kts[tags=env-runtime] ----- - - - -[[build-image.examples.custom-image-name]] -=== Custom Image Name -By default, the image name is inferred from the `name` and the `version` of the project, something like `docker.io/library/${project.name}:${project.version}`. -You can take control over the name by setting task properties, as shown in the following example: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-build-image-name.gradle[tags=image-name] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-build-image-name.gradle.kts[tags=image-name] ----- - -Note that this configuration does not provide an explicit tag so `latest` is used. -It is possible to specify a tag as well, either using `${project.version}`, any property available in the build or a hardcoded version. - -The image name can be specified on the command line as well, as shown in this example: - -[indent=0] ----- - $ gradle bootBuildImage --imageName=example.com/library/my-app:v1 ----- - - - -[[build-image.examples.buildpacks]] -=== Buildpacks -By default, the builder will use buildpacks included in the builder image and apply them in a pre-defined order. -An alternative set of buildpacks can be provided to apply buildpacks that are not included in the builder, or to change the order of included buildpacks. -When one or more buildpacks are provided, only the specified buildpacks will be applied. - -The following example instructs the builder to use a custom buildpack packaged in a `.tgz` file, followed by a buildpack included in the builder. - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-build-image-buildpacks.gradle[tags=buildpacks] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-build-image-buildpacks.gradle.kts[tags=buildpacks] ----- - -Buildpacks can be specified in any of the forms shown below. - -A buildpack located in a CNB Builder (version may be omitted if there is only one buildpack in the builder matching the `buildpack-id`): - -* `urn:cnb:builder:buildpack-id` -* `urn:cnb:builder:buildpack-id@0.0.1` -* `buildpack-id` -* `buildpack-id@0.0.1` - -A path to a directory containing buildpack content (not supported on Windows): - -* `\file:///path/to/buildpack/` -* `/path/to/buildpack/` - -A path to a gzipped tar file containing buildpack content: - -* `\file:///path/to/buildpack.tgz` -* `/path/to/buildpack.tgz` - -An OCI image containing a https://buildpacks.io/docs/buildpack-author-guide/package-a-buildpack/[packaged buildpack]: - -* `docker://example/buildpack` -* `docker:///example/buildpack:latest` -* `docker:///example/buildpack@sha256:45b23dee08...` -* `example/buildpack` -* `example/buildpack:latest` -* `example/buildpack@sha256:45b23dee08...` - - - -[[build-image.examples.publish]] -=== Image Publishing -The generated image can be published to a Docker registry by enabling a `publish` option. - -If the Docker registry requires authentication, the credentials can be configured using `docker.publishRegistry` properties. -If the Docker registry does not require authentication, the `docker.publishRegistry` configuration can be omitted. - -NOTE: The registry that the image will be published to is determined by the registry part of the image name (`docker.example.com` in these examples). -If `docker.publishRegistry` credentials are configured and include a `url` property, this value is passed to the registry but is not used to determine the publishing registry location. - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-build-image-publish.gradle[tags=publish] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-build-image-publish.gradle.kts[tags=publish] ----- - -The publish option can be specified on the command line as well, as shown in this example: - -[indent=0] ----- - $ gradle bootBuildImage --imageName=docker.example.com/library/my-app:v1 --publishImage ----- - - - -[[build-image.examples.caches]] -=== Builder Cache Configuration -The CNB builder caches layers that are used when building and launching an image. -By default, these caches are stored as named volumes in the Docker daemon with names that are derived from the full name of the target image. -If the image name changes frequently, for example when the project version is used as a tag in the image name, then the caches can be invalidated frequently. - -The cache volumes can be configured to use alternative names to give more control over cache lifecycle as shown in the following example: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-build-image-caches.gradle[tags=caches] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-build-image-caches.gradle.kts[tags=caches] ----- - - - -[[build-image.examples.docker]] -=== Docker Configuration - - - -[[build-image.examples.docker.minikube]] -==== Docker Configuration for minikube -The plugin can communicate with the https://minikube.sigs.k8s.io/docs/tasks/docker_daemon/[Docker daemon provided by minikube] instead of the default local connection. - -On Linux and macOS, environment variables can be set using the command `eval $(minikube docker-env)` after minikube has been started. - -The plugin can also be configured to use the minikube daemon by providing connection details similar to those shown in the following example: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-build-image-docker-host.gradle[tags=docker-host] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-build-image-docker-host.gradle.kts[tags=docker-host] ----- - - - -[[build-image.examples.docker.podman]] -==== Docker Configuration for podman -The plugin can communicate with a https://podman.io/[podman container engine]. - -The plugin can be configured to use podman local connection by providing connection details similar to those shown in the following example: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-build-image-docker-host-podman.gradle[tags=docker-host] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-build-image-docker-host-podman.gradle.kts[tags=docker-host] ----- - -TIP: With the `podman` CLI installed, the command `podman info --format='{{.Host.RemoteSocket.Path}}'` can be used to get the value for the `docker.host` configuration property shown in this example. - - - -[[build-image.examples.docker.colima]] -==== Docker Configuration for Colima -The plugin can communicate with the Docker daemon provided by https://github.com/abiosoft/colima[Colima]. -The `DOCKER_HOST` environment variable can be set by using the command `export DOCKER_HOST=$(docker context inspect colima -f '{{.Endpoints.docker.Host}}').` - -The plugin can also be configured to use Colima daemon by providing connection details similar to those shown in the following example: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-build-image-docker-host-colima.gradle[tags=docker-host] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-build-image-docker-host-colima.gradle.kts[tags=docker-host] ----- - - - -[[build-image.examples.docker.auth]] -==== Docker Configuration for Authentication -If the builder or run image are stored in a private Docker registry that supports user authentication, authentication details can be provided using `docker.builderRegistry` properties as shown in the following example: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-build-image-docker-auth-user.gradle[tags=docker-auth-user] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-build-image-docker-auth-user.gradle.kts[tags=docker-auth-user] ----- - -If the builder or run image is stored in a private Docker registry that supports token authentication, the token value can be provided using `docker.builderRegistry` as shown in the following example: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-build-image-docker-auth-token.gradle[tags=docker-auth-token] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-build-image-docker-auth-token.gradle.kts[tags=docker-auth-token] ----- diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging.adoc deleted file mode 100644 index e8b200f69d6c..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging.adoc +++ /dev/null @@ -1,375 +0,0 @@ -[[packaging-executable]] -= Packaging Executable Archives -The plugin can create executable archives (jar files and war files) that contain all of an application's dependencies and can then be run with `java -jar`. - - - -[[packaging-executable.jars]] -== Packaging Executable Jars -Executable jars can be built using the `bootJar` task. -The task is automatically created when the `java` plugin is applied and is an instance of {boot-jar-javadoc}[`BootJar`]. -The `assemble` task is automatically configured to depend upon the `bootJar` task so running `assemble` (or `build`) will also run the `bootJar` task. - - - -[[packaging-executable.wars]] -== Packaging Executable Wars -Executable wars can be built using the `bootWar` task. -The task is automatically created when the `war` plugin is applied and is an instance of {boot-war-javadoc}[`BootWar`]. -The `assemble` task is automatically configured to depend upon the `bootWar` task so running `assemble` (or `build`) will also run the `bootWar` task. - - - -[[packaging-executable.wars.deployable]] -=== Packaging Executable and Deployable Wars -A war file can be packaged such that it can be executed using `java -jar` and deployed to an external container. -To do so, the embedded servlet container dependencies should be added to the `providedRuntime` configuration, for example: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/war-container-dependency.gradle[tags=dependencies] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/war-container-dependency.gradle.kts[tags=dependencies] ----- - -This ensures that they are package in the war file's `WEB-INF/lib-provided` directory from where they will not conflict with the external container's own classes. - -NOTE: `providedRuntime` is preferred to Gradle's `compileOnly` configuration as, among other limitations, `compileOnly` dependencies are not on the test classpath so any web-based integration tests will fail. - - - -[[packaging-executable.and-plain-archives]] -== Packaging Executable and Plain Archives -By default, when the `bootJar` or `bootWar` tasks are configured, the `jar` or `war` tasks are configured to use `plain` as the convention for their archive classifier. -This ensures that `bootJar` and `jar` or `bootWar` and `war` have different output locations, allowing both the executable archive and the plain archive to be built at the same time. - -If you prefer that the executable archive, rather than the plain archive, uses a classifier, configure the classifiers as shown in the following example for the `jar` and `bootJar` tasks: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-jar-and-jar-classifiers.gradle[tags=classifiers] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-jar-and-jar-classifiers.gradle.kts[tags=classifiers] ----- - -Alternatively, if you prefer that the plain archive isn't built at all, disable its task as shown in the following example for the `jar` task: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/only-boot-jar.gradle[tags=disable-jar] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/only-boot-jar.gradle.kts[tags=disable-jar] ----- - -WARNING: Do not disable the `jar` task when creating native images. -See https://github.com/spring-projects/spring-boot/issues/33238[#33238] for details. - - -[[packaging-executable.configuring]] -== Configuring Executable Archive Packaging -The {boot-jar-javadoc}[`BootJar`] and {boot-war-javadoc}[`BootWar`] tasks are subclasses of Gradle's `Jar` and `War` tasks respectively. -As a result, all of the standard configuration options that are available when packaging a jar or war are also available when packaging an executable jar or war. -A number of configuration options that are specific to executable jars and wars are also provided. - - - -[[packaging-executable.configuring.main-class]] -=== Configuring the Main Class -By default, the executable archive's main class will be configured automatically by looking for a class with a `public static void main(String[])` method in the main source set's output. - -The main class can also be configured explicitly using the task's `mainClass` property: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-jar-main-class.gradle[tags=main-class] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-jar-main-class.gradle.kts[tags=main-class] ----- - -Alternatively, the main class name can be configured project-wide using the `mainClass` property of the Spring Boot DSL: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/spring-boot-dsl-main-class.gradle[tags=main-class] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/spring-boot-dsl-main-class.gradle.kts[tags=main-class] ----- - -If the {application-plugin}[`application` plugin] has been applied its `mainClass` property must be configured and can be used for the same purpose: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/application-plugin-main-class.gradle[tags=main-class] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/application-plugin-main-class.gradle.kts[tags=main-class] ----- - -Lastly, the `Start-Class` attribute can be configured on the task's manifest: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-jar-manifest-main-class.gradle[tags=main-class] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-jar-manifest-main-class.gradle.kts[tags=main-class] ----- - -NOTE: If the main class is written in Kotlin, the name of the generated Java class should be used. -By default, this is the name of the Kotlin class with the `Kt` suffix added. -For example, `ExampleApplication` becomes `ExampleApplicationKt`. -If another name is defined using `@JvmName` then that name should be used. - - - -[[packaging-executable.configuring.including-development-only-dependencies]] -=== Including Development-only Dependencies -By default all dependencies declared in the `developmentOnly` configuration will be excluded from an executable jar or war. - -If you want to include dependencies declared in the `developmentOnly` configuration in your archive, configure the classpath of its task to include the configuration, as shown in the following example for the `bootWar` task: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-war-include-devtools.gradle[tags=include-devtools] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-war-include-devtools.gradle.kts[tags=include-devtools] ----- - - - -[[packaging-executable.configuring.unpacking]] -=== Configuring Libraries that Require Unpacking -Most libraries can be used directly when nested in an executable archive, however certain libraries can have problems. -For example, JRuby includes its own nested jar support which assumes that `jruby-complete.jar` is always directly available on the file system. - -To deal with any problematic libraries, an executable archive can be configured to unpack specific nested jars to a temporary directory when the executable archive is run. -Libraries can be identified as requiring unpacking using Ant-style patterns that match against the absolute path of the source jar file: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-jar-requires-unpack.gradle[tags=requires-unpack] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-jar-requires-unpack.gradle.kts[tags=requires-unpack] ----- - -For more control a closure can also be used. -The closure is passed a `FileTreeElement` and should return a `boolean` indicating whether or not unpacking is required. - - - -[[packaging-executable.configuring.launch-script]] -=== Making an Archive Fully Executable -Spring Boot provides support for fully executable archives. -An archive is made fully executable by prepending a shell script that knows how to launch the application. -On Unix-like platforms, this launch script allows the archive to be run directly like any other executable or to be installed as a service. - -NOTE: Currently, some tools do not accept this format so you may not always be able to use this technique. -For example, `jar -xf` may silently fail to extract a jar or war that has been made fully-executable. -It is recommended that you only enable this option if you intend to execute it directly, rather than running it with `java -jar` or deploying it to a servlet container. - -To use this feature, the inclusion of the launch script must be enabled: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-jar-include-launch-script.gradle[tags=include-launch-script] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-jar-include-launch-script.gradle.kts[tags=include-launch-script] ----- - -This will add Spring Boot's default launch script to the archive. -The default launch script includes several properties with sensible default values. -The values can be customized using the `properties` property: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-jar-launch-script-properties.gradle[tags=launch-script-properties] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-jar-launch-script-properties.gradle.kts[tags=launch-script-properties] ----- - -If the default launch script does not meet your needs, the `script` property can be used to provide a custom launch script: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-jar-custom-launch-script.gradle[tags=custom-launch-script] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-jar-custom-launch-script.gradle.kts[tags=custom-launch-script] ----- - - - -[[packaging-executable.configuring.properties-launcher]] -=== Using the PropertiesLauncher -To use the `PropertiesLauncher` to launch an executable jar or war, configure the task's manifest to set the `Main-Class` attribute: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-war-properties-launcher.gradle[tags=properties-launcher] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-war-properties-launcher.gradle.kts[tags=properties-launcher] ----- - - - -[[packaging-executable.configuring.layered-archives]] -=== Packaging Layered Jar or War -By default, the `bootJar` task builds an archive that contains the application's classes and dependencies in `BOOT-INF/classes` and `BOOT-INF/lib` respectively. -Similarly, `bootWar` builds an archive that contains the application's classes in `WEB-INF/classes` and dependencies in `WEB-INF/lib` and `WEB-INF/lib-provided`. -For cases where a docker image needs to be built from the contents of the jar, it's useful to be able to separate these directories further so that they can be written into distinct layers. - -Layered jars use the same layout as regular boot packaged jars, but include an additional meta-data file that describes each layer. - -By default, the following layers are defined: - -* `dependencies` for any non-project dependency whose version does not contain `SNAPSHOT`. -* `spring-boot-loader` for the jar loader classes. -* `snapshot-dependencies` for any non-project dependency whose version contains `SNAPSHOT`. -* `application` for project dependencies, application classes, and resources. - -The layers order is important as it determines how likely previous layers can be cached when part of the application changes. -The default order is `dependencies`, `spring-boot-loader`, `snapshot-dependencies`, `application`. -Content that is least likely to change should be added first, followed by layers that are more likely to change. - -To disable this feature, you can do so in the following manner: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-jar-layered-disabled.gradle[tags=layered] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-jar-layered-disabled.gradle.kts[tags=layered] ----- - -When a layered jar or war is created, the `spring-boot-jarmode-layertools` jar will be added as a dependency to your archive. -With this jar on the classpath, you can launch your application in a special mode which allows the bootstrap code to run something entirely different from your application, for example, something that extracts the layers. -If you wish to exclude this dependency, you can do so in the following manner: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-jar-layered-exclude-tools.gradle[tags=layered] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-jar-layered-exclude-tools.gradle.kts[tags=layered] ----- - - - -[[packaging-executable.configuring.layered-archives.configuration]] -==== Custom Layers Configuration -Depending on your application, you may want to tune how layers are created and add new ones. - -This can be done using configuration that describes how the jar or war can be separated into layers, and the order of those layers. -The following example shows how the default ordering described above can be defined explicitly: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-jar-layered-custom.gradle[tags=layered] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/packaging/boot-jar-layered-custom.gradle.kts[tags=layered] ----- - -The `layered` DSL is defined using three parts: - -* The `application` closure defines how the application classes and resources should be layered. -* The `dependencies` closure defines how dependencies should be layered. -* The `layerOrder` method defines the order that the layers should be written. - -Nested `intoLayer` closures are used within `application` and `dependencies` sections to claim content for a layer. -These closures are evaluated in the order that they are defined, from top to bottom. -Any content not claimed by an earlier `intoLayer` closure remains available for subsequent ones to consider. - -The `intoLayer` closure claims content using nested `include` and `exclude` calls. -The `application` closure uses Ant-style path matching for include/exclude parameters. -The `dependencies` section uses `group:artifact[:version]` patterns. -It also provides `includeProjectDependencies()` and `excludeProjectDependencies()` methods that can be used to include or exclude project dependencies. - -If no `include` call is made, then all content (not claimed by an earlier closure) is considered. - -If no `exclude` call is made, then no exclusions are applied. - -Looking at the `dependencies` closure in the example above, we can see that the first `intoLayer` will claim all project dependencies for the `application` layer. -The next `intoLayer` will claim all SNAPSHOT dependencies for the `snapshot-dependencies` layer. -The third and final `intoLayer` will claim anything left (in this case, any dependency that is not a project dependency or a SNAPSHOT) for the `dependencies` layer. - -The `application` closure has similar rules. -First claiming `org/springframework/boot/loader/**` content for the `spring-boot-loader` layer. -Then claiming any remaining classes and resources for the `application` layer. - -NOTE: The order that `intoLayer` closures are added is often different from the order that the layers are written. -For this reason the `layerOrder` method must always be called and _must_ cover all layers referenced by the `intoLayer` calls. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/publishing.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/publishing.adoc deleted file mode 100644 index 92a14e4b3eb5..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/publishing.adoc +++ /dev/null @@ -1,31 +0,0 @@ -[[publishing-your-application]] -= Publishing your Application - - - -[[publishing-your-application.maven-publish]] -== Publishing with the Maven-publish Plugin -To publish your Spring Boot jar or war, add it to the publication using the `artifact` method on `MavenPublication`. -Pass the task that produces that artifact that you wish to publish to the `artifact` method. -For example, to publish the artifact produced by the default `bootJar` task: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/publishing/maven-publish.gradle[tags=publishing] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/publishing/maven-publish.gradle.kts[tags=publishing] ----- - - - -[[publishing-your-application.distribution]] -== Distributing with the Application Plugin -When the {application-plugin}[`application` plugin] is applied a distribution named `boot` is created. -This distribution contains the archive produced by the `bootJar` or `bootWar` task and scripts to launch it on Unix-like platforms and Windows. -Zip and tar distributions can be built by the `bootDistZip` and `bootDistTar` tasks respectively. -To use the `application` plugin, its `mainClassName` property must be configured with the name of your application's main class. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/reacting.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/reacting.adoc deleted file mode 100644 index bfb245f2d427..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/reacting.adoc +++ /dev/null @@ -1,86 +0,0 @@ -[[reacting-to-other-plugins]] -= Reacting to Other Plugins -When another plugin is applied the Spring Boot plugin reacts by making various changes to the project's configuration. -This section describes those changes. - - - -[[reacting-to-other-plugins.java]] -== Reacting to the Java Plugin -When Gradle's {java-plugin}[`java` plugin] is applied to a project, the Spring Boot plugin: - -1. Creates a {boot-jar-javadoc}[`BootJar`] task named `bootJar` that will create an executable, fat jar for the project. - The jar will contain everything on the runtime classpath of the main source set; classes are packaged in `BOOT-INF/classes` and jars are packaged in `BOOT-INF/lib` -2. Configures the `assemble` task to depend on the `bootJar` task. -3. Configures the `jar` task to use `plain` as the convention for its archive classifier. -4. Creates a {boot-build-image-javadoc}[`BootBuildImage`] task named `bootBuildImage` that will create a OCI image using a https://buildpacks.io[buildpack]. -5. Creates a {boot-run-javadoc}[`BootRun`] task named `bootRun` that can be used to run your application using the `main` source set to find its main method and provide its runtime classpath. -6. Creates a {boot-run-javadoc}['BootRun`] task named `bootTestRun` that can be used to run your application using the `test` source set to find its main method and provide its runtime classpath. -7. Creates a configuration named `bootArchives` that contains the artifact produced by the `bootJar` task. -8. Creates a configuration named `developmentOnly` for dependencies that are only required at development time, such as Spring Boot's Devtools, and should not be packaged in executable jars and wars. -9. Creates a configuration named `productionRuntimeClasspath`. It is equivalent to `runtimeClasspath` minus any dependencies that only appear in the `developmentOnly` configuration. -10. Configures any `JavaCompile` tasks with no configured encoding to use `UTF-8`. -11. Configures any `JavaCompile` tasks to use the `-parameters` compiler argument. - - - -[[reacting-to-other-plugins.kotlin]] -== Reacting to the Kotlin Plugin -When {kotlin-plugin}[Kotlin's Gradle plugin] is applied to a project, the Spring Boot plugin: - -1. Aligns the Kotlin version used in Spring Boot's dependency management with the version of the plugin. - This is achieved by setting the `kotlin.version` property with a value that matches the version of the Kotlin plugin. -2. Configures any `KotlinCompile` tasks to use the `-java-parameters` compiler argument. - - - -[[reacting-to-other-plugins.war]] -== Reacting to the War Plugin -When Gradle's {war-plugin}[`war` plugin] is applied to a project, the Spring Boot plugin: - -1. Creates a {boot-war-javadoc}[`BootWar`] task named `bootWar` that will create an executable, fat war for the project. - In addition to the standard packaging, everything in the `providedRuntime` configuration will be packaged in `WEB-INF/lib-provided`. -2. Configures the `assemble` task to depend on the `bootWar` task. -3. Configures the `war` task to use `plain` as the convention for its archive classifier. -4. Configures the `bootArchives` configuration to contain the artifact produced by the `bootWar` task. - - - -[[reacting-to-other-plugins.dependency-management]] -== Reacting to the Dependency Management Plugin -When the {dependency-management-plugin}[`io.spring.dependency-management` plugin] is applied to a project, the Spring Boot plugin will automatically import the `spring-boot-dependencies` bom. - - - -[[reacting-to-other-plugins.application]] -== Reacting to the Application Plugin -When Gradle's {application-plugin}[`application` plugin] is applied to a project, the Spring Boot plugin: - -1. Creates a `CreateStartScripts` task named `bootStartScripts` that will create scripts that launch the artifact in the `bootArchives` configuration using `java -jar`. - The task is configured to use the `applicationDefaultJvmArgs` property as a convention for its `defaultJvmOpts` property. -2. Creates a new distribution named `boot` and configures it to contain the artifact in the `bootArchives` configuration in its `lib` directory and the start scripts in its `bin` directory. -3. Configures the `bootRun` task to use the `mainClassName` property as a convention for its `main` property. -4. Configures the `bootRun` and `bootTestRun` tasks to use the `applicationDefaultJvmArgs` property as a convention for their `jvmArgs` property. -5. Configures the `bootJar` task to use the `mainClassName` property as a convention for the `Start-Class` entry in its manifest. -6. Configures the `bootWar` task to use the `mainClassName` property as a convention for the `Start-Class` entry in its manifest. - - - -[[reacting-to-other-plugins.nbt]] -== Reacting to the GraalVM Native Image Plugin -When the {nbt-gradle-plugin}[GraalVM Native Image plugin] is applied to a project, the Spring Boot plugin: - -. Applies the `org.springframework.boot.aot` plugin that: -.. Registers `aot` and `aotTest` source sets. -.. Registers a `ProcessAot` task named `processAot` that will generate AOT-optimized source for the application in the `aot` source set. -.. Configures the Java compilation and process resources tasks for the `aot` source set to depend upon `processAot`. -.. Registers a `ProcessTestAot` task named `processTestAot` that will generated AOT-optimized source for the application's tests in the `aotTest` source set. -.. Configures the Java compilation and process resources tasks for the `aotTest` source set to depend upon `processTestAot`. -. Adds the output of the `aot` source set to the classpath of the `main` GraalVM native binary. -. Adds the output of the `aotTest` source set to the classpath of the `test` GraalVM native binary. -. Configures the GraalVM extension to disable Toolchain detection. -. Configures each GraalVM native binary to require GraalVM 22.3 or later. -. Configures the `bootJar` task to include the reachability metadata produced by the `collectReachabilityMetadata` task in its jar. -. Configures the `bootBuildImage` task to use `paketobuildpacks/builder-jammy-tiny:latest` as its builder and to set `BP_NATIVE_IMAGE` to `true` in its environment. - - diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/running.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/running.adoc deleted file mode 100644 index 1ae0f5feb189..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/running.adoc +++ /dev/null @@ -1,152 +0,0 @@ -[[running-your-application]] -= Running your Application with Gradle -To run your application without first building an archive use the `bootRun` task: - -[source,bash,indent=0,subs="verbatim"] ----- - $ ./gradlew bootRun ----- - -The `bootRun` task is an instance of {boot-run-javadoc}[`BootRun`] which is a `JavaExec` subclass. -As such, all of the {gradle-dsl}/org.gradle.api.tasks.JavaExec.html[usual configuration options] for executing a Java process in Gradle are available to you. -The task is automatically configured to use the runtime classpath of the main source set. - -By default, the main class will be configured automatically by looking for a class with a `public static void main(String[])` method in the main source set's output. - -The main class can also be configured explicitly using the task's `main` property: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/running/boot-run-main.gradle[tags=main] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/running/boot-run-main.gradle.kts[tags=main] ----- - -Alternatively, the main class name can be configured project-wide using the `mainClass` property of the Spring Boot DSL: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/running/spring-boot-dsl-main-class-name.gradle[tags=main-class] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/running/spring-boot-dsl-main-class-name.gradle.kts[tags=main-class] ----- - -By default, `bootRun` will configure the JVM to optimize its launch for faster startup during development. -This behavior can be disabled by using the `optimizedLaunch` property, as shown in the following example: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/running/boot-run-disable-optimized-launch.gradle[tags=launch] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/running/boot-run-disable-optimized-launch.gradle.kts[tags=launch] ----- - -If the {application-plugin}[`application` plugin] has been applied, its `mainClass` property must be configured and can be used for the same purpose: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/running/application-plugin-main-class-name.gradle[tags=main-class] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/running/application-plugin-main-class-name.gradle.kts[tags=main-class] ----- - - - -[[running-your-application.passing-arguments]] -== Passing Arguments to your Application -Like all `JavaExec` tasks, arguments can be passed into `bootRun` from the command line using `--args=''` when using Gradle 4.9 or later. -For example, to run your application with a profile named `dev` active the following command can be used: - -[source,bash,indent=0,subs="verbatim"] ----- - $ ./gradlew bootRun --args='--spring.profiles.active=dev' ----- - -See {gradle-api}/org/gradle/api/tasks/JavaExec.html#setArgsString-java.lang.String-[the javadoc for `JavaExec.setArgsString`] for further details. - - - -[[running-your-application.passing-system-properties]] -== Passing System properties to your application -Since `bootRun` is a standard `JavaExec` task, system properties can be passed to the application's JVM by specifying them in the build script. -To make that value of a system property to be configurable set its value using a {gradle-dsl}/org.gradle.api.Project.html#N14FE1[project property]. -To allow a project property to be optional, reference it using `findProperty`. -Doing so also allows a default value to be provided using the `?:` Elvis operator, as shown in the following example: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/running/boot-run-system-property.gradle[tags=system-property] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/running/boot-run-system-property.gradle.kts[tags=system-property] ----- - -The preceding example sets that `com.example.property` system property to the value of the `example` project property. -If the `example` project property has not been set, the value of the system property will be `default`. - -Gradle allows project properties to be set in a variety of ways, including on the command line using the `-P` flag, as shown in the following example: - -[source,bash,indent=0,subs="verbatim,attributes"] ----- -$ ./gradlew bootRun -Pexample=custom ----- - -The preceding example sets the value of the `example` project property to `custom`. -`bootRun` will then use this as the value of the `com.example.property` system property. - - - -[[running-your-application.reloading-resources]] -== Reloading Resources -If devtools has been added to your project it will automatically monitor your application's classpath for changes. -Note that modified files need to be recompiled for the classpath to update in order to trigger reloading with devtools. -For more details on using devtools, refer to {spring-boot-reference}#using.devtools.restart[this section of the reference documentation]. - -Alternatively, you can configure `bootRun` such that your application's static resources are loaded from their source location: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/running/boot-run-source-resources.gradle[tags=source-resources] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/running/boot-run-source-resources.gradle.kts[tags=source-resources] ----- - -This makes them reloadable in the live application which can be helpful at development time. - - - -[[running-your-application.using-a-test-main-class]] -== Using a Test Main Class -In addition to `bootRun` a `bootTestRun` task is also registered. -Like `bootRun`, `bootTestRun` is an instance of `BootRun` but it's configured to use a main class found in the output of the test source set rather than the main source set. -It also uses the test source set's runtime classpath rather than the main source set's runtime classpath. -As `bootTestRun` is an instance of `BootRun`, all of the configuration options described above for `bootRun` can also be used with `bootTestRun`. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/aot/apply-native-image-plugin.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/aot/apply-native-image-plugin.gradle deleted file mode 100644 index 3e25cb898c99..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/aot/apply-native-image-plugin.gradle +++ /dev/null @@ -1,5 +0,0 @@ -plugins { - id 'org.springframework.boot' version '{gradle-project-version}' - id 'org.graalvm.buildtools.native' version '{native-build-tools-version}' - id 'java' -} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/aot/apply-native-image-plugin.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/aot/apply-native-image-plugin.gradle.kts deleted file mode 100644 index 27dba9592562..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/aot/apply-native-image-plugin.gradle.kts +++ /dev/null @@ -1,5 +0,0 @@ -plugins { - id("org.springframework.boot") version "{gradle-project-version}" - id("org.graalvm.buildtools.native") version "{native-build-tools-version}" - java -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/apply-plugin-release.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/apply-plugin-release.gradle deleted file mode 100644 index eea03ac0f688..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/apply-plugin-release.gradle +++ /dev/null @@ -1,3 +0,0 @@ -plugins { - id 'org.springframework.boot' version '{gradle-project-version}' -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/apply-plugin-release.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/apply-plugin-release.gradle.kts deleted file mode 100644 index fead5b05c83c..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/apply-plugin-release.gradle.kts +++ /dev/null @@ -1,3 +0,0 @@ -plugins { - id("org.springframework.boot") version "{gradle-project-version}" -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-additional.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-additional.gradle deleted file mode 100644 index 0412ea4ea883..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-additional.gradle +++ /dev/null @@ -1,17 +0,0 @@ -plugins { - id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' -} - -// tag::additional[] -springBoot { - buildInfo { - properties { - additional = [ - 'a': 'alpha', - 'b': 'bravo' - ] - } - } -} -// end::additional[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-additional.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-additional.gradle.kts deleted file mode 100644 index 2e3a790d1ec9..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-additional.gradle.kts +++ /dev/null @@ -1,18 +0,0 @@ -plugins { - java - id("org.springframework.boot") version "{gradle-project-version}" -} - -// tag::additional[] -springBoot { - buildInfo { - properties { - additional.set(mapOf( - "a" to "alpha", - "b" to "bravo" - )) - } - } -} -// end::additional[] - diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-basic.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-basic.gradle deleted file mode 100644 index b8554d14026f..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-basic.gradle +++ /dev/null @@ -1,10 +0,0 @@ -plugins { - id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' -} - -// tag::build-info[] -springBoot { - buildInfo() -} -// end::build-info[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-basic.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-basic.gradle.kts deleted file mode 100644 index 7eb212645a74..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-basic.gradle.kts +++ /dev/null @@ -1,10 +0,0 @@ -plugins { - java - id("org.springframework.boot") version "{gradle-project-version}" -} - -// tag::build-info[] -springBoot { - buildInfo() -} -// end::build-info[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-exclude-time.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-exclude-time.gradle deleted file mode 100644 index 49c17edab729..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-exclude-time.gradle +++ /dev/null @@ -1,12 +0,0 @@ -plugins { - id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' -} - -// tag::exclude-time[] -springBoot { - buildInfo { - excludes = ['time'] - } -} -// end::exclude-time[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-exclude-time.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-exclude-time.gradle.kts deleted file mode 100644 index 9043e7b96cb0..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/integrating-with-actuator/build-info-exclude-time.gradle.kts +++ /dev/null @@ -1,12 +0,0 @@ -plugins { - java - id("org.springframework.boot") version "{gradle-project-version}" -} - -// tag::exclude-time[] -springBoot { - buildInfo { - excludes.set(setOf("time")) - } -} -// end::exclude-time[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/configure-bom-with-plugins.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/configure-bom-with-plugins.gradle.kts deleted file mode 100644 index 9f22625463d4..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/configure-bom-with-plugins.gradle.kts +++ /dev/null @@ -1,31 +0,0 @@ -import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension - -// tag::configure-bom[] -plugins { - java - id("org.springframework.boot") version "{gradle-project-version}" apply false - id("io.spring.dependency-management") version "{dependency-management-plugin-version}" -} - -dependencyManagement { - imports { - mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) - } -} -// end::configure-bom[] - -the().apply { - resolutionStrategy { - eachDependency { - if (requested.group == "org.springframework.boot") { - useVersion("TEST-SNAPSHOT") - } - } - } -} - -repositories { - maven { - url = uri("file:repository") - } -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/depend-on-plugin-release.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/depend-on-plugin-release.gradle deleted file mode 100644 index 88fba72d152b..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/depend-on-plugin-release.gradle +++ /dev/null @@ -1,3 +0,0 @@ -plugins { - id 'org.springframework.boot' version '{gradle-project-version}' apply false -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/depend-on-plugin-release.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/depend-on-plugin-release.gradle.kts deleted file mode 100644 index 5bebec31c3f8..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/depend-on-plugin-release.gradle.kts +++ /dev/null @@ -1,3 +0,0 @@ -plugins { - id("org.springframework.boot") version "{gradle-project-version}" apply false -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/application-plugin-main-class.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/application-plugin-main-class.gradle deleted file mode 100644 index 02a24dda6794..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/application-plugin-main-class.gradle +++ /dev/null @@ -1,11 +0,0 @@ -plugins { - id 'java' - id 'application' - id 'org.springframework.boot' version '{gradle-project-version}' -} - -// tag::main-class[] -application { - mainClass = 'com.example.ExampleApplication' -} -// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/application-plugin-main-class.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/application-plugin-main-class.gradle.kts deleted file mode 100644 index 23a84fbc2576..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/application-plugin-main-class.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -plugins { - java - application - id("org.springframework.boot") version "{gradle-project-version}" -} - -// tag::main-class[] -application { - mainClass.set("com.example.ExampleApplication") -} -// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env.gradle.kts deleted file mode 100644 index 976f502344c3..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env.gradle.kts +++ /dev/null @@ -1,21 +0,0 @@ -import org.springframework.boot.gradle.tasks.bundling.BootBuildImage - -plugins { - java - id("org.springframework.boot") version "{gradle-project-version}" -} - -// tag::env[] -tasks.named("bootBuildImage") { - environment.set(environment.get() + mapOf("BP_JVM_VERSION" to "17")) -} -// end::env[] - -tasks.register("bootBuildImageEnvironment") { - doFirst { - for((name, value) in tasks.getByName("bootBuildImage").environment.get()) { - print(name + "=" + value) - } - } -} - diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-disabled.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-disabled.gradle deleted file mode 100644 index 3ea9a6277269..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-disabled.gradle +++ /dev/null @@ -1,16 +0,0 @@ -plugins { - id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' -} - -tasks.named("bootJar") { - mainClass = 'com.example.ExampleApplication' -} - -// tag::layered[] -tasks.named("bootJar") { - layered { - enabled = false - } -} -// end::layered[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-exclude-tools.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-exclude-tools.gradle deleted file mode 100644 index 7175092af835..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-exclude-tools.gradle +++ /dev/null @@ -1,16 +0,0 @@ -plugins { - id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' -} - -tasks.named("bootJar") { - mainClass = 'com.example.ExampleApplication' -} - -// tag::layered[] -tasks.named("bootJar") { - layered { - includeLayerTools = false - } -} -// end::layered[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-exclude-tools.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-exclude-tools.gradle.kts deleted file mode 100644 index 06c177b53044..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-exclude-tools.gradle.kts +++ /dev/null @@ -1,18 +0,0 @@ -import org.springframework.boot.gradle.tasks.bundling.BootJar - -plugins { - java - id("org.springframework.boot") version "{gradle-project-version}" -} - -tasks.named("bootJar") { - mainClass.set("com.example.ExampleApplication") -} - -// tag::layered[] -tasks.named("bootJar") { - layered { - includeLayerTools.set(false) - } -} -// end::layered[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-main-class.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-main-class.gradle deleted file mode 100644 index 55b9de16f35c..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-main-class.gradle +++ /dev/null @@ -1,10 +0,0 @@ -plugins { - id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' -} - -// tag::main-class[] -tasks.named("bootJar") { - mainClass = 'com.example.ExampleApplication' -} -// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-manifest-main-class.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-manifest-main-class.gradle deleted file mode 100644 index f384d8e6a6b5..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-manifest-main-class.gradle +++ /dev/null @@ -1,12 +0,0 @@ -plugins { - id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' -} - -// tag::main-class[] -tasks.named("bootJar") { - manifest { - attributes 'Start-Class': 'com.example.ExampleApplication' - } -} -// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-war-properties-launcher.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-war-properties-launcher.gradle deleted file mode 100644 index 2872469f60fb..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-war-properties-launcher.gradle +++ /dev/null @@ -1,16 +0,0 @@ -plugins { - id 'war' - id 'org.springframework.boot' version '{gradle-project-version}' -} - -tasks.named("bootWar") { - mainClass = 'com.example.ExampleApplication' -} - -// tag::properties-launcher[] -tasks.named("bootWar") { - manifest { - attributes 'Main-Class': 'org.springframework.boot.loader.PropertiesLauncher' - } -} -// end::properties-launcher[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-war-properties-launcher.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-war-properties-launcher.gradle.kts deleted file mode 100644 index 19d723b795fa..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-war-properties-launcher.gradle.kts +++ /dev/null @@ -1,18 +0,0 @@ -import org.springframework.boot.gradle.tasks.bundling.BootWar - -plugins { - war - id("org.springframework.boot") version "{gradle-project-version}" -} - -tasks.named("bootWar") { - mainClass.set("com.example.ExampleApplication") -} - -// tag::properties-launcher[] -tasks.named("bootWar") { - manifest { - attributes("Main-Class" to "org.springframework.boot.loader.PropertiesLauncher") - } -} -// end::properties-launcher[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/only-boot-jar.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/only-boot-jar.gradle deleted file mode 100644 index 748aa957f381..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/only-boot-jar.gradle +++ /dev/null @@ -1,14 +0,0 @@ -plugins { - id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' -} - -// tag::disable-jar[] -tasks.named("jar") { - enabled = false -} -// end::disable-jar[] - -tasks.named("bootJar") { - mainClass = 'com.example.Application' -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/spring-boot-dsl-main-class.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/spring-boot-dsl-main-class.gradle deleted file mode 100644 index c84dffd88e47..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/spring-boot-dsl-main-class.gradle +++ /dev/null @@ -1,10 +0,0 @@ -plugins { - id 'java' - id 'org.springframework.boot' version '{gradle-project-version}' -} - -// tag::main-class[] -springBoot { - mainClass = 'com.example.ExampleApplication' -} -// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/spring-boot-dsl-main-class.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/spring-boot-dsl-main-class.gradle.kts deleted file mode 100644 index 5fadbfe46b58..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/spring-boot-dsl-main-class.gradle.kts +++ /dev/null @@ -1,10 +0,0 @@ -plugins { - java - id("org.springframework.boot") version "{gradle-project-version}" -} - -// tag::main-class[] -springBoot { - mainClass.set("com.example.ExampleApplication") -} -// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/application-plugin-main-class-name.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/application-plugin-main-class-name.gradle deleted file mode 100644 index 1ee1d23abaef..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/application-plugin-main-class-name.gradle +++ /dev/null @@ -1,12 +0,0 @@ -plugins { - id 'java' - id 'application' - id 'org.springframework.boot' version '{gradle-project-version}' -} - -// tag::main-class[] -application { - mainClass = 'com.example.ExampleApplication' -} -// end::main-class[] - diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/application-plugin-main-class-name.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/application-plugin-main-class-name.gradle.kts deleted file mode 100644 index 23a84fbc2576..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/application-plugin-main-class-name.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -plugins { - java - application - id("org.springframework.boot") version "{gradle-project-version}" -} - -// tag::main-class[] -application { - mainClass.set("com.example.ExampleApplication") -} -// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/spring-boot-dsl-main-class-name.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/spring-boot-dsl-main-class-name.gradle deleted file mode 100644 index 6703507d6d28..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/spring-boot-dsl-main-class-name.gradle +++ /dev/null @@ -1,11 +0,0 @@ -plugins { - id 'java' - id 'application' - id 'org.springframework.boot' version '{gradle-project-version}' -} - -// tag::main-class[] -springBoot { - mainClass = 'com.example.ExampleApplication' -} -// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/spring-boot-dsl-main-class-name.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/spring-boot-dsl-main-class-name.gradle.kts deleted file mode 100644 index af94660284c3..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/spring-boot-dsl-main-class-name.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -plugins { - java - application - id("org.springframework.boot") version "{gradle-project-version}" -} - -// tag::main-class[] -springBoot { - mainClass.set("com.example.ExampleApplication") -} -// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/dsl/SpringBootExtension.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/dsl/SpringBootExtension.java index 3aded1cb5acc..d082ee8ad13e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/dsl/SpringBootExtension.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/dsl/SpringBootExtension.java @@ -93,7 +93,7 @@ public void buildInfo(Action configurer) { tasks.named(JavaPlugin.CLASSES_TASK_NAME).configure((task) -> task.dependsOn(bootBuildInfo)); bootBuildInfo.configure((buildInfo) -> buildInfo.getProperties() .getArtifact() - .convention(this.project.provider(() -> determineArtifactBaseName()))); + .convention(this.project.provider(this::determineArtifactBaseName))); }); if (configurer != null) { bootBuildInfo.configure(configurer); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ApplicationPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ApplicationPluginAction.java index 62874380d3e5..f87b6fe5fce9 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ApplicationPluginAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ApplicationPluginAction.java @@ -130,7 +130,7 @@ private void configureFilePermissions(CopySpec copySpec, int mode) { if (GradleVersion.current().compareTo(GradleVersion.version("8.3")) >= 0) { try { Method filePermissions = copySpec.getClass().getMethod("filePermissions", Action.class); - filePermissions.invoke(copySpec, new Action() { + filePermissions.invoke(copySpec, new Action<>() { @Override public void execute(Object filePermissions) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/CycloneDxPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/CycloneDxPluginAction.java new file mode 100644 index 000000000000..97bf83e330d0 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/CycloneDxPluginAction.java @@ -0,0 +1,134 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.gradle.plugin; + +import org.cyclonedx.gradle.CycloneDxPlugin; +import org.cyclonedx.gradle.CycloneDxTask; +import org.gradle.api.Action; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Copy; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskProvider; +import org.gradle.api.tasks.bundling.Jar; + +import org.springframework.boot.gradle.tasks.bundling.BootJar; +import org.springframework.boot.gradle.tasks.bundling.BootWar; + +/** + * {@link Action} that is executed in response to the {@link CycloneDxPlugin} being + * applied. + * + * @author Moritz Halbritter + */ +final class CycloneDxPluginAction implements PluginApplicationAction { + + @Override + public Class> getPluginClass() { + return CycloneDxPlugin.class; + } + + @Override + public void execute(Project project) { + TaskProvider cycloneDxTaskProvider = project.getTasks() + .named("cyclonedxBom", CycloneDxTask.class); + configureCycloneDxTask(cycloneDxTaskProvider); + configureJavaPlugin(project, cycloneDxTaskProvider); + configureSpringBootPlugin(project, cycloneDxTaskProvider); + } + + private void configureCycloneDxTask(TaskProvider taskProvider) { + taskProvider.configure((task) -> { + task.getProjectType().convention("application"); + task.getOutputFormat().convention("json"); + task.getOutputName().convention("application.cdx"); + task.getIncludeLicenseText().convention(false); + }); + } + + private void configureJavaPlugin(Project project, TaskProvider cycloneDxTaskProvider) { + configurePlugin(project, JavaPlugin.class, (javaPlugin) -> { + JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); + SourceSet main = javaPluginExtension.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME); + configureTask(project, main.getProcessResourcesTaskName(), Copy.class, (copy) -> { + copy.dependsOn(cycloneDxTaskProvider); + Provider sbomFileName = cycloneDxTaskProvider + .map((cycloneDxTask) -> cycloneDxTask.getOutputName().get() + getSbomExtension(cycloneDxTask)); + copy.from(cycloneDxTaskProvider, (spec) -> spec.include(sbomFileName.get()).into("META-INF/sbom")); + }); + }); + } + + private void configureSpringBootPlugin(Project project, TaskProvider cycloneDxTaskProvider) { + configurePlugin(project, SpringBootPlugin.class, (springBootPlugin) -> { + configureBootJarTask(project, cycloneDxTaskProvider); + configureBootWarTask(project, cycloneDxTaskProvider); + }); + } + + private void configureBootJarTask(Project project, TaskProvider cycloneDxTaskProvider) { + configureTask(project, SpringBootPlugin.BOOT_JAR_TASK_NAME, BootJar.class, + (bootJar) -> configureBootJarTask(bootJar, cycloneDxTaskProvider)); + } + + private void configureBootWarTask(Project project, TaskProvider cycloneDxTaskProvider) { + configureTask(project, SpringBootPlugin.BOOT_WAR_TASK_NAME, BootWar.class, + (bootWar) -> configureBootWarTask(bootWar, cycloneDxTaskProvider)); + } + + private void configureBootJarTask(BootJar task, TaskProvider cycloneDxTaskProvider) { + configureJarTask(task, cycloneDxTaskProvider); + } + + private void configureBootWarTask(BootWar task, TaskProvider cycloneDxTaskProvider) { + configureJarTask(task, cycloneDxTaskProvider); + } + + private void configureJarTask(Jar task, TaskProvider cycloneDxTaskProvider) { + Provider sbomFileName = cycloneDxTaskProvider.map((cycloneDxTask) -> "META-INF/sbom/" + + cycloneDxTask.getOutputName().get() + getSbomExtension(cycloneDxTask)); + task.manifest((manifest) -> { + manifest.getAttributes().put("Sbom-Format", "CycloneDX"); + manifest.getAttributes().put("Sbom-Location", sbomFileName); + }); + } + + private String getSbomExtension(CycloneDxTask task) { + String format = task.getOutputFormat().get(); + if ("all".equals(format)) { + return ".json"; + } + return "." + format; + } + + private void configureTask(Project project, String name, Class type, Action action) { + project.getTasks().withType(type).configureEach((task) -> { + if (task.getName().equals(name)) { + action.execute(task); + } + }); + } + + private > void configurePlugin(Project project, Class plugin, Action action) { + project.getPlugins().withType(plugin, action); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java index d31b39c74984..cef1a8e3159c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java @@ -76,7 +76,9 @@ public Class> getPluginClass() { public void execute(Project project) { classifyJarTask(project); configureBuildTask(project); + configureProductionRuntimeClasspathConfiguration(project); configureDevelopmentOnlyConfiguration(project); + configureTestAndDevelopmentOnlyConfiguration(project); TaskProvider resolveMainClassName = configureResolveMainClassNameTask(project); TaskProvider bootJar = configureBootJarTask(project, resolveMainClassName); configureBootBuildImageTask(project, bootJar); @@ -158,12 +160,15 @@ private TaskProvider configureBootJarTask(Project project, .getByName(SourceSet.MAIN_SOURCE_SET_NAME); Configuration developmentOnly = project.getConfigurations() .getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); + Configuration testAndDevelopmentOnly = project.getConfigurations() + .getByName(SpringBootPlugin.TEST_AND_DEVELOPMENT_ONLY_CONFIGURATION_NAME); Configuration productionRuntimeClasspath = project.getConfigurations() .getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME); Configuration runtimeClasspath = project.getConfigurations() .getByName(mainSourceSet.getRuntimeClasspathConfigurationName()); Callable classpath = () -> mainSourceSet.getRuntimeClasspath() .minus((developmentOnly.minus(productionRuntimeClasspath))) + .minus((testAndDevelopmentOnly.minus(productionRuntimeClasspath))) .filter(new JarTypeFileSpec()); return project.getTasks().register(SpringBootPlugin.BOOT_JAR_TASK_NAME, BootJar.class, (bootJar) -> { bootJar.setDescription( @@ -269,15 +274,12 @@ private void configureAdditionalMetadataLocations(JavaCompile compile) { } @SuppressWarnings({ "rawtypes", "unchecked" }) - private void configureDevelopmentOnlyConfiguration(Project project) { - Configuration developmentOnly = project.getConfigurations() - .create(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); - developmentOnly - .setDescription("Configuration for development-only dependencies such as Spring Boot's DevTools."); - Configuration runtimeClasspath = project.getConfigurations() - .getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); + private void configureProductionRuntimeClasspathConfiguration(Project project) { Configuration productionRuntimeClasspath = project.getConfigurations() .create(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME); + productionRuntimeClasspath.setVisible(false); + Configuration runtimeClasspath = project.getConfigurations() + .getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); productionRuntimeClasspath.attributes((attributes) -> { ProviderFactory providers = project.getProviders(); AttributeContainer sourceAttributes = runtimeClasspath.getAttributes(); @@ -286,13 +288,35 @@ private void configureDevelopmentOnlyConfiguration(Project project) { providers.provider(() -> sourceAttributes.getAttribute(attribute))); } }); - productionRuntimeClasspath.setVisible(false); productionRuntimeClasspath.setExtendsFrom(runtimeClasspath.getExtendsFrom()); productionRuntimeClasspath.setCanBeResolved(runtimeClasspath.isCanBeResolved()); productionRuntimeClasspath.setCanBeConsumed(runtimeClasspath.isCanBeConsumed()); + } + + private void configureDevelopmentOnlyConfiguration(Project project) { + Configuration developmentOnly = project.getConfigurations() + .create(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); + developmentOnly + .setDescription("Configuration for development-only dependencies such as Spring Boot's DevTools."); + Configuration runtimeClasspath = project.getConfigurations() + .getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); + runtimeClasspath.extendsFrom(developmentOnly); } + private void configureTestAndDevelopmentOnlyConfiguration(Project project) { + Configuration testAndDevelopmentOnly = project.getConfigurations() + .create(SpringBootPlugin.TEST_AND_DEVELOPMENT_ONLY_CONFIGURATION_NAME); + testAndDevelopmentOnly + .setDescription("Configuration for test and development-only dependencies such as Spring Boot's DevTools."); + Configuration runtimeClasspath = project.getConfigurations() + .getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); + runtimeClasspath.extendsFrom(testAndDevelopmentOnly); + Configuration testImplementation = project.getConfigurations() + .getByName(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME); + testImplementation.extendsFrom(testAndDevelopmentOnly); + } + /** * Task {@link Action} to add additional meta-data locations. We need to use an * inner-class rather than a lambda due to diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/NativeImagePluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/NativeImagePluginAction.java index 2984088e09d8..d7555c4255b2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/NativeImagePluginAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/NativeImagePluginAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,14 +22,12 @@ import org.graalvm.buildtools.gradle.NativeImagePlugin; import org.graalvm.buildtools.gradle.dsl.GraalVMExtension; -import org.graalvm.buildtools.gradle.dsl.GraalVMReachabilityMetadataRepositoryExtension; import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.FileCollection; import org.gradle.api.java.archives.Manifest; -import org.gradle.api.plugins.ExtensionAware; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.tasks.SourceSetContainer; @@ -47,8 +45,7 @@ class NativeImagePluginAction implements PluginApplicationAction { @Override - public Class> getPluginClass() - throws ClassNotFoundException, NoClassDefFoundError { + public Class> getPluginClass() { return NativeImagePlugin.class; } @@ -61,7 +58,6 @@ public void execute(Project project) { GraalVMExtension graalVmExtension = configureGraalVmExtension(project); configureMainNativeBinaryClasspath(project, sourceSets, graalVmExtension); configureTestNativeBinaryClasspath(sourceSets, graalVmExtension); - configureGraalVmReachabilityExtension(graalVmExtension); copyReachabilityMetadataToBootJar(project); configureBootBuildImageToProduceANativeImage(project); configureJarManifestNativeAttribute(project); @@ -84,7 +80,8 @@ private Iterable removeDevelopmentOnly(Set configu } private boolean isNotDevelopmentOnly(Configuration configuration) { - return !SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME.equals(configuration.getName()); + return !SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME.equals(configuration.getName()) + && !SpringBootPlugin.TEST_AND_DEVELOPMENT_ONLY_CONFIGURATION_NAME.equals(configuration.getName()); } private void configureTestNativeBinaryClasspath(SourceSetContainer sourceSets, GraalVMExtension graalVmExtension) { @@ -99,12 +96,6 @@ private GraalVMExtension configureGraalVmExtension(Project project) { return extension; } - private void configureGraalVmReachabilityExtension(GraalVMExtension graalVmExtension) { - GraalVMReachabilityMetadataRepositoryExtension extension = ((ExtensionAware) graalVmExtension).getExtensions() - .getByType(GraalVMReachabilityMetadataRepositoryExtension.class); - extension.getEnabled().set(true); - } - private void copyReachabilityMetadataToBootJar(Project project) { project.getTasks() .named(SpringBootPlugin.BOOT_JAR_TASK_NAME, BootJar.class) @@ -114,10 +105,7 @@ private void copyReachabilityMetadataToBootJar(Project project) { private void configureBootBuildImageToProduceANativeImage(Project project) { project.getTasks() .named(SpringBootPlugin.BOOT_BUILD_IMAGE_TASK_NAME, BootBuildImage.class) - .configure((bootBuildImage) -> { - bootBuildImage.getBuilder().convention("paketobuildpacks/builder-jammy-tiny:latest"); - bootBuildImage.getEnvironment().put("BP_NATIVE_IMAGE", "true"); - }); + .configure((bootBuildImage) -> bootBuildImage.getEnvironment().put("BP_NATIVE_IMAGE", "true")); } private void configureJarManifestNativeAttribute(Project project) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootAotPlugin.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootAotPlugin.java index fa1418d7ed18..477f718d4a04 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootAotPlugin.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootAotPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,9 +81,9 @@ public void apply(Project project) { JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); SourceSetContainer sourceSets = javaPluginExtension.getSourceSets(); SourceSet mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); - SourceSet aotSourceSet = configureSourceSet(project, "aot", mainSourceSet); + SourceSet aotSourceSet = configureSourceSet(project, AOT_SOURCE_SET_NAME, mainSourceSet); SourceSet testSourceSet = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME); - SourceSet aotTestSourceSet = configureSourceSet(project, "aotTest", testSourceSet); + SourceSet aotTestSourceSet = configureSourceSet(project, AOT_TEST_SOURCE_SET_NAME, testSourceSet); plugins.withType(SpringBootPlugin.class).all((bootPlugin) -> { registerProcessAotTask(project, aotSourceSet, mainSourceSet); registerProcessTestAotTask(project, mainSourceSet, aotTestSourceSet, testSourceSet); @@ -119,7 +119,9 @@ private void configureJavaRuntimeUsageAttribute(Project project, AttributeContai private void registerProcessAotTask(Project project, SourceSet aotSourceSet, SourceSet mainSourceSet) { TaskProvider resolveMainClassName = project.getTasks() .named(SpringBootPlugin.RESOLVE_MAIN_CLASS_NAME_TASK_NAME, ResolveMainClassName.class); - Configuration aotClasspath = createAotProcessingClasspath(project, PROCESS_AOT_TASK_NAME, mainSourceSet); + Configuration aotClasspath = createAotProcessingClasspath(project, PROCESS_AOT_TASK_NAME, mainSourceSet, + Set.of(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME, + SpringBootPlugin.TEST_AND_DEVELOPMENT_ONLY_CONFIGURATION_NAME)); project.getDependencies().add(aotClasspath.getName(), project.files(mainSourceSet.getOutput())); Configuration compileClasspath = project.getConfigurations() .getByName(aotSourceSet.getCompileClasspathConfigurationName()); @@ -129,7 +131,7 @@ private void registerProcessAotTask(Project project, SourceSet aotSourceSet, Sou .dir("generated/" + aotSourceSet.getName() + "Resources"); TaskProvider processAot = project.getTasks() .register(PROCESS_AOT_TASK_NAME, ProcessAot.class, (task) -> { - configureAotTask(project, aotSourceSet, task, mainSourceSet, resourcesOutput); + configureAotTask(project, aotSourceSet, task, resourcesOutput); task.getApplicationMainClass() .set(resolveMainClassName.flatMap(ResolveMainClassName::readMainClassName)); task.setClasspath(aotClasspath); @@ -142,7 +144,7 @@ private void registerProcessAotTask(Project project, SourceSet aotSourceSet, Sou configureDependsOn(project, aotSourceSet, processAot); } - private void configureAotTask(Project project, SourceSet sourceSet, AbstractAot task, SourceSet inputSourceSet, + private void configureAotTask(Project project, SourceSet sourceSet, AbstractAot task, Provider resourcesOutput) { task.getSourcesOutput() .set(project.getLayout().getBuildDirectory().dir("generated/" + sourceSet.getName() + "Sources")); @@ -150,7 +152,7 @@ private void configureAotTask(Project project, SourceSet sourceSet, AbstractAot task.getClassesOutput() .set(project.getLayout().getBuildDirectory().dir("generated/" + sourceSet.getName() + "Classes")); task.getGroupId().set(project.provider(() -> String.valueOf(project.getGroup()))); - task.getArtifactId().set(project.provider(() -> project.getName())); + task.getArtifactId().set(project.provider(project::getName)); configureToolchainConvention(project, task); } @@ -161,17 +163,19 @@ private void configureToolchainConvention(Project project, AbstractAot aotTask) } @SuppressWarnings({ "unchecked", "rawtypes" }) - private Configuration createAotProcessingClasspath(Project project, String taskName, SourceSet inputSourceSet) { + private Configuration createAotProcessingClasspath(Project project, String taskName, SourceSet inputSourceSet, + Set developmentOnlyConfigurationNames) { Configuration base = project.getConfigurations() .getByName(inputSourceSet.getRuntimeClasspathConfigurationName()); - Configuration aotClasspath = project.getConfigurations().create(taskName + "Classpath", (classpath) -> { + return project.getConfigurations().create(taskName + "Classpath", (classpath) -> { classpath.setCanBeConsumed(false); if (!classpath.isCanBeResolved()) { throw new IllegalStateException("Unexpected"); } classpath.setCanBeResolved(true); classpath.setDescription("Classpath of the " + taskName + " task."); - removeDevelopmentOnly(base.getExtendsFrom()).forEach(classpath::extendsFrom); + removeDevelopmentOnly(base.getExtendsFrom(), developmentOnlyConfigurationNames) + .forEach(classpath::extendsFrom); classpath.attributes((attributes) -> { ProviderFactory providers = project.getProviders(); AttributeContainer baseAttributes = base.getAttributes(); @@ -181,15 +185,12 @@ private Configuration createAotProcessingClasspath(Project project, String taskN } }); }); - return aotClasspath; } - private Stream removeDevelopmentOnly(Set configurations) { - return configurations.stream().filter(this::isNotDevelopmentOnly); - } - - private boolean isNotDevelopmentOnly(Configuration configuration) { - return !SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME.equals(configuration.getName()); + private Stream removeDevelopmentOnly(Set configurations, + Set developmentOnlyConfigurationNames) { + return configurations.stream() + .filter((configuration) -> !developmentOnlyConfigurationNames.contains(configuration.getName())); } private void configureDependsOn(Project project, SourceSet aotSourceSet, @@ -201,7 +202,8 @@ private void configureDependsOn(Project project, SourceSet aotSourceSet, private void registerProcessTestAotTask(Project project, SourceSet mainSourceSet, SourceSet aotTestSourceSet, SourceSet testSourceSet) { - Configuration aotClasspath = createAotProcessingClasspath(project, PROCESS_TEST_AOT_TASK_NAME, testSourceSet); + Configuration aotClasspath = createAotProcessingClasspath(project, PROCESS_TEST_AOT_TASK_NAME, testSourceSet, + Set.of(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME)); addJUnitPlatformLauncherDependency(project, aotClasspath); Configuration compileClasspath = project.getConfigurations() .getByName(aotTestSourceSet.getCompileClasspathConfigurationName()); @@ -211,7 +213,7 @@ private void registerProcessTestAotTask(Project project, SourceSet mainSourceSet .dir("generated/" + aotTestSourceSet.getName() + "Resources"); TaskProvider processTestAot = project.getTasks() .register(PROCESS_TEST_AOT_TASK_NAME, ProcessTestAot.class, (task) -> { - configureAotTask(project, aotTestSourceSet, task, testSourceSet, resourcesOutput); + configureAotTask(project, aotTestSourceSet, task, resourcesOutput); task.setClasspath(aotClasspath); task.setClasspathRoots(testSourceSet.getOutput()); }); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java index af3a1f1bf91a..f4e634c3f1cc 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java @@ -20,11 +20,9 @@ import java.util.List; import java.util.function.Consumer; -import org.gradle.api.GradleException; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; -import org.gradle.util.GradleVersion; import org.springframework.boot.gradle.dsl.SpringBootExtension; import org.springframework.boot.gradle.tasks.bundling.BootBuildImage; @@ -80,6 +78,12 @@ public class SpringBootPlugin implements Plugin { */ public static final String DEVELOPMENT_ONLY_CONFIGURATION_NAME = "developmentOnly"; + /** + * The name of the {@code testAndDevelopmentOnly} configuration. + * @since 3.2.0 + */ + public static final String TEST_AND_DEVELOPMENT_ONLY_CONFIGURATION_NAME = "testAndDevelopmentOnly"; + /** * The name of the {@code productionRuntimeClasspath} configuration. */ @@ -109,20 +113,11 @@ public class SpringBootPlugin implements Plugin { @Override public void apply(Project project) { - verifyGradleVersion(); createExtension(project); Configuration bootArchives = createBootArchivesConfiguration(project); registerPluginActions(project, bootArchives); } - private void verifyGradleVersion() { - GradleVersion currentVersion = GradleVersion.current(); - if (currentVersion.compareTo(GradleVersion.version("7.5")) < 0) { - throw new GradleException("Spring Boot plugin requires Gradle 7.x (7.5 or later). " - + "The current version is " + currentVersion); - } - } - private void createExtension(Project project) { project.getExtensions().create("springBoot", SpringBootExtension.class, project); } @@ -139,7 +134,8 @@ private void registerPluginActions(Project project, Configuration bootArchives) project.getArtifacts()); List actions = Arrays.asList(new JavaPluginAction(singlePublishedArtifact), new WarPluginAction(singlePublishedArtifact), new DependencyManagementPluginAction(), - new ApplicationPluginAction(), new KotlinPluginAction(), new NativeImagePluginAction()); + new ApplicationPluginAction(), new KotlinPluginAction(), new NativeImagePluginAction(), + new CycloneDxPluginAction()); for (PluginApplicationAction action : actions) { withPluginClassOfAction(action, (pluginClass) -> project.getPlugins().withType(pluginClass, (plugin) -> action.execute(project))); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java index d4cf73bc8a3a..250ff6f10ebc 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java @@ -72,6 +72,8 @@ private void classifyWarTask(Project project) { private TaskProvider configureBootWarTask(Project project) { Configuration developmentOnly = project.getConfigurations() .getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); + Configuration testAndDevelopmentOnly = project.getConfigurations() + .getByName(SpringBootPlugin.TEST_AND_DEVELOPMENT_ONLY_CONFIGURATION_NAME); Configuration productionRuntimeClasspath = project.getConfigurations() .getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME); SourceSet mainSourceSet = project.getExtensions() @@ -82,6 +84,7 @@ private TaskProvider configureBootWarTask(Project project) { Callable classpath = () -> mainSourceSet.getRuntimeClasspath() .minus(providedRuntimeConfiguration(project)) .minus((developmentOnly.minus(productionRuntimeClasspath))) + .minus((testAndDevelopmentOnly.minus(productionRuntimeClasspath))) .filter(new JarTypeFileSpec()); TaskProvider resolveMainClassName = project.getTasks() .named(SpringBootPlugin.RESOLVE_MAIN_CLASS_NAME_TASK_NAME, ResolveMainClassName.class); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoProperties.java index 5ace201ad715..cb2d41311013 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoProperties.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,13 +30,12 @@ import org.gradle.api.Project; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.Optional; -import org.springframework.util.function.SingletonSupplier; - /** * The properties that are written into the {@code build-info.properties} file. * @@ -48,7 +47,7 @@ public abstract class BuildInfoProperties implements Serializable { private final SetProperty excludes; - private final Supplier creationTime = SingletonSupplier.of(new CurrentIsoInstantSupplier()); + private final Supplier creationTime = () -> DateTimeFormatter.ISO_INSTANT.format(Instant.now()); @Inject public BuildInfoProperties(Project project, SetProperty excludes) { @@ -155,7 +154,12 @@ private T getIfNotExcluded(Property property, String name, Supplier de private Map coerceToStringValues(Map input) { Map output = new HashMap<>(); - input.forEach((key, value) -> output.put(key, (value != null) ? value.toString() : null)); + input.forEach((key, value) -> { + if (value instanceof Provider provider) { + value = provider.getOrNull(); + } + output.put(key, (value != null) ? value.toString() : null); + }); return output; } @@ -166,13 +170,4 @@ private Map applyExclusions(Map input) { return output; } - private static final class CurrentIsoInstantSupplier implements Supplier { - - @Override - public String get() { - return DateTimeFormatter.ISO_INSTANT.format(Instant.now()); - } - - } - } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchive.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchive.java index 1f3875a45aab..ec7d7099bcf3 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchive.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchive.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,10 +33,13 @@ import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Optional; +import org.springframework.boot.loader.tools.LoaderImplementation; + /** * A Spring Boot "fat" archive task. * * @author Andy Wilkinson + * @author Moritz Halbritter * @since 2.0.0 */ public interface BootArchive extends Task { @@ -133,4 +136,22 @@ public interface BootArchive extends Task { */ void resolvedArtifacts(Provider> resolvedArtifacts); + /** + * The loader implementation that should be used with the archive. + * @return the loader implementation + * @since 3.2.0 + */ + @Input + @Optional + Property getLoaderImplementation(); + + /** + * Returns whether the JAR tools should be included as a dependency in the layered + * archive. + * @return whether the JAR tools should be included + * @since 3.3.0 + */ + @Input + Property getIncludeTools(); + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java index a87c2e2e32b9..23cf11417560 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,6 +46,8 @@ import org.gradle.api.tasks.util.PatternSet; import org.gradle.util.GradleVersion; +import org.springframework.boot.loader.tools.LoaderImplementation; + /** * Support class for implementations of {@link BootArchive}. * @@ -65,9 +67,9 @@ class BootArchiveSupport { static { Set defaultLauncherClasses = new HashSet<>(); - defaultLauncherClasses.add("org.springframework.boot.loader.JarLauncher"); - defaultLauncherClasses.add("org.springframework.boot.loader.PropertiesLauncher"); - defaultLauncherClasses.add("org.springframework.boot.loader.WarLauncher"); + defaultLauncherClasses.add("org.springframework.boot.loader.launch.JarLauncher"); + defaultLauncherClasses.add("org.springframework.boot.loader.launch.PropertiesLauncher"); + defaultLauncherClasses.add("org.springframework.boot.loader.launch.WarLauncher"); DEFAULT_LAUNCHER_CLASSES = Collections.unmodifiableSet(defaultLauncherClasses); } @@ -120,12 +122,14 @@ private String determineSpringBootVersion() { return (version != null) ? version : "unknown"; } - CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies) { - return createCopyAction(jar, resolvedDependencies, null, null); + CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, + LoaderImplementation loaderImplementation, boolean supportsSignatureFile) { + return createCopyAction(jar, resolvedDependencies, loaderImplementation, supportsSignatureFile, null, null); } - CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, LayerResolver layerResolver, - String layerToolsLocation) { + CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, + LoaderImplementation loaderImplementation, boolean supportsSignatureFile, LayerResolver layerResolver, + String jarmodeToolsLocation) { File output = jar.getArchiveFile().get().getAsFile(); Manifest manifest = jar.getManifest(); boolean preserveFileTimestamps = jar.isPreserveFileTimestamps(); @@ -139,8 +143,9 @@ CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, Function compressionResolver = this.compressionResolver; String encoding = jar.getMetadataCharset(); CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, dirMode, fileMode, - includeDefaultLoader, layerToolsLocation, requiresUnpack, exclusions, launchScript, librarySpec, - compressionResolver, encoding, resolvedDependencies, layerResolver); + includeDefaultLoader, jarmodeToolsLocation, requiresUnpack, exclusions, launchScript, librarySpec, + compressionResolver, encoding, resolvedDependencies, supportsSignatureFile, layerResolver, + loaderImplementation); return jar.isReproducibleFileOrder() ? new ReproducibleOrderingCopyAction(action) : action; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java index 2b307fb322be..ce11a93a4aaf 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,6 +70,8 @@ public abstract class BootBuildImage extends DefaultTask { private final String projectName; + private final CacheSpec buildWorkspace; + private final CacheSpec buildCache; private final CacheSpec launchCache; @@ -89,13 +91,16 @@ public BootBuildImage() { } return ImageReference.of(imageName, projectVersion.get()).toString(); })); + getTrustBuilder().convention((Boolean) null); getCleanCache().convention(false); getVerboseLogging().convention(false); getPublish().convention(false); + this.buildWorkspace = getProject().getObjects().newInstance(CacheSpec.class); this.buildCache = getProject().getObjects().newInstance(CacheSpec.class); this.launchCache = getProject().getObjects().newInstance(CacheSpec.class); this.docker = getProject().getObjects().newInstance(DockerSpec.class); this.pullPolicy = getProject().getObjects().property(PullPolicy.class); + getSecurityOptions().convention((Iterable) null); } /** @@ -127,6 +132,16 @@ public BootBuildImage() { @Option(option = "builder", description = "The name of the builder image to use") public abstract Property getBuilder(); + /** + * Whether to treat the builder as trusted. + * @return whether to trust the builder + * @since 3.4.0 + */ + @Input + @Optional + @Option(option = "trustBuilder", description = "Consider the builder trusted") + public abstract Property getTrustBuilder(); + /** * Returns the run image that will be included in the built image. When {@code null}, * the run image bundled with the builder will be used. @@ -223,6 +238,27 @@ public void setPullPolicy(String pullPolicy) { @Option(option = "network", description = "Connect detect and build containers to network") public abstract Property getNetwork(); + /** + * Returns the build temporary workspace that will be used when building the image. + * @return the cache + * @since 3.2.0 + */ + @Nested + @Optional + public CacheSpec getBuildWorkspace() { + return this.buildWorkspace; + } + + /** + * Customizes the {@link CacheSpec} for the build temporary workspace using the given + * {@code action}. + * @param action the action + * @since 3.2.0 + */ + public void buildWorkspace(Action action) { + action.execute(this.buildWorkspace); + } + /** * Returns the build cache that will be used when building the image. * @return the cache @@ -281,6 +317,15 @@ public void launchCache(Action action) { @Option(option = "applicationDirectory", description = "The directory containing application content in the image") public abstract Property getApplicationDirectory(); + /** + * Returns the security options that will be applied to the builder container. + * @return the security options + */ + @Input + @Optional + @Option(option = "securityOptions", description = "Security options that will be applied to the builder container") + public abstract ListProperty getSecurityOptions(); + /** * Returns the Docker configuration the builder will use. * @return docker configuration. @@ -314,13 +359,16 @@ BuildRequest createRequest() { private BuildRequest customize(BuildRequest request) { request = customizeBuilder(request); + if (getTrustBuilder().isPresent()) { + request = request.withTrustBuilder(getTrustBuilder().get()); + } request = customizeRunImage(request); request = customizeEnvironment(request); request = customizeCreator(request); request = request.withCleanCache(getCleanCache().get()); request = request.withVerboseLogging(getVerboseLogging().get()); request = customizePullPolicy(request); - request = customizePublish(request); + request = request.withPublish(getPublish().get()); request = customizeBuildpacks(request); request = customizeBindings(request); request = customizeTags(request); @@ -328,6 +376,7 @@ private BuildRequest customize(BuildRequest request) { request = request.withNetwork(getNetwork().getOrNull()); request = customizeCreatedDate(request); request = customizeApplicationDirectory(request); + request = customizeSecurityOptions(request); return request; } @@ -371,11 +420,6 @@ private BuildRequest customizePullPolicy(BuildRequest request) { return request; } - private BuildRequest customizePublish(BuildRequest request) { - request = request.withPublish(getPublish().get()); - return request; - } - private BuildRequest customizeBuildpacks(BuildRequest request) { List buildpacks = getBuildpacks().getOrNull(); if (!CollectionUtils.isEmpty(buildpacks)) { @@ -401,6 +445,9 @@ private BuildRequest customizeTags(BuildRequest request) { } private BuildRequest customizeCaches(BuildRequest request) { + if (this.buildWorkspace.asCache() != null) { + request = request.withBuildWorkspace((this.buildWorkspace.asCache())); + } if (this.buildCache.asCache() != null) { request = request.withBuildCache(this.buildCache.asCache()); } @@ -426,4 +473,14 @@ private BuildRequest customizeApplicationDirectory(BuildRequest request) { return request; } + private BuildRequest customizeSecurityOptions(BuildRequest request) { + if (getSecurityOptions().isPresent()) { + List securityOptions = getSecurityOptions().getOrNull(); + if (securityOptions != null) { + return request.withSecurityOptions(securityOptions); + } + } + return request; + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java index 2b2d1cfa2e55..27454860a172 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,8 @@ import org.gradle.api.tasks.bundling.Jar; import org.gradle.work.DisableCachingByDefault; +import org.springframework.boot.loader.tools.LoaderImplementation; + /** * A custom {@link Jar} task that produces a Spring Boot executable jar. * @@ -49,7 +51,7 @@ @DisableCachingByDefault(because = "Not worth caching") public abstract class BootJar extends Jar implements BootArchive { - private static final String LAUNCHER = "org.springframework.boot.loader.JarLauncher"; + private static final String LAUNCHER = "org.springframework.boot.loader.launch.JarLauncher"; private static final String CLASSES_DIRECTORY = "BOOT-INF/classes/"; @@ -86,6 +88,7 @@ public BootJar() { this.projectName = project.provider(project::getName); this.projectVersion = project.provider(project::getVersion); this.resolvedDependencies = new ResolvedDependencies(project); + getIncludeTools().convention(true); } private void configureBootInfSpec(CopySpec bootInfSpec) { @@ -141,12 +144,20 @@ private boolean isLayeredDisabled() { @Override protected CopyAction createCopyAction() { + LoaderImplementation loaderImplementation = getLoaderImplementation().getOrElse(LoaderImplementation.DEFAULT); + LayerResolver layerResolver = null; if (!isLayeredDisabled()) { - LayerResolver layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary); - String layerToolsLocation = this.layered.getIncludeLayerTools().get() ? LIB_DIRECTORY : null; - return this.support.createCopyAction(this, this.resolvedDependencies, layerResolver, layerToolsLocation); + layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary); } - return this.support.createCopyAction(this, this.resolvedDependencies); + String jarmodeToolsLocation = isIncludeJarmodeTools() ? LIB_DIRECTORY : null; + return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation, true, layerResolver, + jarmodeToolsLocation); + } + + @SuppressWarnings("removal") + private boolean isIncludeJarmodeTools() { + return Boolean.TRUE.equals(this.getIncludeTools().get()) + && Boolean.TRUE.equals(this.layered.getIncludeLayerTools().get()); } @Override diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java index 47ce5f0c5410..2dfaee6ffe04 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,8 @@ import org.gradle.api.tasks.bundling.War; import org.gradle.work.DisableCachingByDefault; +import org.springframework.boot.loader.tools.LoaderImplementation; + /** * A custom {@link War} task that produces a Spring Boot executable war. * @@ -48,7 +50,7 @@ @DisableCachingByDefault(because = "Not worth caching") public abstract class BootWar extends War implements BootArchive { - private static final String LAUNCHER = "org.springframework.boot.loader.WarLauncher"; + private static final String LAUNCHER = "org.springframework.boot.loader.launch.WarLauncher"; private static final String CLASSES_DIRECTORY = "WEB-INF/classes/"; @@ -85,6 +87,7 @@ public BootWar() { this.projectName = project.provider(project::getName); this.projectVersion = project.provider(project::getVersion); this.resolvedDependencies = new ResolvedDependencies(project); + getIncludeTools().convention(true); } private Object getProvidedLibFiles() { @@ -115,12 +118,20 @@ private boolean isLayeredDisabled() { @Override protected CopyAction createCopyAction() { + LoaderImplementation loaderImplementation = getLoaderImplementation().getOrElse(LoaderImplementation.DEFAULT); + LayerResolver layerResolver = null; if (!isLayeredDisabled()) { - LayerResolver layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary); - String layerToolsLocation = this.layered.getIncludeLayerTools().get() ? LIB_DIRECTORY : null; - return this.support.createCopyAction(this, this.resolvedDependencies, layerResolver, layerToolsLocation); + layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary); } - return this.support.createCopyAction(this, this.resolvedDependencies); + String jarmodeToolsLocation = isIncludeJarmodeTools() ? LIB_DIRECTORY : null; + return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation, false, + layerResolver, jarmodeToolsLocation); + } + + @SuppressWarnings("removal") + private boolean isIncludeJarmodeTools() { + return Boolean.TRUE.equals(this.getIncludeTools().get()) + && Boolean.TRUE.equals(this.layered.getIncludeLayerTools().get()); } @Override diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java index 53ccdcece2cb..60bcebc04921 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java @@ -59,6 +59,7 @@ import org.springframework.boot.loader.tools.Layer; import org.springframework.boot.loader.tools.LayersIndex; import org.springframework.boot.loader.tools.LibraryCoordinates; +import org.springframework.boot.loader.tools.LoaderImplementation; import org.springframework.boot.loader.tools.NativeImageArgFile; import org.springframework.boot.loader.tools.ReachabilityMetadataProperties; import org.springframework.util.Assert; @@ -95,7 +96,7 @@ class BootZipCopyAction implements CopyAction { private final boolean includeDefaultLoader; - private final String layerToolsLocation; + private final String jarmodeToolsLocation; private final Spec requiresUnpack; @@ -111,20 +112,25 @@ class BootZipCopyAction implements CopyAction { private final ResolvedDependencies resolvedDependencies; + private final boolean supportsSignatureFile; + private final LayerResolver layerResolver; + private final LoaderImplementation loaderImplementation; + BootZipCopyAction(File output, Manifest manifest, boolean preserveFileTimestamps, Integer dirMode, Integer fileMode, - boolean includeDefaultLoader, String layerToolsLocation, Spec requiresUnpack, + boolean includeDefaultLoader, String jarmodeToolsLocation, Spec requiresUnpack, Spec exclusions, LaunchScriptConfiguration launchScript, Spec librarySpec, Function compressionResolver, String encoding, - ResolvedDependencies resolvedDependencies, LayerResolver layerResolver) { + ResolvedDependencies resolvedDependencies, boolean supportsSignatureFile, LayerResolver layerResolver, + LoaderImplementation loaderImplementation) { this.output = output; this.manifest = manifest; this.preserveFileTimestamps = preserveFileTimestamps; this.dirMode = dirMode; this.fileMode = fileMode; this.includeDefaultLoader = includeDefaultLoader; - this.layerToolsLocation = layerToolsLocation; + this.jarmodeToolsLocation = jarmodeToolsLocation; this.requiresUnpack = requiresUnpack; this.exclusions = exclusions; this.launchScript = launchScript; @@ -132,7 +138,9 @@ class BootZipCopyAction implements CopyAction { this.compressionResolver = compressionResolver; this.encoding = encoding; this.resolvedDependencies = resolvedDependencies; + this.supportsSignatureFile = supportsSignatureFile; this.layerResolver = layerResolver; + this.loaderImplementation = loaderImplementation; } @Override @@ -299,6 +307,7 @@ private String getParentDirectory(String name) { void finish() throws IOException { writeLoaderEntriesIfNecessary(null); writeJarToolsIfNecessary(); + writeSignatureFileIfNecessary(); writeClassPathIndexIfNecessary(); writeNativeImageArgFileIfNecessary(); // We must write the layer index last @@ -313,7 +322,8 @@ private void writeLoaderEntriesIfNecessary(FileCopyDetails details) throws IOExc // Always write loader entries after META-INF directory (see gh-16698) return; } - LoaderZipEntries loaderEntries = new LoaderZipEntries(getTime(), getDirMode(), getFileMode()); + LoaderZipEntries loaderEntries = new LoaderZipEntries(getTime(), getDirMode(), getFileMode(), + BootZipCopyAction.this.loaderImplementation); this.writtenLoaderEntries = loaderEntries.writeTo(this.out); if (BootZipCopyAction.this.layerResolver != null) { for (String name : this.writtenLoaderEntries.getFiles()) { @@ -332,8 +342,8 @@ private boolean isInMetaInf(FileCopyDetails details) { } private void writeJarToolsIfNecessary() throws IOException { - if (BootZipCopyAction.this.layerToolsLocation != null) { - writeJarModeLibrary(BootZipCopyAction.this.layerToolsLocation, JarModeLibrary.LAYER_TOOLS); + if (BootZipCopyAction.this.jarmodeToolsLocation != null) { + writeJarModeLibrary(BootZipCopyAction.this.jarmodeToolsLocation, JarModeLibrary.TOOLS); } } @@ -347,6 +357,22 @@ private void writeJarModeLibrary(String location, JarModeLibrary library) throws } } + private void writeSignatureFileIfNecessary() throws IOException { + if (BootZipCopyAction.this.supportsSignatureFile && hasSignedLibrary()) { + writeEntry("META-INF/BOOT.SF", (out) -> { + }, false); + } + } + + private boolean hasSignedLibrary() throws IOException { + for (FileCopyDetails writtenLibrary : this.writtenLibraries.values()) { + if (FileUtils.isSignedJarFile(writtenLibrary.getFile())) { + return true; + } + } + return false; + } + private void writeClassPathIndexIfNecessary() throws IOException { Attributes manifestAttributes = BootZipCopyAction.this.manifest.getAttributes(); String classPathIndex = (String) manifestAttributes.get("Spring-Boot-Classpath-Index"); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/CacheSpec.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/CacheSpec.java index d33d6a964966..235a3665f148 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/CacheSpec.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/CacheSpec.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 the original author or authors. + * Copyright 2021-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,6 +60,19 @@ public void volume(Action action) { this.cache = Cache.volume(spec.getName().get()); } + /** + * Configures a bind cache using the given {@code action}. + * @param action the action + */ + public void bind(Action action) { + if (this.cache != null) { + throw new GradleException("Each image building cache can be configured only once"); + } + BindCacheSpec spec = this.objectFactory.newInstance(BindCacheSpec.class); + action.execute(spec); + this.cache = Cache.bind(spec.getSource().get()); + } + /** * Configuration for an image building cache stored in a Docker volume. */ @@ -74,4 +87,18 @@ public abstract static class VolumeCacheSpec { } + /** + * Configuration for an image building cache stored in a bind mount. + */ + public abstract static class BindCacheSpec { + + /** + * Returns the source of the cache. + * @return the cache source + */ + @Input + public abstract Property getSource(); + + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java index ce3907a4db16..ffed3ddba17c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,6 +54,10 @@ public DockerSpec(ObjectFactory objects) { this.publishRegistry = publishRegistry; } + @Input + @Optional + public abstract Property getContext(); + @Input @Optional public abstract Property getHost(); @@ -124,7 +128,15 @@ DockerConfiguration asDockerConfiguration() { } private DockerConfiguration customizeHost(DockerConfiguration dockerConfiguration) { + String context = getContext().getOrNull(); String host = getHost().getOrNull(); + if (context != null && host != null) { + throw new GradleException( + "Invalid Docker configuration, either context or host can be provided but not both"); + } + if (context != null) { + return dockerConfiguration.withContext(context); + } if (host != null) { return dockerConfiguration.withHost(host, getTlsVerify().get(), getCertPath().getOrNull()); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LaunchScriptConfiguration.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LaunchScriptConfiguration.java index 63dd55290e22..c3a4d1c74b63 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LaunchScriptConfiguration.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LaunchScriptConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayeredSpec.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayeredSpec.java index b2f6842f21f0..98898783a26e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayeredSpec.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayeredSpec.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -72,8 +72,10 @@ public LayeredSpec(ObjectFactory objects) { * archive. * @return whether the layer tools should be included * @since 3.0.0 + * @deprecated since 3.3.0 for removal in 3.5.0 in favor of {@code includeTools}. */ @Input + @Deprecated(since = "3.3.0", forRemoval = true) public abstract Property getIncludeLayerTools(); /** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java index dd4d50894ff4..64b3ea1685ee 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java @@ -28,6 +28,7 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.gradle.api.file.FileTreeElement; +import org.springframework.boot.loader.tools.LoaderImplementation; import org.springframework.util.StreamUtils; /** @@ -39,30 +40,34 @@ */ class LoaderZipEntries { + private final LoaderImplementation loaderImplementation; + private final Long entryTime; private final int dirMode; private final int fileMode; - LoaderZipEntries(Long entryTime, int dirMode, int fileMode) { + LoaderZipEntries(Long entryTime, int dirMode, int fileMode, LoaderImplementation loaderImplementation) { this.entryTime = entryTime; this.dirMode = dirMode; this.fileMode = fileMode; + this.loaderImplementation = (loaderImplementation != null) ? loaderImplementation + : LoaderImplementation.DEFAULT; } WrittenEntries writeTo(ZipArchiveOutputStream out) throws IOException { WrittenEntries written = new WrittenEntries(); try (ZipInputStream loaderJar = new ZipInputStream( - getClass().getResourceAsStream("/META-INF/loader/spring-boot-loader.jar"))) { + getClass().getResourceAsStream("/" + this.loaderImplementation.getJarResourceName()))) { java.util.zip.ZipEntry entry = loaderJar.getNextEntry(); while (entry != null) { if (entry.isDirectory() && !entry.getName().equals("META-INF/")) { writeDirectory(new ZipArchiveEntry(entry), out); written.addDirectory(entry); } - else if (entry.getName().endsWith(".class")) { - writeClass(new ZipArchiveEntry(entry), loaderJar, out); + else if (entry.getName().endsWith(".class") || entry.getName().startsWith("META-INF/services/")) { + writeFile(new ZipArchiveEntry(entry), loaderJar, out); written.addFile(entry); } entry = loaderJar.getNextEntry(); @@ -77,7 +82,7 @@ private void writeDirectory(ZipArchiveEntry entry, ZipArchiveOutputStream out) t out.closeArchiveEntry(); } - private void writeClass(ZipArchiveEntry entry, ZipInputStream in, ZipArchiveOutputStream out) throws IOException { + private void writeFile(ZipArchiveEntry entry, ZipInputStream in, ZipArchiveOutputStream out) throws IOException { prepareEntry(entry, this.fileMode); out.putArchiveEntry(entry); copy(in, out); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/Examples.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/Examples.java new file mode 100644 index 000000000000..36706d3400c0 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/Examples.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.gradle.docs; + +/** + * @author Phillip Webb + */ +final class Examples { + + static final String DIR = "src/docs/antora/modules/gradle-plugin/examples/"; + + private Examples() { + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/GettingStartedDocumentationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/GettingStartedDocumentationTests.java index 4295db38275f..f7e01c169134 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/GettingStartedDocumentationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/GettingStartedDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,7 @@ class GettingStartedDocumentationTests { @TestTemplate void typicalPluginsAppliesExceptedPlugins() { - this.gradleBuild.script("src/docs/gradle/getting-started/typical-plugins").build("verify"); + this.gradleBuild.script(Examples.DIR + "getting-started/typical-plugins").build("verify"); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/IntegratingWithActuatorDocumentationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/IntegratingWithActuatorDocumentationTests.java index 8483a52d791e..29a0cf428ccf 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/IntegratingWithActuatorDocumentationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/IntegratingWithActuatorDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,14 +42,14 @@ class IntegratingWithActuatorDocumentationTests { @TestTemplate void basicBuildInfo() { - this.gradleBuild.script("src/docs/gradle/integrating-with-actuator/build-info-basic").build("bootBuildInfo"); + this.gradleBuild.script(Examples.DIR + "integrating-with-actuator/build-info-basic").build("bootBuildInfo"); assertThat(new File(this.gradleBuild.getProjectDir(), "build/resources/main/META-INF/build-info.properties")) .isFile(); } @TestTemplate void buildInfoCustomValues() { - this.gradleBuild.script("src/docs/gradle/integrating-with-actuator/build-info-custom-values") + this.gradleBuild.script(Examples.DIR + "integrating-with-actuator/build-info-custom-values") .build("bootBuildInfo"); File file = new File(this.gradleBuild.getProjectDir(), "build/resources/main/META-INF/build-info.properties"); assertThat(file).isFile(); @@ -63,7 +63,7 @@ void buildInfoCustomValues() { @TestTemplate void buildInfoAdditional() { - this.gradleBuild.script("src/docs/gradle/integrating-with-actuator/build-info-additional") + this.gradleBuild.script(Examples.DIR + "integrating-with-actuator/build-info-additional") .build("bootBuildInfo"); File file = new File(this.gradleBuild.getProjectDir(), "build/resources/main/META-INF/build-info.properties"); assertThat(file).isFile(); @@ -74,7 +74,7 @@ void buildInfoAdditional() { @TestTemplate void buildInfoExcludeTime() { - this.gradleBuild.script("src/docs/gradle/integrating-with-actuator/build-info-exclude-time") + this.gradleBuild.script(Examples.DIR + "integrating-with-actuator/build-info-exclude-time") .build("bootBuildInfo"); File file = new File(this.gradleBuild.getProjectDir(), "build/resources/main/META-INF/build-info.properties"); assertThat(file).isFile(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/ManagingDependenciesDocumentationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/ManagingDependenciesDocumentationTests.java index e33ee06b0309..7e16f7834574 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/ManagingDependenciesDocumentationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/ManagingDependenciesDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,19 +39,19 @@ class ManagingDependenciesDocumentationTests { @TestTemplate void dependenciesExampleEvaluatesSuccessfully() { - this.gradleBuild.script("src/docs/gradle/managing-dependencies/dependencies").build(); + this.gradleBuild.script(Examples.DIR + "managing-dependencies/dependencies").build(); } @TestTemplate void customManagedVersions() { - assertThat(this.gradleBuild.script("src/docs/gradle/managing-dependencies/custom-version") + assertThat(this.gradleBuild.script(Examples.DIR + "managing-dependencies/custom-version") .build("slf4jVersion") .getOutput()).contains("1.7.20"); } @TestTemplate void dependencyManagementInIsolation() { - assertThat(this.gradleBuild.script("src/docs/gradle/managing-dependencies/configure-bom") + assertThat(this.gradleBuild.script(Examples.DIR + "managing-dependencies/configure-bom") .build("dependencyManagement") .getOutput()).contains("org.springframework.boot:spring-boot-starter "); } @@ -60,7 +60,7 @@ void dependencyManagementInIsolation() { void dependencyManagementInIsolationWithPluginsBlock() { assumingThat(this.gradleBuild.getDsl() == Dsl.KOTLIN, () -> assertThat( - this.gradleBuild.script("src/docs/gradle/managing-dependencies/configure-bom-with-plugins") + this.gradleBuild.script(Examples.DIR + "managing-dependencies/configure-bom-with-plugins") .build("dependencyManagement") .getOutput()) .contains("org.springframework.boot:spring-boot-starter TEST-SNAPSHOT")); @@ -68,14 +68,14 @@ void dependencyManagementInIsolationWithPluginsBlock() { @TestTemplate void configurePlatform() { - assertThat(this.gradleBuild.script("src/docs/gradle/managing-dependencies/configure-platform") + assertThat(this.gradleBuild.script(Examples.DIR + "managing-dependencies/configure-platform") .build("dependencies", "--configuration", "compileClasspath") .getOutput()).contains("org.springframework.boot:spring-boot-starter "); } @TestTemplate void customManagedVersionsWithPlatform() { - assertThat(this.gradleBuild.script("src/docs/gradle/managing-dependencies/custom-version-with-platform") + assertThat(this.gradleBuild.script(Examples.DIR + "managing-dependencies/custom-version-with-platform") .build("dependencies", "--configuration", "compileClasspath") .getOutput()).contains("1.7.20"); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PackagingDocumentationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PackagingDocumentationTests.java index f38abf738313..fcb13ba1296f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PackagingDocumentationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PackagingDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,12 +52,12 @@ class PackagingDocumentationTests { @TestTemplate void warContainerDependencyEvaluatesSuccessfully() { - this.gradleBuild.script("src/docs/gradle/packaging/war-container-dependency").build(); + this.gradleBuild.script(Examples.DIR + "packaging/war-container-dependency").build(); } @TestTemplate void bootJarMainClass() throws IOException { - this.gradleBuild.script("src/docs/gradle/packaging/boot-jar-main-class").build("bootJar"); + this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-main-class").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); @@ -69,7 +69,7 @@ void bootJarMainClass() throws IOException { @TestTemplate void bootJarManifestMainClass() throws IOException { - this.gradleBuild.script("src/docs/gradle/packaging/boot-jar-manifest-main-class").build("bootJar"); + this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-manifest-main-class").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); @@ -81,7 +81,7 @@ void bootJarManifestMainClass() throws IOException { @TestTemplate void applicationPluginMainClass() throws IOException { - this.gradleBuild.script("src/docs/gradle/packaging/application-plugin-main-class").build("bootJar"); + this.gradleBuild.script(Examples.DIR + "packaging/application-plugin-main-class").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); @@ -93,7 +93,7 @@ void applicationPluginMainClass() throws IOException { @TestTemplate void springBootDslMainClass() throws IOException { - this.gradleBuild.script("src/docs/gradle/packaging/spring-boot-dsl-main-class").build("bootJar"); + this.gradleBuild.script(Examples.DIR + "packaging/spring-boot-dsl-main-class").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); @@ -106,7 +106,7 @@ void springBootDslMainClass() throws IOException { @TestTemplate void bootWarIncludeDevtools() throws IOException { jarFile(new File(this.gradleBuild.getProjectDir(), "spring-boot-devtools-1.2.3.RELEASE.jar")); - this.gradleBuild.script("src/docs/gradle/packaging/boot-war-include-devtools").build("bootWar"); + this.gradleBuild.script(Examples.DIR + "packaging/boot-war-include-devtools").build("bootWar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".war"); assertThat(file).isFile(); @@ -117,7 +117,7 @@ void bootWarIncludeDevtools() throws IOException { @TestTemplate void bootJarRequiresUnpack() throws IOException { - this.gradleBuild.script("src/docs/gradle/packaging/boot-jar-requires-unpack").build("bootJar"); + this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-requires-unpack").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); @@ -130,7 +130,7 @@ void bootJarRequiresUnpack() throws IOException { @TestTemplate void bootJarIncludeLaunchScript() throws IOException { - this.gradleBuild.script("src/docs/gradle/packaging/boot-jar-include-launch-script").build("bootJar"); + this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-include-launch-script").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); @@ -139,7 +139,7 @@ void bootJarIncludeLaunchScript() throws IOException { @TestTemplate void bootJarLaunchScriptProperties() throws IOException { - this.gradleBuild.script("src/docs/gradle/packaging/boot-jar-launch-script-properties").build("bootJar"); + this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-launch-script-properties").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); @@ -151,7 +151,7 @@ void bootJarCustomLaunchScript() throws IOException { File customScriptFile = new File(this.gradleBuild.getProjectDir(), "src/custom.script"); customScriptFile.getParentFile().mkdirs(); FileCopyUtils.copy("custom", new FileWriter(customScriptFile)); - this.gradleBuild.script("src/docs/gradle/packaging/boot-jar-custom-launch-script").build("bootJar"); + this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-custom-launch-script").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); @@ -160,19 +160,19 @@ void bootJarCustomLaunchScript() throws IOException { @TestTemplate void bootWarPropertiesLauncher() throws IOException { - this.gradleBuild.script("src/docs/gradle/packaging/boot-war-properties-launcher").build("bootWar"); + this.gradleBuild.script(Examples.DIR + "packaging/boot-war-properties-launcher").build("bootWar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".war"); assertThat(file).isFile(); try (JarFile jar = new JarFile(file)) { assertThat(jar.getManifest().getMainAttributes().getValue("Main-Class")) - .isEqualTo("org.springframework.boot.loader.PropertiesLauncher"); + .isEqualTo("org.springframework.boot.loader.launch.PropertiesLauncher"); } } @TestTemplate void onlyBootJar() throws IOException { - this.gradleBuild.script("src/docs/gradle/packaging/only-boot-jar").build("assemble"); + this.gradleBuild.script(Examples.DIR + "packaging/only-boot-jar").build("assemble"); File plainJar = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + "-plain.jar"); assertThat(plainJar).doesNotExist(); @@ -186,7 +186,7 @@ void onlyBootJar() throws IOException { @TestTemplate void classifiedBootJar() throws IOException { - this.gradleBuild.script("src/docs/gradle/packaging/boot-jar-and-jar-classifiers").build("assemble"); + this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-and-jar-classifiers").build("assemble"); File plainJar = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(plainJar).isFile(); @@ -203,7 +203,7 @@ void classifiedBootJar() throws IOException { @TestTemplate void bootJarLayeredDisabled() throws IOException { - this.gradleBuild.script("src/docs/gradle/packaging/boot-jar-layered-disabled").build("bootJar"); + this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-layered-disabled").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); @@ -215,7 +215,7 @@ void bootJarLayeredDisabled() throws IOException { @TestTemplate void bootJarLayeredCustom() throws IOException { - this.gradleBuild.script("src/docs/gradle/packaging/boot-jar-layered-custom").build("bootJar"); + this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-layered-custom").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); @@ -231,7 +231,7 @@ void bootJarLayeredCustom() throws IOException { @TestTemplate void bootJarLayeredExcludeTools() throws IOException { - this.gradleBuild.script("src/docs/gradle/packaging/boot-jar-layered-exclude-tools").build("bootJar"); + this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-layered-exclude-tools").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); @@ -247,21 +247,21 @@ void bootJarLayeredExcludeTools() throws IOException { @TestTemplate void bootBuildImageWithBuilder() { - BuildResult result = this.gradleBuild.script("src/docs/gradle/packaging/boot-build-image-builder") + BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-builder") .build("bootBuildImageBuilder"); assertThat(result.getOutput()).contains("builder=mine/java-cnb-builder").contains("runImage=mine/java-cnb-run"); } @TestTemplate void bootBuildImageWithCustomBuildpackJvmVersion() { - BuildResult result = this.gradleBuild.script("src/docs/gradle/packaging/boot-build-image-env") + BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-env") .build("bootBuildImageEnvironment"); assertThat(result.getOutput()).contains("BP_JVM_VERSION=17"); } @TestTemplate void bootBuildImageWithCustomProxySettings() { - BuildResult result = this.gradleBuild.script("src/docs/gradle/packaging/boot-build-image-env-proxy") + BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-env-proxy") .build("bootBuildImageEnvironment"); assertThat(result.getOutput()).contains("HTTP_PROXY=http://proxy.example.com") .contains("HTTPS_PROXY=https://proxy.example.com"); @@ -269,7 +269,7 @@ void bootBuildImageWithCustomProxySettings() { @TestTemplate void bootBuildImageWithCustomRuntimeConfiguration() { - BuildResult result = this.gradleBuild.script("src/docs/gradle/packaging/boot-build-image-env-runtime") + BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-env-runtime") .build("bootBuildImageEnvironment"); assertThat(result.getOutput()).contains("BPE_DELIM_JAVA_TOOL_OPTIONS= ") .contains("BPE_APPEND_JAVA_TOOL_OPTIONS=-XX:+HeapDumpOnOutOfMemoryError"); @@ -277,14 +277,14 @@ void bootBuildImageWithCustomRuntimeConfiguration() { @TestTemplate void bootBuildImageWithCustomImageName() { - BuildResult result = this.gradleBuild.script("src/docs/gradle/packaging/boot-build-image-name") + BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-name") .build("bootBuildImageName"); assertThat(result.getOutput()).contains("example.com/library/" + this.gradleBuild.getProjectDir().getName()); } @TestTemplate void bootBuildImageWithDockerHostMinikube() { - BuildResult result = this.gradleBuild.script("src/docs/gradle/packaging/boot-build-image-docker-host") + BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-docker-host") .build("bootBuildImageDocker"); assertThat(result.getOutput()).contains("host=tcp://192.168.99.100:2376") .contains("tlsVerify=true") @@ -293,7 +293,7 @@ void bootBuildImageWithDockerHostMinikube() { @TestTemplate void bootBuildImageWithDockerHostPodman() { - BuildResult result = this.gradleBuild.script("src/docs/gradle/packaging/boot-build-image-docker-host-podman") + BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-docker-host-podman") .build("bootBuildImageDocker"); assertThat(result.getOutput()).contains("host=unix:///run/user/1000/podman/podman.sock") .contains("bindHostToBuilder=true"); @@ -301,7 +301,7 @@ void bootBuildImageWithDockerHostPodman() { @TestTemplate void bootBuildImageWithDockerHostColima() { - BuildResult result = this.gradleBuild.script("src/docs/gradle/packaging/boot-build-image-docker-host-colima") + BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-docker-host-colima") .build("bootBuildImageDocker"); assertThat(result.getOutput()) .contains("host=unix://" + System.getProperty("user.home") + "/.colima/docker.sock"); @@ -309,7 +309,7 @@ void bootBuildImageWithDockerHostColima() { @TestTemplate void bootBuildImageWithDockerUserAuth() { - BuildResult result = this.gradleBuild.script("src/docs/gradle/packaging/boot-build-image-docker-auth-user") + BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-docker-auth-user") .build("bootBuildImageDocker"); assertThat(result.getOutput()).contains("username=user") .contains("password=secret") @@ -319,21 +319,21 @@ void bootBuildImageWithDockerUserAuth() { @TestTemplate void bootBuildImageWithDockerTokenAuth() { - BuildResult result = this.gradleBuild.script("src/docs/gradle/packaging/boot-build-image-docker-auth-token") + BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-docker-auth-token") .build("bootBuildImageDocker"); assertThat(result.getOutput()).contains("token=9cbaf023786cd7..."); } @TestTemplate void bootBuildImagePublish() { - BuildResult result = this.gradleBuild.script("src/docs/gradle/packaging/boot-build-image-publish") + BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-publish") .build("bootBuildImagePublish"); assertThat(result.getOutput()).contains("true"); } @TestTemplate void bootBuildImageWithBuildpacks() { - BuildResult result = this.gradleBuild.script("src/docs/gradle/packaging/boot-build-image-buildpacks") + BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-buildpacks") .build("bootBuildImageBuildpacks"); assertThat(result.getOutput()).contains("file:///path/to/example-buildpack.tgz") .contains("urn:cnb:builder:paketo-buildpacks/java"); @@ -341,12 +341,21 @@ void bootBuildImageWithBuildpacks() { @TestTemplate void bootBuildImageWithCaches() { - BuildResult result = this.gradleBuild.script("src/docs/gradle/packaging/boot-build-image-caches") + BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-caches") .build("bootBuildImageCaches"); assertThat(result.getOutput()).containsPattern("buildCache=cache-gradle-[\\d]+.build") .containsPattern("launchCache=cache-gradle-[\\d]+.launch"); } + @TestTemplate + void bootBuildImageWithBindCaches() { + BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-bind-caches") + .build("bootBuildImageCaches"); + assertThat(result.getOutput()).containsPattern("buildWorkspace=/tmp/cache-gradle-[\\d]+.work") + .containsPattern("buildCache=/tmp/cache-gradle-[\\d]+.build") + .containsPattern("launchCache=/tmp/cache-gradle-[\\d]+.launch"); + } + protected void jarFile(File file) throws IOException { try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(file))) { jar.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PublishingDocumentationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PublishingDocumentationTests.java index 10ff323e83ea..71ccbad56754 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PublishingDocumentationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PublishingDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ class PublishingDocumentationTests { @TestTemplate void mavenPublish() { - assertThat(this.gradleBuild.script("src/docs/gradle/publishing/maven-publish") + assertThat(this.gradleBuild.script(Examples.DIR + "publishing/maven-publish") .build("publishingConfiguration") .getOutput()).contains("MavenPublication").contains("https://repo.example.com"); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/RunningDocumentationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/RunningDocumentationTests.java index 71833a8e38e9..b1471e09d645 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/RunningDocumentationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/RunningDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,14 +43,14 @@ class RunningDocumentationTests { @TestTemplate void bootRunMain() throws IOException { writeMainClass(); - assertThat(this.gradleBuild.script("src/docs/gradle/running/boot-run-main").build("bootRun").getOutput()) + assertThat(this.gradleBuild.script(Examples.DIR + "running/boot-run-main").build("bootRun").getOutput()) .contains("com.example.ExampleApplication"); } @TestTemplate void applicationPluginMainClassName() throws IOException { writeMainClass(); - assertThat(this.gradleBuild.script("src/docs/gradle/running/application-plugin-main-class-name") + assertThat(this.gradleBuild.script(Examples.DIR + "running/application-plugin-main-class-name") .build("bootRun") .getOutput()).contains("com.example.ExampleApplication"); } @@ -58,35 +58,35 @@ void applicationPluginMainClassName() throws IOException { @TestTemplate void springBootDslMainClassName() throws IOException { writeMainClass(); - assertThat(this.gradleBuild.script("src/docs/gradle/running/spring-boot-dsl-main-class-name") + assertThat(this.gradleBuild.script(Examples.DIR + "running/spring-boot-dsl-main-class-name") .build("bootRun") .getOutput()).contains("com.example.ExampleApplication"); } @TestTemplate void bootRunSourceResources() { - assertThat(this.gradleBuild.script("src/docs/gradle/running/boot-run-source-resources") + assertThat(this.gradleBuild.script(Examples.DIR + "running/boot-run-source-resources") .build("configuredClasspath") .getOutput()).contains(new File("src/main/resources").getPath()); } @TestTemplate void bootRunDisableOptimizedLaunch() { - assertThat(this.gradleBuild.script("src/docs/gradle/running/boot-run-disable-optimized-launch") + assertThat(this.gradleBuild.script(Examples.DIR + "running/boot-run-disable-optimized-launch") .build("optimizedLaunch") .getOutput()).contains("false"); } @TestTemplate void bootRunSystemPropertyDefaultValue() { - assertThat(this.gradleBuild.script("src/docs/gradle/running/boot-run-system-property") + assertThat(this.gradleBuild.script(Examples.DIR + "running/boot-run-system-property") .build("configuredSystemProperties") .getOutput()).contains("com.example.property = default"); } @TestTemplate void bootRunSystemProperty() { - assertThat(this.gradleBuild.script("src/docs/gradle/running/boot-run-system-property") + assertThat(this.gradleBuild.script(Examples.DIR + "running/boot-run-system-property") .build("-Pexample=custom", "configuredSystemProperties") .getOutput()).contains("com.example.property = custom"); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/ApplicationPluginActionIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/ApplicationPluginActionIntegrationTests.java index 6d2daf320a1a..292dd2bbd52b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/ApplicationPluginActionIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/ApplicationPluginActionIntegrationTests.java @@ -195,7 +195,7 @@ private List tarEntryNames(File distribution) throws IOException { List entryNames = new ArrayList<>(); try (TarArchiveInputStream input = new TarArchiveInputStream(new FileInputStream(distribution))) { TarArchiveEntry entry; - while ((entry = input.getNextTarEntry()) != null) { + while ((entry = input.getNextEntry()) != null) { entryNames.add(entry.getName()); } } @@ -205,7 +205,7 @@ private List tarEntryNames(File distribution) throws IOException { private void tarEntries(File distribution, Consumer consumer) throws IOException { try (TarArchiveInputStream input = new TarArchiveInputStream(new FileInputStream(distribution))) { TarArchiveEntry entry; - while ((entry = input.getNextTarEntry()) != null) { + while ((entry = input.getNextEntry()) != null) { consumer.accept(entry); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.java index e966bdfab6d6..1e3e77782209 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.java @@ -144,7 +144,52 @@ void additionalMetadataLocationsNotConfiguredWhenProcessorIsAbsent() throws IOEx @TestTemplate void applyingJavaPluginCreatesDevelopmentOnlyConfiguration() { - assertThat(this.gradleBuild.build("build").getOutput()).contains("developmentOnly exists = true"); + assertThat(this.gradleBuild.build("help").getOutput()).contains("developmentOnly exists = true"); + } + + @TestTemplate + void applyingJavaPluginCreatesTestAndDevelopmentOnlyConfiguration() { + assertThat(this.gradleBuild.build("help").getOutput()).contains("testAndDevelopmentOnly exists = true"); + } + + @TestTemplate + void testCompileClasspathIncludesTestAndDevelopmentOnlyDependencies() { + assertThat(this.gradleBuild.build("help").getOutput()).contains("commons-lang3-3.12.0.jar"); + } + + @TestTemplate + void testRuntimeClasspathIncludesTestAndDevelopmentOnlyDependencies() { + assertThat(this.gradleBuild.build("help").getOutput()).contains("commons-lang3-3.12.0.jar"); + } + + @TestTemplate + void testCompileClasspathDoesNotIncludeDevelopmentOnlyDependencies() { + assertThat(this.gradleBuild.build("help").getOutput()).doesNotContain("commons-lang3-3.12.0.jar"); + } + + @TestTemplate + void testRuntimeClasspathDoesNotIncludeDevelopmentOnlyDependencies() { + assertThat(this.gradleBuild.build("help").getOutput()).doesNotContain("commons-lang3-3.12.0.jar"); + } + + @TestTemplate + void compileClasspathDoesNotIncludeTestAndDevelopmentOnlyDependencies() { + assertThat(this.gradleBuild.build("help").getOutput()).doesNotContain("commons-lang3-3.12.0.jar"); + } + + @TestTemplate + void runtimeClasspathIncludesTestAndDevelopmentOnlyDependencies() { + assertThat(this.gradleBuild.build("help").getOutput()).contains("commons-lang3-3.12.0.jar"); + } + + @TestTemplate + void compileClasspathDoesNotIncludeDevelopmentOnlyDependencies() { + assertThat(this.gradleBuild.build("help").getOutput()).doesNotContain("commons-lang3-3.12.0.jar"); + } + + @TestTemplate + void runtimeClasspathIncludesDevelopmentOnlyDependencies() { + assertThat(this.gradleBuild.build("help").getOutput()).contains("commons-lang3-3.12.0.jar"); } @TestTemplate diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests.java index 85a089bd5084..ab32302702fc 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests.java @@ -23,11 +23,13 @@ import java.util.Set; import org.gradle.testkit.runner.BuildResult; -import org.gradle.util.GradleVersion; -import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledForJreRange; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; +import org.springframework.boot.testsupport.gradle.testkit.GradleBuildExtension; import static org.assertj.core.api.Assertions.assertThat; @@ -36,43 +38,41 @@ * * @author Andy Wilkinson */ -@GradleCompatibility +@DisabledForJreRange(min = JRE.JAVA_20) +@ExtendWith(GradleBuildExtension.class) class KotlinPluginActionIntegrationTests { - GradleBuild gradleBuild; + GradleBuild gradleBuild = new GradleBuild(); - @TestTemplate + @Test void noKotlinVersionPropertyWithoutKotlinPlugin() { assertThat(this.gradleBuild.build("kotlinVersion").getOutput()).contains("Kotlin version: none"); } - @TestTemplate + @Test void kotlinVersionPropertyIsSet() { - String output = this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.1-rc-1") - .build("kotlinVersion", "dependencies", "--configuration", "compileClasspath") + String output = this.gradleBuild.build("kotlinVersion", "dependencies", "--configuration", "compileClasspath") .getOutput(); assertThat(output).containsPattern("Kotlin version: [0-9]\\.[0-9]\\.[0-9]+"); } - @TestTemplate + @Test void kotlinCompileTasksUseJavaParametersFlagByDefault() { - assertThat(this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.1-rc-1") - .build("kotlinCompileTasksJavaParameters") - .getOutput()).contains("compileKotlin java parameters: true") + assertThat(this.gradleBuild.build("kotlinCompileTasksJavaParameters").getOutput()) + .contains("compileKotlin java parameters: true") .contains("compileTestKotlin java parameters: true"); } - @TestTemplate + @Test void kotlinCompileTasksCanOverrideDefaultJavaParametersFlag() { - assertThat(this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.1-rc-1") - .build("kotlinCompileTasksJavaParameters") - .getOutput()).contains("compileKotlin java parameters: false") + assertThat(this.gradleBuild.build("kotlinCompileTasksJavaParameters").getOutput()) + .contains("compileKotlin java parameters: false") .contains("compileTestKotlin java parameters: false"); } - @TestTemplate + @Test void taskConfigurationIsAvoided() throws IOException { - BuildResult result = this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.1-rc-1").build("help"); + BuildResult result = this.gradleBuild.build("help"); String output = result.getOutput(); BufferedReader reader = new BufferedReader(new StringReader(output)); String line; @@ -82,16 +82,7 @@ void taskConfigurationIsAvoided() throws IOException { configured.add(line.substring("Configuring :".length())); } } - if (GradleVersion.version(this.gradleBuild.getGradleVersion()).compareTo(GradleVersion.version("7.3.3")) < 0) { - assertThat(configured).containsExactly("help"); - } - else if (GradleVersion.version(this.gradleBuild.getGradleVersion()) - .compareTo(GradleVersion.version("8.3")) < 0) { - assertThat(configured).containsExactlyInAnyOrder("help", "clean"); - } - else { - assertThat(configured).containsExactlyInAnyOrder("help", "clean", "compileJava"); - } + assertThat(configured).containsExactlyInAnyOrder("help", "clean"); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests.java index 80876724b21f..d3f471771bfe 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests.java @@ -92,8 +92,7 @@ void reachabilityMetadataConfigurationFilesFromFileRepositoryAreCopiedToJar() th void bootBuildImageIsConfiguredToBuildANativeImage() { writeDummySpringApplicationAotProcessorMainClass(); BuildResult result = this.gradleBuild.build("bootBuildImageConfiguration"); - assertThat(result.getOutput()).contains("paketobuildpacks/builder-jammy-tiny:latest") - .contains("BP_NATIVE_IMAGE = true"); + assertThat(result.getOutput()).contains("BP_NATIVE_IMAGE = true"); } @TestTemplate @@ -103,6 +102,13 @@ void developmentOnlyDependenciesDoNotAppearInNativeImageClasspath() { assertThat(result.getOutput()).doesNotContain("commons-lang"); } + @TestTemplate + void testAndDevelopmentOnlyDependenciesDoNotAppearInNativeImageClasspath() { + writeDummySpringApplicationAotProcessorMainClass(); + BuildResult result = this.gradleBuild.build("checkNativeImageClasspath"); + assertThat(result.getOutput()).doesNotContain("commons-lang"); + } + @TestTemplate void classesGeneratedDuringAotProcessingAreOnTheNativeImageClasspath() { BuildResult result = this.gradleBuild.build("checkNativeImageClasspath"); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests.java index aead5ebcc535..7abf855bbeae 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests.java @@ -106,10 +106,22 @@ void processAotDoesNotHaveDevelopmentOnlyDependenciesOnItsClasspath() { @TestTemplate void processTestAotDoesNotHaveDevelopmentOnlyDependenciesOnItsClasspath() { - String output = this.gradleBuild.build("processTestAotClasspath", "--stacktrace").getOutput(); + String output = this.gradleBuild.build("processTestAotClasspath").getOutput(); + assertThat(output).doesNotContain("commons-lang"); + } + + @TestTemplate + void processAotDoesNotHaveTestAndDevelopmentOnlyDependenciesOnItsClasspath() { + String output = this.gradleBuild.build("processAotClasspath").getOutput(); assertThat(output).doesNotContain("commons-lang"); } + @TestTemplate + void processTestAotHasTestAndDevelopmentOnlyDependenciesOnItsClasspath() { + String output = this.gradleBuild.build("processTestAotClasspath").getOutput(); + assertThat(output).contains("commons-lang"); + } + @TestTemplate void processAotRunsWhenProjectHasMainSource() throws IOException { writeMainClass("org.springframework.boot", "SpringApplicationAotProcessor"); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/SpringBootPluginIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/SpringBootPluginIntegrationTests.java deleted file mode 100644 index 8128321c3872..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/SpringBootPluginIntegrationTests.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2012-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.gradle.plugin; - -import org.gradle.testkit.runner.BuildResult; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledForJreRange; -import org.junit.jupiter.api.condition.JRE; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; -import org.springframework.boot.testsupport.gradle.testkit.GradleBuildExtension; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link SpringBootPlugin}. - * - * @author Andy Wilkinson - */ -@ExtendWith(GradleBuildExtension.class) -class SpringBootPluginIntegrationTests { - - final GradleBuild gradleBuild = new GradleBuild(); - - @Test - @DisabledForJreRange(min = JRE.JAVA_20) - void failFastWithVersionOfGradle7LowerThanRequired() { - BuildResult result = this.gradleBuild.gradleVersion("7.4.1").buildAndFail(); - assertThat(result.getOutput()) - .contains("Spring Boot plugin requires Gradle 7.x (7.5 or later). The current version is Gradle 7.4.1"); - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java index 42a4f9918156..3dce1fed4c3a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,6 +66,7 @@ * @author Andy Wilkinson * @author Madhura Bhave * @author Scott Frederick + * @author Moritz Halbritter */ abstract class AbstractBootArchiveIntegrationTests { @@ -106,6 +107,16 @@ void reproducibleArchive() throws IOException, InterruptedException { assertThat(firstHash).isEqualTo(secondHash); } + @TestTemplate + void classicLoader() throws IOException { + assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome()) + .isEqualTo(TaskOutcome.SUCCESS); + File jar = new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0]; + try (JarFile jarFile = new JarFile(jar)) { + assertThat(jarFile.getEntry("org/springframework/boot/loader/LaunchedURLClassLoader.class")).isNotNull(); + } + } + @TestTemplate void upToDateWhenBuiltTwice() { assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome()) @@ -221,6 +232,41 @@ void developmentOnlyDependenciesCanBeIncludedInTheArchive() throws IOException { } } + @TestTemplate + void testAndDevelopmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault() throws IOException { + File srcMainResources = new File(this.gradleBuild.getProjectDir(), "src/main/resources"); + srcMainResources.mkdirs(); + new File(srcMainResources, "resource").createNewFile(); + assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome()) + .isEqualTo(TaskOutcome.SUCCESS); + try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { + Stream libEntryNames = jarFile.stream() + .filter((entry) -> !entry.isDirectory()) + .map(JarEntry::getName) + .filter((name) -> name.startsWith(this.libPath)); + assertThat(libEntryNames).containsExactly(this.libPath + "commons-io-2.6.jar"); + Stream classesEntryNames = jarFile.stream() + .filter((entry) -> !entry.isDirectory()) + .map(JarEntry::getName) + .filter((name) -> name.startsWith(this.classesPath)); + assertThat(classesEntryNames).containsExactly(this.classesPath + "resource"); + } + } + + @TestTemplate + void testAndDevelopmentOnlyDependenciesCanBeIncludedInTheArchive() throws IOException { + assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome()) + .isEqualTo(TaskOutcome.SUCCESS); + try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { + Stream libEntryNames = jarFile.stream() + .filter((entry) -> !entry.isDirectory()) + .map(JarEntry::getName) + .filter((name) -> name.startsWith(this.libPath)); + assertThat(libEntryNames).containsExactly(this.libPath + "commons-io-2.6.jar", + this.libPath + "commons-lang3-3.9.jar"); + } + } + @TestTemplate void jarTypeFilteringIsApplied() throws IOException { File flatDirRepository = new File(this.gradleBuild.getProjectDir(), "repository"); @@ -287,6 +333,18 @@ void notUpToDateWhenBuiltWithLayerToolsAndThenWithoutLayerTools() { .getOutcome()).isEqualTo(TaskOutcome.SUCCESS); } + @TestTemplate + void notUpToDateWhenBuiltWithToolsAndThenWithoutTools() { + assertThat(this.gradleBuild.scriptProperty("includeTools", "") + .build(this.taskName) + .task(":" + this.taskName) + .getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(this.gradleBuild.scriptProperty("includeTools", "includeTools = false") + .build(this.taskName) + .task(":" + this.taskName) + .getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + } + @TestTemplate void layersWithCustomSourceSet() { assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome()) @@ -300,7 +358,7 @@ void implicitLayers() throws IOException { assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome()) .isEqualTo(TaskOutcome.SUCCESS); Map> indexedLayers; - String layerToolsJar = this.libPath + JarModeLibrary.LAYER_TOOLS.getName(); + String layerToolsJar = this.libPath + JarModeLibrary.TOOLS.getName(); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { assertThat(jarFile.getEntry(layerToolsJar)).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "commons-lang3-3.9.jar")).isNotNull(); @@ -352,7 +410,7 @@ void multiModuleImplicitLayers() throws IOException { assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome()) .isEqualTo(TaskOutcome.SUCCESS); Map> indexedLayers; - String layerToolsJar = this.libPath + JarModeLibrary.LAYER_TOOLS.getName(); + String layerToolsJar = this.libPath + JarModeLibrary.TOOLS.getName(); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { assertThat(jarFile.getEntry(layerToolsJar)).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "alpha-1.2.3.jar")).isNotNull(); @@ -398,7 +456,7 @@ void customLayers() throws IOException { BuildResult build = this.gradleBuild.build(this.taskName); assertThat(build.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS); Map> indexedLayers; - String layerToolsJar = this.libPath + JarModeLibrary.LAYER_TOOLS.getName(); + String layerToolsJar = this.libPath + JarModeLibrary.TOOLS.getName(); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { assertThat(jarFile.getEntry(layerToolsJar)).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "commons-lang3-3.9.jar")).isNotNull(); @@ -445,7 +503,7 @@ void multiModuleCustomLayers() throws IOException { BuildResult build = this.gradleBuild.build(this.taskName); assertThat(build.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS); Map> indexedLayers; - String layerToolsJar = this.libPath + JarModeLibrary.LAYER_TOOLS.getName(); + String layerToolsJar = this.libPath + JarModeLibrary.TOOLS.getName(); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { assertThat(jarFile.getEntry(layerToolsJar)).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "alpha-1.2.3.jar")).isNotNull(); @@ -551,7 +609,11 @@ void defaultDirAndFileModesAreUsed() throws IOException { @TestTemplate void dirModeAndFileModeAreApplied() throws IOException { - BuildResult result = this.gradleBuild.build(this.taskName); + BuildResult result = this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.8-rc-1") + .expectDeprecationMessages("The CopyProcessingSpec.setDirMode(Integer) method has been deprecated", + "The CopyProcessingSpec.setFileMode(Integer) method has been deprecated", + "upgrading_version_8.html#unix_file_permissions_deprecated") + .build(this.taskName); assertThat(result.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS); try (ZipFile jarFile = new ZipFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { Enumeration entries = jarFile.getEntries(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java index ec338420f1b9..ebda757e698d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,6 +65,7 @@ import org.springframework.boot.gradle.junit.GradleProjectBuilder; import org.springframework.boot.loader.tools.DefaultLaunchScript; import org.springframework.boot.loader.tools.JarModeLibrary; +import org.springframework.boot.loader.tools.LoaderImplementation; import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -79,6 +80,7 @@ * @param the type of the concrete BootArchive implementation * @author Andy Wilkinson * @author Scott Frederick + * @author Moritz Halbritter */ abstract class AbstractBootArchiveTests { @@ -255,7 +257,8 @@ void loaderIsWrittenToTheRootOfTheJarAfterManifest() throws IOException { this.task.getMainClass().set("com.example.Main"); executeTask(); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { - assertThat(jarFile.getEntry("org/springframework/boot/loader/LaunchedURLClassLoader.class")).isNotNull(); + assertThat(jarFile.getEntry("org/springframework/boot/loader/launch/LaunchedClassLoader.class")) + .isNotNull(); assertThat(jarFile.getEntry("org/springframework/boot/loader/")).isNotNull(); } // gh-16698 @@ -270,7 +273,21 @@ void loaderIsWrittenToTheRootOfTheJarAfterManifest() throws IOException { void loaderIsWrittenToTheRootOfTheJarWhenUsingThePropertiesLauncher() throws IOException { this.task.getMainClass().set("com.example.Main"); executeTask(); - this.task.getManifest().getAttributes().put("Main-Class", "org.springframework.boot.loader.PropertiesLauncher"); + this.task.getManifest() + .getAttributes() + .put("Main-Class", "org.springframework.boot.loader.launch.PropertiesLauncher"); + try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { + assertThat(jarFile.getEntry("org/springframework/boot/loader/launch/LaunchedClassLoader.class")) + .isNotNull(); + assertThat(jarFile.getEntry("org/springframework/boot/loader/")).isNotNull(); + } + } + + @Test + void loaderIsWrittenToTheRootOfTheJarWhenUsingClassicLoader() throws IOException { + this.task.getMainClass().set("com.example.Main"); + this.task.getLoaderImplementation().set(LoaderImplementation.CLASSIC); + executeTask(); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry("org/springframework/boot/loader/LaunchedURLClassLoader.class")).isNotNull(); assertThat(jarFile.getEntry("org/springframework/boot/loader/")).isNotNull(); @@ -362,7 +379,7 @@ void customMainClassInTheManifestIsHonored() throws IOException { assertThat(jarFile.getManifest().getMainAttributes().getValue("Main-Class")) .isEqualTo("com.example.CustomLauncher"); assertThat(jarFile.getManifest().getMainAttributes().getValue("Start-Class")).isEqualTo("com.example.Main"); - assertThat(jarFile.getEntry("org/springframework/boot/loader/LaunchedURLClassLoader.class")).isNull(); + assertThat(jarFile.getEntry("org/springframework/boot/loader/launch/LaunchedClassLoader.class")).isNull(); } } @@ -480,7 +497,7 @@ void archiveShouldBeLayeredByDefault() throws IOException { assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Lib")).isEqualTo(this.libPath); assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Layers-Index")) .isEqualTo(this.indexPath + "layers.idx"); - assertThat(getEntryNames(jarFile)).contains(this.libPath + JarModeLibrary.LAYER_TOOLS.getName()); + assertThat(getEntryNames(jarFile)).contains(this.libPath + JarModeLibrary.TOOLS.getName()); } } @@ -488,7 +505,7 @@ void archiveShouldBeLayeredByDefault() throws IOException { void jarWhenLayersDisabledShouldNotContainLayersIndex() throws IOException { List entryNames = getEntryNames( createLayeredJar((configuration) -> configuration.getEnabled().set(false))); - assertThat(entryNames).doesNotContain(this.indexPath + "layers.idx"); + assertThat(entryNames).isNotEmpty().doesNotContain(this.indexPath + "layers.idx"); } @Test @@ -514,7 +531,7 @@ void whenJarIsLayeredThenLayersIndexIsPresentAndCorrect() throws IOException { List index = entryLines(jarFile, this.indexPath + "layers.idx"); assertThat(getLayerNames(index)).containsExactly("dependencies", "spring-boot-loader", "snapshot-dependencies", "application"); - String layerToolsJar = this.libPath + JarModeLibrary.LAYER_TOOLS.getName(); + String layerToolsJar = this.libPath + JarModeLibrary.TOOLS.getName(); List expected = new ArrayList<>(); expected.add("- \"dependencies\":"); expected.add(" - \"" + this.libPath + "first-library.jar\""); @@ -568,7 +585,7 @@ void whenJarIsLayeredWithCustomStrategiesThenLayersIndexIsPresentAndCorrect() th List index = entryLines(jarFile, this.indexPath + "layers.idx"); assertThat(getLayerNames(index)).containsExactly("my-deps", "my-internal-deps", "my-snapshot-deps", "resources", "application"); - String layerToolsJar = this.libPath + JarModeLibrary.LAYER_TOOLS.getName(); + String layerToolsJar = this.libPath + JarModeLibrary.TOOLS.getName(); List expected = new ArrayList<>(); expected.add("- \"my-deps\":"); expected.add(" - \"" + layerToolsJar + "\""); @@ -598,15 +615,32 @@ void whenJarIsLayeredWithCustomStrategiesThenLayersIndexIsPresentAndCorrect() th @Test void whenArchiveIsLayeredThenLayerToolsAreAddedToTheJar() throws IOException { List entryNames = getEntryNames(createLayeredJar()); - assertThat(entryNames).contains(this.libPath + JarModeLibrary.LAYER_TOOLS.getName()); + assertThat(entryNames).contains(this.libPath + JarModeLibrary.TOOLS.getName()); } @Test + void shouldAddToolsToTheJar() throws IOException { + this.task.getMainClass().set("com.example.Main"); + executeTask(); + List entryNames = getEntryNames(this.task.getArchiveFile().get().getAsFile()); + assertThat(entryNames).isNotEmpty().contains(this.libPath + JarModeLibrary.TOOLS.getName()); + } + + @Test + @SuppressWarnings("removal") void whenArchiveIsLayeredAndIncludeLayerToolsIsFalseThenLayerToolsAreNotAddedToTheJar() throws IOException { List entryNames = getEntryNames( createLayeredJar((configuration) -> configuration.getIncludeLayerTools().set(false))); - assertThat(entryNames) - .doesNotContain(this.indexPath + "layers/dependencies/lib/spring-boot-jarmode-layertools.jar"); + assertThat(entryNames).isNotEmpty().doesNotContain(this.libPath + JarModeLibrary.TOOLS.getName()); + } + + @Test + void whenIncludeToolsIsFalseThenToolsAreNotAddedToTheJar() throws IOException { + this.task.getIncludeTools().set(false); + this.task.getMainClass().set("com.example.Main"); + executeTask(); + List entryNames = getEntryNames(this.task.getArchiveFile().get().getAsFile()); + assertThat(entryNames).isNotEmpty().doesNotContain(this.libPath + JarModeLibrary.TOOLS.getName()); } protected File jarFile(String name) throws IOException { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java index 33dac8a784e4..348af9d7a26a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import java.io.File; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -171,14 +172,24 @@ void whenUsingDefaultConfigurationThenRequestHasPublishDisabled() { @Test void whenNoBuilderIsConfiguredThenRequestHasDefaultBuilder() { - assertThat(this.buildImage.createRequest().getBuilder().getName()) - .isEqualTo("paketobuildpacks/builder-jammy-base"); + BuildRequest request = this.buildImage.createRequest(); + assertThat(request.getBuilder().getName()).isEqualTo("paketobuildpacks/builder-jammy-tiny"); + assertThat(request.isTrustBuilder()).isTrue(); } @Test void whenBuilderIsConfiguredThenRequestUsesSpecifiedBuilder() { this.buildImage.getBuilder().set("example.com/test/builder:1.2"); - assertThat(this.buildImage.createRequest().getBuilder().getName()).isEqualTo("test/builder"); + BuildRequest request = this.buildImage.createRequest(); + assertThat(request.getBuilder().getName()).isEqualTo("test/builder"); + assertThat(request.isTrustBuilder()).isFalse(); + } + + @Test + void whenTrustBuilderIsEnabledThenRequestHasTrustBuilderEnabled() { + this.buildImage.getBuilder().set("example.com/test/builder:1.2"); + this.buildImage.getTrustBuilder().set(true); + assertThat(this.buildImage.createRequest().isTrustBuilder()).isTrue(); } @Test @@ -294,4 +305,23 @@ void whenIndividualEntriesAreAddedToTagsThenRequestHasTags() { ImageReference.of("example.com/my-app:0.0.1-SNAPSHOT"), ImageReference.of("example.com/my-app:latest")); } + @Test + void whenSecurityOptionsAreNotConfiguredThenRequestHasNoSecurityOptions() { + assertThat(this.buildImage.createRequest().getSecurityOptions()).isNull(); + } + + @Test + void whenSecurityOptionsAreEmptyThenRequestHasEmptySecurityOptions() { + this.buildImage.getSecurityOptions().set(Collections.emptyList()); + assertThat(this.buildImage.createRequest().getSecurityOptions()).isEmpty(); + } + + @Test + void whenSecurityOptionsAreConfiguredThenRequestHasSecurityOptions() { + this.buildImage.getSecurityOptions().add("label=user:USER"); + this.buildImage.getSecurityOptions().add("label=role:ROLE"); + assertThat(this.buildImage.createRequest().getSecurityOptions()).containsExactly("label=user:USER", + "label=role:ROLE"); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java index 146fb595ae60..cf14bba28c32 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,15 @@ package org.springframework.boot.gradle.tasks.bundling; +import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Set; import java.util.TreeSet; +import java.util.jar.JarFile; import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.junit.GradleCompatibility; @@ -42,6 +45,15 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests { super("bootJar", "BOOT-INF/lib/", "BOOT-INF/classes/", "BOOT-INF/"); } + @TestTemplate + void signed() throws Exception { + assertThat(this.gradleBuild.build("bootJar").task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + File jar = new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0]; + try (JarFile jarFile = new JarFile(jar)) { + assertThat(jarFile.getEntry("META-INF/BOOT.SF")).isNotNull(); + } + } + @TestTemplate void whenAResolvableCopyOfAnUnresolvableConfigurationIsResolvedThenResolutionSucceeds() { this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.0").build("build"); @@ -55,7 +67,7 @@ void packagedApplicationClasspath() throws IOException { assertThat(output).containsPattern("1\\. .*classes"); assertThat(output).containsPattern("2\\. .*library-1.0-SNAPSHOT.jar"); assertThat(output).containsPattern("3\\. .*commons-lang3-3.9.jar"); - assertThat(output).containsPattern("4\\. .*spring-boot-jarmode-layertools.*.jar"); + assertThat(output).containsPattern("4\\. .*spring-boot-jarmode-tools.*.jar"); assertThat(output).doesNotContain("5. "); } @@ -65,7 +77,7 @@ void explodedApplicationClasspath() throws IOException { BuildResult result = this.gradleBuild.build("launch"); String output = result.getOutput(); assertThat(output).containsPattern("1\\. .*classes"); - assertThat(output).containsPattern("2\\. .*spring-boot-jarmode-layertools.*.jar"); + assertThat(output).containsPattern("2\\. .*spring-boot-jarmode-tools.*.jar"); assertThat(output).containsPattern("3\\. .*library-1.0-SNAPSHOT.jar"); assertThat(output).containsPattern("4\\. .*commons-lang3-3.9.jar"); assertThat(output).doesNotContain("5. "); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarTests.java index 5355e1ba79d8..2cbe89cf5712 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarTests.java @@ -41,7 +41,7 @@ class BootJarTests extends AbstractBootArchiveTests { BootJarTests() { - super(BootJar.class, "org.springframework.boot.loader.JarLauncher", "BOOT-INF/lib/", "BOOT-INF/classes/", + super(BootJar.class, "org.springframework.boot.loader.launch.JarLauncher", "BOOT-INF/lib/", "BOOT-INF/classes/", "BOOT-INF/"); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarTests.java index e53080ca779f..8728298b4936 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarTests.java @@ -39,7 +39,7 @@ class BootWarTests extends AbstractBootArchiveTests { BootWarTests() { - super(BootWar.class, "org.springframework.boot.loader.WarLauncher", "WEB-INF/lib/", "WEB-INF/classes/", + super(BootWar.class, "org.springframework.boot.loader.launch.WarLauncher", "WEB-INF/lib/", "WEB-INF/classes/", "WEB-INF/"); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java index 7cce2758b0ad..3252cedb2da0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java @@ -25,7 +25,7 @@ import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; import org.springframework.boot.gradle.junit.GradleProjectBuilder; import static org.assertj.core.api.Assertions.assertThat; @@ -68,10 +68,11 @@ void asDockerConfigurationWithHostConfiguration() { this.dockerSpec.getTlsVerify().set(true); this.dockerSpec.getCertPath().set("/tmp/ca-cert"); DockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration(); - DockerHost host = dockerConfiguration.getHost(); + DockerHostConfiguration host = dockerConfiguration.getHost(); assertThat(host.getAddress()).isEqualTo("docker.example.com"); assertThat(host.isSecure()).isTrue(); assertThat(host.getCertificatePath()).isEqualTo("/tmp/ca-cert"); + assertThat(host.getContext()).isNull(); assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse(); assertThat(this.dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull(); assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader())) @@ -85,10 +86,11 @@ void asDockerConfigurationWithHostConfiguration() { void asDockerConfigurationWithHostConfigurationNoTlsVerify() { this.dockerSpec.getHost().set("docker.example.com"); DockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration(); - DockerHost host = dockerConfiguration.getHost(); + DockerHostConfiguration host = dockerConfiguration.getHost(); assertThat(host.getAddress()).isEqualTo("docker.example.com"); assertThat(host.isSecure()).isFalse(); assertThat(host.getCertificatePath()).isNull(); + assertThat(host.getContext()).isNull(); assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse(); assertThat(this.dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull(); assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader())) @@ -98,12 +100,38 @@ void asDockerConfigurationWithHostConfigurationNoTlsVerify() { .contains("\"serveraddress\" : \"\""); } + @Test + void asDockerConfigurationWithContextConfiguration() { + this.dockerSpec.getContext().set("test-context"); + DockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration(); + DockerHostConfiguration host = dockerConfiguration.getHost(); + assertThat(host.getContext()).isEqualTo("test-context"); + assertThat(host.getAddress()).isNull(); + assertThat(host.isSecure()).isFalse(); + assertThat(host.getCertificatePath()).isNull(); + assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse(); + assertThat(this.dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull(); + assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader())) + .contains("\"username\" : \"\"") + .contains("\"password\" : \"\"") + .contains("\"email\" : \"\"") + .contains("\"serveraddress\" : \"\""); + } + + @Test + void asDockerConfigurationWithHostAndContextFails() { + this.dockerSpec.getContext().set("test-context"); + this.dockerSpec.getHost().set("docker.example.com"); + assertThatExceptionOfType(GradleException.class).isThrownBy(this.dockerSpec::asDockerConfiguration) + .withMessageContaining("Invalid Docker configuration"); + } + @Test void asDockerConfigurationWithBindHostToBuilder() { this.dockerSpec.getHost().set("docker.example.com"); this.dockerSpec.getBindHostToBuilder().set(true); DockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration(); - DockerHost host = dockerConfiguration.getHost(); + DockerHostConfiguration host = dockerConfiguration.getHost(); assertThat(host.getAddress()).isEqualTo("docker.example.com"); assertThat(host.isSecure()).isFalse(); assertThat(host.getCertificatePath()).isNull(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests.java index 7550399b8824..54ee2a33f0f5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests.java @@ -146,6 +146,22 @@ void classesFromASecondarySourceSetCanBeOnTheClasspath() throws IOException { assertThat(result.getOutput()).contains("com.example.bootrun.main.CustomMainClass"); } + @TestTemplate + void developmentOnlyDependenciesAreOnTheClasspath() throws IOException { + copyClasspathApplication(); + BuildResult result = this.gradleBuild.build("bootRun"); + assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()).contains("commons-lang3-3.12.0.jar"); + } + + @TestTemplate + void testAndDevelopmentOnlyDependenciesAreOnTheClasspath() throws IOException { + copyClasspathApplication(); + BuildResult result = this.gradleBuild.build("bootRun"); + assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()).contains("commons-lang3-3.12.0.jar"); + } + private void copyMainClassApplication() throws IOException { copyApplication("main"); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests.java index bfd83476049e..826036c06782 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests.java @@ -117,6 +117,22 @@ void failsGracefullyWhenNoTestMainMethodIsFound() throws IOException { } } + @TestTemplate + void developmentOnlyDependenciesAreNotOnTheClasspath() throws IOException { + copyClasspathApplication(); + BuildResult result = this.gradleBuild.build("bootTestRun"); + assertThat(result.task(":bootTestRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()).doesNotContain("commons-lang3-3.12.0.jar"); + } + + @TestTemplate + void testAndDevelopmentOnlyDependenciesAreOnTheClasspath() throws IOException { + copyClasspathApplication(); + BuildResult result = this.gradleBuild.build("bootTestRun"); + assertThat(result.task(":bootTestRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()).contains("commons-lang3-3.12.0.jar"); + } + private void copyClasspathApplication() throws IOException { copyApplication("classpath"); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/dsl/BuildInfoDslIntegrationTests-additionalProperties.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/dsl/BuildInfoDslIntegrationTests-additionalProperties.gradle index 0567e3acb71c..d8d2b8d319e6 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/dsl/BuildInfoDslIntegrationTests-additionalProperties.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/dsl/BuildInfoDslIntegrationTests-additionalProperties.gradle @@ -10,7 +10,8 @@ springBoot { buildInfo { properties { additional = [ - 'a': 'alpha', 'b': 'bravo' + 'a': 'alpha', + 'b': providers.provider({'bravo'}) ] } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-applyingJavaPluginCreatesTestAndDevelopmentOnlyConfiguration.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-applyingJavaPluginCreatesTestAndDevelopmentOnlyConfiguration.gradle new file mode 100644 index 000000000000..ebf12ae42908 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-applyingJavaPluginCreatesTestAndDevelopmentOnlyConfiguration.gradle @@ -0,0 +1,12 @@ +plugins { + id 'org.springframework.boot' version '{version}' + id 'java' +} + +springBoot { + mainClass = "com.example.Main" +} + +gradle.taskGraph.whenReady { + println "testAndDevelopmentOnly exists = ${configurations.findByName('testAndDevelopmentOnly') != null}" +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-compileClasspathDoesNotIncludeDevelopmentOnlyDependencies.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-compileClasspathDoesNotIncludeDevelopmentOnlyDependencies.gradle new file mode 100644 index 000000000000..b956631b4634 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-compileClasspathDoesNotIncludeDevelopmentOnlyDependencies.gradle @@ -0,0 +1,20 @@ +plugins { + id 'org.springframework.boot' version '{version}' + id 'java' +} + +springBoot { + mainClass = "com.example.Main" +} + +repositories { + mavenCentral() +} + +dependencies { + developmentOnly("org.apache.commons:commons-lang3:3.12.0") +} + +gradle.taskGraph.whenReady { + configurations.compileClasspath.resolve().each { println it } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-compileClasspathDoesNotIncludeTestAndDevelopmentOnlyDependencies.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-compileClasspathDoesNotIncludeTestAndDevelopmentOnlyDependencies.gradle new file mode 100644 index 000000000000..6e53332c97f5 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-compileClasspathDoesNotIncludeTestAndDevelopmentOnlyDependencies.gradle @@ -0,0 +1,20 @@ +plugins { + id 'org.springframework.boot' version '{version}' + id 'java' +} + +springBoot { + mainClass = "com.example.Main" +} + +repositories { + mavenCentral() +} + +dependencies { + testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.12.0") +} + +gradle.taskGraph.whenReady { + configurations.compileClasspath.resolve().each { println it } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-runtimeClasspathIncludesDevelopmentOnlyDependencies.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-runtimeClasspathIncludesDevelopmentOnlyDependencies.gradle new file mode 100644 index 000000000000..a8b1f4d3bffd --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-runtimeClasspathIncludesDevelopmentOnlyDependencies.gradle @@ -0,0 +1,20 @@ +plugins { + id 'org.springframework.boot' version '{version}' + id 'java' +} + +springBoot { + mainClass = "com.example.Main" +} + +repositories { + mavenCentral() +} + +dependencies { + developmentOnly("org.apache.commons:commons-lang3:3.12.0") +} + +gradle.taskGraph.whenReady { + configurations.runtimeClasspath.resolve().each { println it } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-runtimeClasspathIncludesTestAndDevelopmentOnlyDependencies.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-runtimeClasspathIncludesTestAndDevelopmentOnlyDependencies.gradle new file mode 100644 index 000000000000..6a51fe371128 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-runtimeClasspathIncludesTestAndDevelopmentOnlyDependencies.gradle @@ -0,0 +1,20 @@ +plugins { + id 'org.springframework.boot' version '{version}' + id 'java' +} + +springBoot { + mainClass = "com.example.Main" +} + +repositories { + mavenCentral() +} + +dependencies { + testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.12.0") +} + +gradle.taskGraph.whenReady { + configurations.runtimeClasspath.resolve().each { println it } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testCompileClasspathDoesNotIncludeDevelopmentOnlyDependencies.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testCompileClasspathDoesNotIncludeDevelopmentOnlyDependencies.gradle new file mode 100644 index 000000000000..264944e602a6 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testCompileClasspathDoesNotIncludeDevelopmentOnlyDependencies.gradle @@ -0,0 +1,20 @@ +plugins { + id 'org.springframework.boot' version '{version}' + id 'java' +} + +springBoot { + mainClass = "com.example.Main" +} + +repositories { + mavenCentral() +} + +dependencies { + developmentOnly("org.apache.commons:commons-lang3:3.12.0") +} + +gradle.taskGraph.whenReady { + configurations.testCompileClasspath.resolve().each { println it } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testCompileClasspathIncludesTestAndDevelopmentOnlyDependencies.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testCompileClasspathIncludesTestAndDevelopmentOnlyDependencies.gradle new file mode 100644 index 000000000000..1f934deadb11 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testCompileClasspathIncludesTestAndDevelopmentOnlyDependencies.gradle @@ -0,0 +1,20 @@ +plugins { + id 'org.springframework.boot' version '{version}' + id 'java' +} + +springBoot { + mainClass = "com.example.Main" +} + +repositories { + mavenCentral() +} + +dependencies { + testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.12.0") +} + +gradle.taskGraph.whenReady { + configurations.testCompileClasspath.resolve().each { println it } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testRuntimeClasspathDoesNotIncludeDevelopmentOnlyDependencies.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testRuntimeClasspathDoesNotIncludeDevelopmentOnlyDependencies.gradle new file mode 100644 index 000000000000..4334e2a97bd2 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testRuntimeClasspathDoesNotIncludeDevelopmentOnlyDependencies.gradle @@ -0,0 +1,20 @@ +plugins { + id 'org.springframework.boot' version '{version}' + id 'java' +} + +springBoot { + mainClass = "com.example.Main" +} + +repositories { + mavenCentral() +} + +dependencies { + developmentOnly("org.apache.commons:commons-lang3:3.12.0") +} + +gradle.taskGraph.whenReady { + configurations.testRuntimeClasspath.resolve().each { println it } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testRuntimeClasspathIncludesTestAndDevelopmentOnlyDependencies.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testRuntimeClasspathIncludesTestAndDevelopmentOnlyDependencies.gradle new file mode 100644 index 000000000000..581c58617968 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testRuntimeClasspathIncludesTestAndDevelopmentOnlyDependencies.gradle @@ -0,0 +1,20 @@ +plugins { + id 'org.springframework.boot' version '{version}' + id 'java' +} + +springBoot { + mainClass = "com.example.Main" +} + +repositories { + mavenCentral() +} + +dependencies { + testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.12.0") +} + +gradle.taskGraph.whenReady { + configurations.testRuntimeClasspath.resolve().each { println it } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests-bootBuildImageIsConfiguredToBuildANativeImage.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests-bootBuildImageIsConfiguredToBuildANativeImage.gradle index 5af90e228e91..969f40bd1eb1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests-bootBuildImageIsConfiguredToBuildANativeImage.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests-bootBuildImageIsConfiguredToBuildANativeImage.gradle @@ -7,7 +7,6 @@ apply plugin: 'org.graalvm.buildtools.native' task('bootBuildImageConfiguration') { doFirst { - println "builder = ${tasks.getByName('bootBuildImage').builder.get()}" println "BP_NATIVE_IMAGE = ${tasks.getByName('bootBuildImage').environment.get()['BP_NATIVE_IMAGE']}" } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests-testAndDevelopmentOnlyDependenciesDoNotAppearInNativeImageClasspath.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests-testAndDevelopmentOnlyDependenciesDoNotAppearInNativeImageClasspath.gradle new file mode 100644 index 000000000000..62d4912299a1 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests-testAndDevelopmentOnlyDependenciesDoNotAppearInNativeImageClasspath.gradle @@ -0,0 +1,20 @@ +plugins { + id 'org.springframework.boot' version '{version}' + id 'java' +} + +apply plugin: 'org.graalvm.buildtools.native' + +repositories { + mavenCentral() +} + +dependencies { + testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.12.0") +} + +task('checkNativeImageClasspath') { + doFirst { + tasks.nativeCompile.options.get().classpath.each { println it } + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-processAotDoesNotHaveTestAndDevelopmentOnlyDependenciesOnItsClasspath.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-processAotDoesNotHaveTestAndDevelopmentOnlyDependenciesOnItsClasspath.gradle new file mode 100644 index 000000000000..0568dc1a9c21 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-processAotDoesNotHaveTestAndDevelopmentOnlyDependenciesOnItsClasspath.gradle @@ -0,0 +1,20 @@ +plugins { + id 'org.springframework.boot' version '{version}' + id 'java' +} + +apply plugin: 'org.springframework.boot.aot' + +repositories { + mavenCentral() +} + +dependencies { + testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.12.0") +} + +task('processAotClasspath') { + doFirst { + tasks.processAot.classpath.each { println it } + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-processTestAotHasTestAndDevelopmentOnlyDependenciesOnItsClasspath.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-processTestAotHasTestAndDevelopmentOnlyDependenciesOnItsClasspath.gradle new file mode 100644 index 000000000000..abfe85dbb543 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-processTestAotHasTestAndDevelopmentOnlyDependenciesOnItsClasspath.gradle @@ -0,0 +1,31 @@ +plugins { + id 'org.springframework.boot' version '{version}' + id 'java' +} + +apply plugin: 'org.springframework.boot.aot' + +repositories { + mavenCentral() + maven { url 'repository' } +} + +configurations.all { + resolutionStrategy { + eachDependency { + if (it.requested.group == 'org.springframework.boot') { + it.useVersion project.bootVersion + } + } + } +} + +dependencies { + testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.12.0") +} + +task('processTestAotClasspath') { + doFirst { + tasks.processTestAot.classpath.each { println it } + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWhenCachesAreConfiguredTwice.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWhenCachesAreConfiguredTwice.gradle deleted file mode 100644 index 2554ec6fc03b..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWhenCachesAreConfiguredTwice.gradle +++ /dev/null @@ -1,16 +0,0 @@ -plugins { - id 'java' - id 'org.springframework.boot' version '{version}' -} - -bootBuildImage { - builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1" - buildCache { - volume { - name = "build-cache-volume1" - } - volume { - name = "build-cache-volum2" - } - } -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-classicLoader.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-classicLoader.gradle new file mode 100644 index 000000000000..2e9e26c99cad --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-classicLoader.gradle @@ -0,0 +1,9 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootJar { + mainClass = 'com.example.Application' + loaderImplementation = org.springframework.boot.loader.tools.LoaderImplementation.CLASSIC +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-customLayers.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-customLayers.gradle index b69f85638018..0c5a9175aaa7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-customLayers.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-customLayers.gradle @@ -38,12 +38,12 @@ dependencies { task listLayers(type: JavaExec) { classpath = bootJar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "list" + systemProperties = [ "jarmode": "tools" ] + args "list-layers" } task extractLayers(type: JavaExec) { classpath = bootJar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "extract" + systemProperties = [ "jarmode": "tools" ] + args "extract", "--layers", "--launcher", "--destination", ".", "--force" } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-developmentOnlyDependenciesAreNotIncludedInTheArchive.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-developmentOnlyDependenciesAreNotIncludedInTheArchive.gradle index cdbb87315a6e..2e42641b0081 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-developmentOnlyDependenciesAreNotIncludedInTheArchive.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-developmentOnlyDependenciesAreNotIncludedInTheArchive.gradle @@ -17,7 +17,5 @@ dependencies { } bootJar { - layered { - enabled = false - } + includeTools = false } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-developmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-developmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle index 941f20aa4c92..803b09d444eb 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-developmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-developmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle @@ -18,7 +18,5 @@ dependencies { } bootJar { - layered { - enabled = false - } + includeTools = false } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-developmentOnlyDependenciesCanBeIncludedInTheArchive.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-developmentOnlyDependenciesCanBeIncludedInTheArchive.gradle index d035cf456ef0..a412d8d01b8b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-developmentOnlyDependenciesCanBeIncludedInTheArchive.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-developmentOnlyDependenciesCanBeIncludedInTheArchive.gradle @@ -21,7 +21,5 @@ bootJar { } bootJar { - layered { - enabled = false - } + includeTools = false } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-explodedApplicationClasspath.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-explodedApplicationClasspath.gradle index 194eb9cf0e60..757a45e38a6f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-explodedApplicationClasspath.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-explodedApplicationClasspath.gradle @@ -21,5 +21,5 @@ task explode(type: Sync) { task launch(type: JavaExec) { classpath = files(explode) - mainClass = 'org.springframework.boot.loader.JarLauncher' + mainClass = 'org.springframework.boot.loader.launch.JarLauncher' } \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-implicitLayers.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-implicitLayers.gradle index 9a9d19d59fe4..d3f7649e75f3 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-implicitLayers.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-implicitLayers.gradle @@ -21,12 +21,12 @@ dependencies { task listLayers(type: JavaExec) { classpath = bootJar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "list" + systemProperties = [ "jarmode": "tools" ] + args "list-layers" } task extractLayers(type: JavaExec) { classpath = bootJar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "extract" + systemProperties = [ "jarmode": "tools" ] + args "extract", "--layers", "--launcher", "--destination", ".", "--force" } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-jarTypeFilteringIsApplied.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-jarTypeFilteringIsApplied.gradle index 970d90d116f9..29e8f89004c7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-jarTypeFilteringIsApplied.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-jarTypeFilteringIsApplied.gradle @@ -19,7 +19,5 @@ dependencies { } bootJar { - layered { - enabled = false - } + includeTools = false } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-layersWithCustomSourceSet.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-layersWithCustomSourceSet.gradle index 5ad8628d0582..d7005e366e51 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-layersWithCustomSourceSet.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-layersWithCustomSourceSet.gradle @@ -24,12 +24,12 @@ dependencies { task listLayers(type: JavaExec) { classpath = bootJar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "list" + systemProperties = [ "jarmode": "tools" ] + args "list-layers" } task extractLayers(type: JavaExec) { classpath = bootJar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "extract" + systemProperties = [ "jarmode": "tools" ] + args "extract", "--layers", "--launcher", "--destination", ".", "--force" } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-multiModuleCustomLayers.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-multiModuleCustomLayers.gradle index d76772925c71..df0629d86ed3 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-multiModuleCustomLayers.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-multiModuleCustomLayers.gradle @@ -55,12 +55,12 @@ dependencies { task listLayers(type: JavaExec) { classpath = bootJar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "list" + systemProperties = [ "jarmode": "tools" ] + args "list-layers" } task extractLayers(type: JavaExec) { classpath = bootJar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "extract" + systemProperties = [ "jarmode": "tools" ] + args "extract", "--layers", "--launcher", "--destination", ".", "--force" } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-multiModuleImplicitLayers.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-multiModuleImplicitLayers.gradle index 556583a46688..fe34ea1e3e31 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-multiModuleImplicitLayers.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-multiModuleImplicitLayers.gradle @@ -33,12 +33,12 @@ dependencies { task listLayers(type: JavaExec) { classpath = bootJar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "list" + systemProperties = [ "jarmode": "tools" ] + args "list-layers" } task extractLayers(type: JavaExec) { classpath = bootJar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "extract" + systemProperties = [ "jarmode": "tools" ] + args "extract", "--layers", "--launcher", "--destination", ".", "--force" } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenBuiltWithToolsAndThenWithoutTools.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenBuiltWithToolsAndThenWithoutTools.gradle new file mode 100644 index 000000000000..583ab4fa3706 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenBuiltWithToolsAndThenWithoutTools.gradle @@ -0,0 +1,9 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootJar { + mainClass = 'com.example.Application' + {includeTools} +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-signed.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-signed.gradle new file mode 100644 index 000000000000..eacb285bd79c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-signed.gradle @@ -0,0 +1,17 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootJar { + mainClass = 'com.example.Application' +} + +repositories { + mavenCentral() + maven { url 'repository' } +} + +dependencies { + implementation("org.bouncycastle:bcprov-jdk18on:1.78.1") +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-testAndDevelopmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-testAndDevelopmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle new file mode 100644 index 000000000000..27347023a828 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-testAndDevelopmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle @@ -0,0 +1,22 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootJar { + mainClass = 'com.example.Application' +} + +repositories { + mavenCentral() +} + +dependencies { + testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.9") + testAndDevelopmentOnly("commons-io:commons-io:2.6") + implementation("commons-io:commons-io:2.6") +} + +bootJar { + includeTools = false +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-testAndDevelopmentOnlyDependenciesCanBeIncludedInTheArchive.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-testAndDevelopmentOnlyDependenciesCanBeIncludedInTheArchive.gradle new file mode 100644 index 000000000000..d0dfd4e27f8e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-testAndDevelopmentOnlyDependenciesCanBeIncludedInTheArchive.gradle @@ -0,0 +1,25 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootJar { + mainClass = 'com.example.Application' +} + +repositories { + mavenCentral() +} + +dependencies { + testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.9") + implementation("commons-io:commons-io:2.6") +} + +bootJar { + classpath configurations.testAndDevelopmentOnly +} + +bootJar { + includeTools = false +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-classicLoader.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-classicLoader.gradle new file mode 100644 index 000000000000..fd14cc1a64af --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-classicLoader.gradle @@ -0,0 +1,9 @@ +plugins { + id 'war' + id 'org.springframework.boot' version '{version}' +} + +bootWar { + mainClass = 'com.example.Application' + loaderImplementation = org.springframework.boot.loader.tools.LoaderImplementation.CLASSIC +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-customLayers.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-customLayers.gradle index 012ebb10bfc5..0a8dfed2c086 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-customLayers.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-customLayers.gradle @@ -39,12 +39,12 @@ dependencies { task listLayers(type: JavaExec) { classpath = bootWar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "list" + systemProperties = [ "jarmode": "tools" ] + args "list-layers" } task extractLayers(type: JavaExec) { classpath = bootWar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "extract" + systemProperties = [ "jarmode": "tools" ] + args "extract", "--layers", "--launcher", "--destination", ".", "--force" } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-developmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-developmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle index 85aea3ecce29..b04983661b9a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-developmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-developmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle @@ -18,7 +18,5 @@ dependencies { } bootWar { - layered { - enabled = false - } + includeTools = false } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-developmentOnlyDependenciesCanBeIncludedInTheArchive.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-developmentOnlyDependenciesCanBeIncludedInTheArchive.gradle index 184c97603e2b..3cbd4aa11f88 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-developmentOnlyDependenciesCanBeIncludedInTheArchive.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-developmentOnlyDependenciesCanBeIncludedInTheArchive.gradle @@ -21,7 +21,5 @@ bootWar { } bootWar { - layered { - enabled = false - } + includeTools = false } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-implicitLayers.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-implicitLayers.gradle index 751196a20dac..37669030756d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-implicitLayers.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-implicitLayers.gradle @@ -22,12 +22,12 @@ dependencies { task listLayers(type: JavaExec) { classpath = bootWar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "list" + systemProperties = [ "jarmode": "tools" ] + args "list-layers" } task extractLayers(type: JavaExec) { classpath = bootWar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "extract" + systemProperties = [ "jarmode": "tools" ] + args "extract", "--layers", "--launcher", "--destination", ".", "--force" } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-jarTypeFilteringIsApplied.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-jarTypeFilteringIsApplied.gradle index 60e32af928b3..a8c43a25016e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-jarTypeFilteringIsApplied.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-jarTypeFilteringIsApplied.gradle @@ -19,7 +19,5 @@ dependencies { } bootWar { - layered { - enabled = false - } + includeTools = false } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-layersWithCustomSourceSet.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-layersWithCustomSourceSet.gradle index 379c147b0e7d..eddc6edebe40 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-layersWithCustomSourceSet.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-layersWithCustomSourceSet.gradle @@ -25,12 +25,12 @@ dependencies { task listLayers(type: JavaExec) { classpath = bootWar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "list" + systemProperties = [ "jarmode": "tools" ] + args "list-layers" } task extractLayers(type: JavaExec) { classpath = bootWar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "extract" + systemProperties = [ "jarmode": "tools" ] + args "extract", "--layers", "--launcher", "--destination", ".", "--force" } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-multiModuleCustomLayers.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-multiModuleCustomLayers.gradle index 20aead726280..97cec4b47e6a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-multiModuleCustomLayers.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-multiModuleCustomLayers.gradle @@ -56,12 +56,12 @@ dependencies { task listLayers(type: JavaExec) { classpath = bootWar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "list" + systemProperties = [ "jarmode": "tools" ] + args "list-layers" } task extractLayers(type: JavaExec) { classpath = bootWar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "extract" + systemProperties = [ "jarmode": "tools" ] + args "extract", "--layers", "--launcher", "--destination", ".", "--force" } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-multiModuleImplicitLayers.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-multiModuleImplicitLayers.gradle index aec06f925ad2..bdb281dd9a46 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-multiModuleImplicitLayers.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-multiModuleImplicitLayers.gradle @@ -34,12 +34,12 @@ dependencies { task listLayers(type: JavaExec) { classpath = bootWar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "list" + systemProperties = [ "jarmode": "tools" ] + args "list-layers" } task extractLayers(type: JavaExec) { classpath = bootWar.outputs.files - systemProperties = [ "jarmode": "layertools" ] - args "extract" + systemProperties = [ "jarmode": "tools" ] + args "extract", "--layers", "--launcher", "--destination", ".", "--force" } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenBuiltWithToolsAndThenWithoutTools.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenBuiltWithToolsAndThenWithoutTools.gradle new file mode 100644 index 000000000000..851db4027a08 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenBuiltWithToolsAndThenWithoutTools.gradle @@ -0,0 +1,10 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' + id 'war' +} + +bootWar { + mainClass = 'com.example.Application' + {includeTools} +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-testAndDevelopmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-testAndDevelopmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle new file mode 100644 index 000000000000..00efac247c1a --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-testAndDevelopmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle @@ -0,0 +1,22 @@ +plugins { + id 'war' + id 'org.springframework.boot' version '{version}' +} + +bootWar { + mainClass = 'com.example.Application' +} + +repositories { + mavenCentral() +} + +dependencies { + testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.9") + testAndDevelopmentOnly("commons-io:commons-io:2.6") + implementation("commons-io:commons-io:2.6") +} + +bootWar { + includeTools = false +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-testAndDevelopmentOnlyDependenciesCanBeIncludedInTheArchive.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-testAndDevelopmentOnlyDependenciesCanBeIncludedInTheArchive.gradle new file mode 100644 index 000000000000..5688972529c3 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-testAndDevelopmentOnlyDependenciesCanBeIncludedInTheArchive.gradle @@ -0,0 +1,25 @@ +plugins { + id 'war' + id 'org.springframework.boot' version '{version}' +} + +bootWar { + mainClass = 'com.example.Application' +} + +repositories { + mavenCentral() +} + +dependencies { + testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.9") + implementation("commons-io:commons-io:2.6") +} + +bootWar { + classpath configurations.testAndDevelopmentOnly +} + +bootWar { + includeTools = false +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-developmentOnlyDependenciesAreOnTheClasspath.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-developmentOnlyDependenciesAreOnTheClasspath.gradle new file mode 100644 index 000000000000..39b58bb700d6 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-developmentOnlyDependenciesAreOnTheClasspath.gradle @@ -0,0 +1,12 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +repositories { + mavenCentral() +} + +dependencies { + developmentOnly("org.apache.commons:commons-lang3:3.12.0") +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-testAndDevelopmentOnlyDependenciesAreOnTheClasspath.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-testAndDevelopmentOnlyDependenciesAreOnTheClasspath.gradle new file mode 100644 index 000000000000..6d4b5ce828fb --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-testAndDevelopmentOnlyDependenciesAreOnTheClasspath.gradle @@ -0,0 +1,12 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +repositories { + mavenCentral() +} + +dependencies { + testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.12.0") +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests-developmentOnlyDependenciesAreNotOnTheClasspath.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests-developmentOnlyDependenciesAreNotOnTheClasspath.gradle new file mode 100644 index 000000000000..39b58bb700d6 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests-developmentOnlyDependenciesAreNotOnTheClasspath.gradle @@ -0,0 +1,12 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +repositories { + mavenCentral() +} + +dependencies { + developmentOnly("org.apache.commons:commons-lang3:3.12.0") +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests-testAndDevelopmentOnlyDependenciesAreOnTheClasspath.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests-testAndDevelopmentOnlyDependenciesAreOnTheClasspath.gradle new file mode 100644 index 000000000000..6d4b5ce828fb --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests-testAndDevelopmentOnlyDependenciesAreOnTheClasspath.gradle @@ -0,0 +1,12 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +repositories { + mavenCentral() +} + +dependencies { + testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.12.0") +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/build.gradle index d3df269b1b2c..4cbb3e3183c6 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/build.gradle @@ -11,13 +11,20 @@ dependencies { implementation(gradleTestKit()) implementation(project(":spring-boot-project:spring-boot-tools:spring-boot-buildpack-platform")) implementation(project(":spring-boot-project:spring-boot-tools:spring-boot-loader-tools")) + implementation("com.fasterxml.jackson.core:jackson-databind") + implementation("com.fasterxml.jackson.module:jackson-module-parameter-names") implementation("io.spring.gradle:dependency-management-plugin") + implementation("net.java.dev.jna:jna-platform") implementation("org.graalvm.buildtools:native-gradle-plugin") implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:$kotlinVersion") implementation("org.jetbrains.kotlin:kotlin-compiler-runner:$kotlinVersion") implementation("org.jetbrains.kotlin:kotlin-daemon-client:$kotlinVersion") implementation("org.apache.commons:commons-compress") + implementation("org.apache.commons:commons-compress") + implementation("org.apache.httpcomponents.client5:httpclient5") + implementation("org.springframework:spring-core") + implementation("org.tomlj:tomlj:1.0.0") implementation("org.assertj:assertj-core") } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleBuild.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleBuild.java index de5d6d427ceb..20ebe5a95c67 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleBuild.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleBuild.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -118,7 +118,6 @@ private List pluginClasspath() { new File(pathOfJarContaining(ClassVisitor.class)), new File(pathOfJarContaining(DependencyManagementPlugin.class)), new File(pathOfJarContaining("org.jetbrains.kotlin.cli.common.PropertiesKt")), - new File(pathOfJarContaining("org.jetbrains.kotlin.compilerRunner.KotlinLogger")), new File(pathOfJarContaining(KotlinPlatformJvmPlugin.class)), new File(pathOfJarContaining(KotlinProject.class)), new File(pathOfJarContaining(KotlinToolingVersion.class)), diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleVersions.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleVersions.java index a82c53ccef09..03b4d42d9fc8 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleVersions.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleVersions.java @@ -35,9 +35,9 @@ private GradleVersions() { @SuppressWarnings("UnstableApiUsage") public static List allCompatible() { if (isJavaVersion(JavaVersion.VERSION_20)) { - return Arrays.asList("8.1.1", "8.7"); + return Arrays.asList("8.3", "8.9"); } - return Arrays.asList("7.5.1", GradleVersion.current().getVersion(), "8.0.2", "8.7"); + return Arrays.asList(GradleVersion.current().getVersion(), "8.3", "8.9"); } public static String minimumCompatible() { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/build.gradle deleted file mode 100644 index 1f78242394e5..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/build.gradle +++ /dev/null @@ -1,22 +0,0 @@ -plugins { - id "java-library" - id "org.springframework.boot.conventions" - id "org.springframework.boot.deployed" -} - -description = "Spring Boot Layers Tools" - -dependencies { - implementation(project(":spring-boot-project:spring-boot-tools:spring-boot-loader")) - implementation("org.springframework:spring-core") - - testImplementation("org.assertj:assertj-core") - testImplementation("org.junit.jupiter:junit-jupiter") - testImplementation("org.mockito:mockito-core") - testImplementation("org.mockito:mockito-junit-jupiter") -} - -jar { - reproducibleFileOrder = true - preserveFileTimestamps = false -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/ExtractCommand.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/ExtractCommand.java deleted file mode 100644 index c9f661facb19..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/ExtractCommand.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.jarmode.layertools; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.attribute.BasicFileAttributeView; -import java.util.List; -import java.util.Map; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import org.springframework.util.Assert; -import org.springframework.util.StreamUtils; - -/** - * The {@code 'extract'} tools command. - * - * @author Phillip Webb - */ -class ExtractCommand extends Command { - - static final Option DESTINATION_OPTION = Option.of("destination", "string", "The destination to extract files to"); - - private final Context context; - - private final Layers layers; - - ExtractCommand(Context context) { - this(context, Layers.get(context)); - } - - ExtractCommand(Context context, Layers layers) { - super("extract", "Extracts layers from the jar for image creation", Options.of(DESTINATION_OPTION), - Parameters.of("[...]")); - this.context = context; - this.layers = layers; - } - - @Override - protected void run(Map options, List parameters) { - try { - File destination = options.containsKey(DESTINATION_OPTION) ? new File(options.get(DESTINATION_OPTION)) - : this.context.getWorkingDir(); - for (String layer : this.layers) { - if (parameters.isEmpty() || parameters.contains(layer)) { - mkDirs(new File(destination, layer)); - } - } - try (ZipInputStream zip = new ZipInputStream(new FileInputStream(this.context.getArchiveFile()))) { - ZipEntry entry = zip.getNextEntry(); - Assert.state(entry != null, "File '" + this.context.getArchiveFile().toString() - + "' is not compatible with layertools; ensure jar file is valid and launch script is not enabled"); - while (entry != null) { - if (!entry.isDirectory()) { - String layer = this.layers.getLayer(entry); - if (parameters.isEmpty() || parameters.contains(layer)) { - write(zip, entry, new File(destination, layer)); - } - } - entry = zip.getNextEntry(); - } - } - } - catch (IOException ex) { - throw new IllegalStateException(ex); - } - } - - private void write(ZipInputStream zip, ZipEntry entry, File destination) throws IOException { - String canonicalOutputPath = destination.getCanonicalPath() + File.separator; - File file = new File(destination, entry.getName()); - String canonicalEntryPath = file.getCanonicalPath(); - Assert.state(canonicalEntryPath.startsWith(canonicalOutputPath), - () -> "Entry '" + entry.getName() + "' would be written to '" + canonicalEntryPath - + "'. This is outside the output location of '" + canonicalOutputPath - + "'. Verify the contents of your archive."); - mkParentDirs(file); - try (OutputStream out = new FileOutputStream(file)) { - StreamUtils.copy(zip, out); - } - try { - Files.getFileAttributeView(file.toPath(), BasicFileAttributeView.class) - .setTimes(entry.getLastModifiedTime(), entry.getLastAccessTime(), entry.getCreationTime()); - } - catch (IOException ex) { - // File system does not support setting time attributes. Continue. - } - } - - private void mkParentDirs(File file) throws IOException { - mkDirs(file.getParentFile()); - } - - private void mkDirs(File file) throws IOException { - if (!file.exists() && !file.mkdirs()) { - throw new IOException("Unable to create directory " + file); - } - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/HelpCommand.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/HelpCommand.java deleted file mode 100644 index 70f5d385c3fb..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/HelpCommand.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.jarmode.layertools; - -import java.io.PrintStream; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -/** - * Implicit {@code 'help'} command. - * - * @author Phillip Webb - */ -class HelpCommand extends Command { - - private final Context context; - - private final List commands; - - HelpCommand(Context context, List commands) { - super("help", "Help about any command", Options.none(), Parameters.of("[ options, List parameters) { - run(System.out, options, parameters); - } - - void run(PrintStream out, Map options, List parameters) { - Command command = (!parameters.isEmpty()) ? Command.find(this.commands, parameters.get(0)) : null; - if (command != null) { - printCommandHelp(out, command); - return; - } - printUsageAndCommands(out); - } - - private void printCommandHelp(PrintStream out, Command command) { - out.println(command.getDescription()); - out.println(); - out.println("Usage:"); - out.println(" " + getJavaCommand() + " " + getUsage(command)); - if (!command.getOptions().isEmpty()) { - out.println(); - out.println("Options:"); - int maxNameLength = getMaxLength(0, command.getOptions().stream().map(Option::getNameAndValueDescription)); - command.getOptions().stream().forEach((option) -> printOptionSummary(out, option, maxNameLength)); - } - } - - private void printOptionSummary(PrintStream out, Option option, int padding) { - out.println(String.format(" --%-" + padding + "s %s", option.getNameAndValueDescription(), - option.getDescription())); - } - - private String getUsage(Command command) { - StringBuilder usage = new StringBuilder(); - usage.append(command.getName()); - if (!command.getOptions().isEmpty()) { - usage.append(" [options]"); - } - command.getParameters().getDescriptions().forEach((param) -> usage.append(" " + param)); - return usage.toString(); - } - - private void printUsageAndCommands(PrintStream out) { - out.println("Usage:"); - out.println(" " + getJavaCommand()); - out.println(); - out.println("Available commands:"); - int maxNameLength = getMaxLength(getName().length(), this.commands.stream().map(Command::getName)); - this.commands.forEach((command) -> printCommandSummary(out, command, maxNameLength)); - printCommandSummary(out, this, maxNameLength); - } - - private int getMaxLength(int minimum, Stream strings) { - return Math.max(minimum, strings.mapToInt(String::length).max().orElse(0)); - } - - private void printCommandSummary(PrintStream out, Command command, int padding) { - out.println(String.format(" %-" + padding + "s %s", command.getName(), command.getDescription())); - } - - private String getJavaCommand() { - return "java -Djarmode=layertools -jar " + this.context.getArchiveFile().getName(); - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/LayerToolsJarMode.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/LayerToolsJarMode.java deleted file mode 100644 index 7ac5c2d8ae9a..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/LayerToolsJarMode.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2012-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.jarmode.layertools; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Deque; -import java.util.List; - -import org.springframework.boot.loader.jarmode.JarMode; - -/** - * {@link JarMode} providing {@code "layertools"} support. - * - * @author Phillip Webb - * @author Scott Frederick - * @since 2.3.0 - */ -public class LayerToolsJarMode implements JarMode { - - @Override - public boolean accepts(String mode) { - return "layertools".equalsIgnoreCase(mode); - } - - @Override - public void run(String mode, String[] args) { - try { - new Runner().run(args); - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - static class Runner { - - static Context contextOverride; - - private final List commands; - - private final HelpCommand help; - - Runner() { - Context context = (contextOverride != null) ? contextOverride : new Context(); - this.commands = getCommands(context); - this.help = new HelpCommand(context, this.commands); - } - - private void run(String[] args) { - run(dequeOf(args)); - } - - private void run(Deque args) { - if (!args.isEmpty()) { - String commandName = args.removeFirst(); - Command command = Command.find(this.commands, commandName); - if (command != null) { - runCommand(command, args); - return; - } - printError("Unknown command \"" + commandName + "\""); - } - this.help.run(args); - } - - private void runCommand(Command command, Deque args) { - try { - command.run(args); - } - catch (UnknownOptionException ex) { - printError("Unknown option \"" + ex.getMessage() + "\" for the " + command.getName() + " command"); - this.help.run(dequeOf(command.getName())); - } - catch (MissingValueException ex) { - printError("Option \"" + ex.getMessage() + "\" for the " + command.getName() - + " command requires a value"); - this.help.run(dequeOf(command.getName())); - } - } - - private void printError(String errorMessage) { - System.out.println("Error: " + errorMessage); - System.out.println(); - } - - private Deque dequeOf(String... args) { - return new ArrayDeque<>(Arrays.asList(args)); - } - - static List getCommands(Context context) { - List commands = new ArrayList<>(); - commands.add(new ListCommand(context)); - commands.add(new ExtractCommand(context)); - return Collections.unmodifiableList(commands); - } - - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/Layers.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/Layers.java deleted file mode 100644 index 181efb3cd844..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/Layers.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2012-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.jarmode.layertools; - -import java.util.Iterator; -import java.util.zip.ZipEntry; - -/** - * Provides information about the jar layers. - * - * @author Phillip Webb - * @see ExtractCommand - * @see ListCommand - */ -interface Layers extends Iterable { - - /** - * Return the jar layers in the order that they should be added (starting with the - * least frequently changed layer). - */ - @Override - Iterator iterator(); - - /** - * Return the layer that a given entry is in. - * @param entry the entry to check - * @return the layer that the entry is in - */ - String getLayer(ZipEntry entry); - - /** - * Return a {@link Layers} instance for the currently running application. - * @param context the command context - * @return a new layers instance - */ - static Layers get(Context context) { - IndexedLayers indexedLayers = IndexedLayers.get(context); - if (indexedLayers == null) { - throw new IllegalStateException("Failed to load layers.idx which is required by layertools"); - } - return indexedLayers; - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/ListCommand.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/ListCommand.java deleted file mode 100644 index b08a7924944d..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/ListCommand.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.jarmode.layertools; - -import java.io.PrintStream; -import java.util.List; -import java.util.Map; - -/** - * The {@code 'list'} tools command. - * - * @author Phillip Webb - */ -class ListCommand extends Command { - - private final Context context; - - ListCommand(Context context) { - super("list", "List layers from the jar that can be extracted", Options.none(), Parameters.none()); - this.context = context; - } - - @Override - protected void run(Map options, List parameters) { - printLayers(Layers.get(this.context), System.out); - } - - void printLayers(Layers layers, PrintStream out) { - layers.forEach(out::println); - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/package-info.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/package-info.java deleted file mode 100644 index dff2cc435785..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2012-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * JarMode support for layertools. - */ -package org.springframework.boot.jarmode.layertools; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 850c9c453d30..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,3 +0,0 @@ -# Jar Modes -org.springframework.boot.loader.jarmode.JarMode=\ -org.springframework.boot.jarmode.layertools.LayerToolsJarMode \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/ExtractCommandTests.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/ExtractCommandTests.java deleted file mode 100644 index 257bdb3f12cd..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/ExtractCommandTests.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright 2012-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.jarmode.layertools; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.file.Files; -import java.nio.file.LinkOption; -import java.nio.file.attribute.BasicFileAttributeView; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.FileTime; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Arrays; -import java.util.Collections; -import java.util.EnumSet; -import java.util.Iterator; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.JRE; -import org.junit.jupiter.api.condition.OS; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.core.io.ClassPathResource; -import org.springframework.util.FileCopyUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; -import static org.mockito.BDDMockito.given; - -/** - * Tests for {@link ExtractCommand}. - * - * @author Phillip Webb - * @author Andy Wilkinson - */ -@ExtendWith(MockitoExtension.class) -class ExtractCommandTests { - - private static final Instant NOW = Instant.now(); - - private static final FileTime CREATION_TIME = FileTime.from(NOW.minus(3, ChronoUnit.DAYS)); - - private static final FileTime LAST_MODIFIED_TIME = FileTime.from(NOW.minus(2, ChronoUnit.DAYS)); - - private static final FileTime LAST_ACCESS_TIME = FileTime.from(NOW.minus(1, ChronoUnit.DAYS)); - - @TempDir - File temp; - - @Mock - private Context context; - - private File jarFile; - - private File extract; - - private final Layers layers = new TestLayers(); - - private ExtractCommand command; - - @BeforeEach - void setup() throws Exception { - this.jarFile = createJarFile("test.jar"); - this.extract = new File(this.temp, "extract"); - this.extract.mkdir(); - this.command = new ExtractCommand(this.context, this.layers); - } - - @Test - void runExtractsLayers() { - given(this.context.getArchiveFile()).willReturn(this.jarFile); - given(this.context.getWorkingDir()).willReturn(this.extract); - this.command.run(Collections.emptyMap(), Collections.emptyList()); - assertThat(this.extract.list()).containsOnly("a", "b", "c", "d"); - assertThat(new File(this.extract, "a/a/a.jar")).exists().satisfies(this::timeAttributes); - assertThat(new File(this.extract, "b/b/b.jar")).exists().satisfies(this::timeAttributes); - assertThat(new File(this.extract, "c/c/c.jar")).exists().satisfies(this::timeAttributes); - assertThat(new File(this.extract, "d")).isDirectory(); - assertThat(new File(this.extract.getParentFile(), "e.jar")).doesNotExist(); - } - - private void timeAttributes(File file) { - try { - BasicFileAttributes basicAttributes = Files - .getFileAttributeView(file.toPath(), BasicFileAttributeView.class, new LinkOption[0]) - .readAttributes(); - assertThat(basicAttributes.lastModifiedTime().to(TimeUnit.SECONDS)) - .isEqualTo(LAST_MODIFIED_TIME.to(TimeUnit.SECONDS)); - FileTime expectedCreationTime = expectedCreationTime(); - if (expectedCreationTime != null) { - assertThat(basicAttributes.creationTime().to(TimeUnit.SECONDS)) - .isEqualTo(expectedCreationTime.to(TimeUnit.SECONDS)); - } - assertThat(basicAttributes.lastAccessTime().to(TimeUnit.SECONDS)) - .isEqualTo(LAST_ACCESS_TIME.to(TimeUnit.SECONDS)); - } - catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - private FileTime expectedCreationTime() { - // macOS uses last modified time until Java 20 where it uses creation time. - // https://github.com/openjdk/jdk21u-dev/commit/6397d564a5dab07f81bf4c69b116ebfabb2446ba - if (OS.MAC.isCurrentOs()) { - return (EnumSet.range(JRE.JAVA_17, JRE.JAVA_19).contains(JRE.currentVersion())) ? LAST_MODIFIED_TIME - : CREATION_TIME; - } - if (OS.LINUX.isCurrentOs()) { - // Linux uses the birth time which it has not set, preventing us from - // verifying it. - // https://github.com/openjdk/jdk21u-dev/commit/4cf572e3b99b675418e456e7815fb6fd79245e30 - // https://github.com/openjdk/jdk17u-dev/commit/184fac8af61633ccf833eda53183a27da8efb0f7 - return null; - } - return CREATION_TIME; - } - - @Test - void runWhenHasDestinationOptionExtractsLayers() { - given(this.context.getArchiveFile()).willReturn(this.jarFile); - File out = new File(this.extract, "out"); - this.command.run(Collections.singletonMap(ExtractCommand.DESTINATION_OPTION, out.getAbsolutePath()), - Collections.emptyList()); - assertThat(this.extract.list()).containsOnly("out"); - assertThat(new File(this.extract, "out/a/a/a.jar")).exists().satisfies(this::timeAttributes); - assertThat(new File(this.extract, "out/b/b/b.jar")).exists().satisfies(this::timeAttributes); - assertThat(new File(this.extract, "out/c/c/c.jar")).exists().satisfies(this::timeAttributes); - } - - @Test - void runWhenHasLayerParamsExtractsLimitedLayers() { - given(this.context.getArchiveFile()).willReturn(this.jarFile); - given(this.context.getWorkingDir()).willReturn(this.extract); - this.command.run(Collections.emptyMap(), Arrays.asList("a", "c")); - assertThat(this.extract.list()).containsOnly("a", "c"); - assertThat(new File(this.extract, "a/a/a.jar")).exists().satisfies(this::timeAttributes); - assertThat(new File(this.extract, "c/c/c.jar")).exists().satisfies(this::timeAttributes); - assertThat(new File(this.extract.getParentFile(), "e.jar")).doesNotExist(); - } - - @Test - void runWithJarFileContainingNoEntriesFails() throws IOException { - File file = new File(this.temp, "empty.jar"); - try (FileWriter writer = new FileWriter(file)) { - writer.write("text"); - } - given(this.context.getArchiveFile()).willReturn(file); - given(this.context.getWorkingDir()).willReturn(this.extract); - assertThatIllegalStateException() - .isThrownBy(() -> this.command.run(Collections.emptyMap(), Collections.emptyList())) - .withMessageContaining("not compatible with layertools"); - } - - @Test - void runWithJarFileThatWouldWriteEntriesOutsideDestinationFails() throws Exception { - this.jarFile = createJarFile("test.jar", (out) -> { - try { - out.putNextEntry(new ZipEntry("e/../../e.jar")); - out.closeEntry(); - } - catch (IOException ex) { - throw new IllegalStateException(ex); - } - }); - given(this.context.getArchiveFile()).willReturn(this.jarFile); - given(this.context.getWorkingDir()).willReturn(this.extract); - assertThatIllegalStateException() - .isThrownBy(() -> this.command.run(Collections.emptyMap(), Collections.emptyList())) - .withMessageContaining("Entry 'e/../../e.jar' would be written"); - } - - private File createJarFile(String name) throws Exception { - return createJarFile(name, (out) -> { - }); - } - - private File createJarFile(String name, Consumer streamHandler) throws Exception { - File file = new File(this.temp, name); - try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(file))) { - out.putNextEntry(entry("a/")); - out.closeEntry(); - out.putNextEntry(entry("a/a.jar")); - out.closeEntry(); - out.putNextEntry(entry("b/")); - out.closeEntry(); - out.putNextEntry(entry("b/b.jar")); - out.closeEntry(); - out.putNextEntry(entry("c/")); - out.closeEntry(); - out.putNextEntry(entry("c/c.jar")); - out.closeEntry(); - out.putNextEntry(entry("d/")); - out.closeEntry(); - out.putNextEntry(entry("META-INF/MANIFEST.MF")); - out.write(getFile("test-manifest.MF").getBytes()); - out.closeEntry(); - streamHandler.accept(out); - } - return file; - } - - private ZipEntry entry(String path) { - ZipEntry entry = new ZipEntry(path); - entry.setCreationTime(CREATION_TIME); - entry.setLastModifiedTime(LAST_MODIFIED_TIME); - entry.setLastAccessTime(LAST_ACCESS_TIME); - return entry; - } - - private String getFile(String fileName) throws Exception { - ClassPathResource resource = new ClassPathResource(fileName, getClass()); - InputStreamReader reader = new InputStreamReader(resource.getInputStream()); - return FileCopyUtils.copyToString(reader); - } - - private static final class TestLayers implements Layers { - - @Override - public Iterator iterator() { - return Arrays.asList("a", "b", "c", "d").iterator(); - } - - @Override - public String getLayer(ZipEntry entry) { - if (entry.getName().startsWith("a")) { - return "a"; - } - if (entry.getName().startsWith("b")) { - return "b"; - } - return "c"; - } - - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/HelpCommandTests.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/HelpCommandTests.java deleted file mode 100644 index 4491d8798f18..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/HelpCommandTests.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.jarmode.layertools; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.jar.JarEntry; -import java.util.zip.ZipOutputStream; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.core.io.ClassPathResource; -import org.springframework.util.FileCopyUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link HelpCommand}. - * - * @author Phillip Webb - */ -class HelpCommandTests { - - private HelpCommand command; - - private TestPrintStream out; - - @TempDir - File temp; - - @BeforeEach - void setup() throws Exception { - Context context = mock(Context.class); - given(context.getArchiveFile()).willReturn(createJarFile("test.jar")); - this.command = new HelpCommand(context, LayerToolsJarMode.Runner.getCommands(context)); - this.out = new TestPrintStream(this); - } - - @Test - void runWhenHasNoParametersPrintsUsage() { - this.command.run(this.out, Collections.emptyMap(), Collections.emptyList()); - assertThat(this.out).hasSameContentAsResource("help-output.txt"); - } - - @Test - void runWhenHasNoCommandParameterPrintsUsage() { - this.command.run(this.out, Collections.emptyMap(), Arrays.asList("extract")); - System.out.println(this.out); - assertThat(this.out).hasSameContentAsResource("help-extract-output.txt"); - } - - private File createJarFile(String name) throws Exception { - File file = new File(this.temp, name); - try (ZipOutputStream jarOutputStream = new ZipOutputStream(new FileOutputStream(file))) { - jarOutputStream.putNextEntry(new JarEntry("META-INF/MANIFEST.MF")); - jarOutputStream.write(getFile("test-manifest.MF").getBytes()); - jarOutputStream.closeEntry(); - JarEntry indexEntry = new JarEntry("BOOT-INF/layers.idx"); - jarOutputStream.putNextEntry(indexEntry); - Writer writer = new OutputStreamWriter(jarOutputStream, StandardCharsets.UTF_8); - writer.write("- \"0001\":\n"); - writer.write(" - \"BOOT-INF/lib/a.jar\"\n"); - writer.write(" - \"BOOT-INF/lib/b.jar\"\n"); - writer.write("- \"0002\":\n"); - writer.write(" - \"BOOT-INF/lib/c.jar\"\n"); - writer.write("- \"0003\":\n"); - writer.write(" - \"BOOT-INF/lib/d.jar\"\n"); - writer.flush(); - } - return file; - } - - private String getFile(String fileName) throws Exception { - ClassPathResource resource = new ClassPathResource(fileName, getClass()); - InputStreamReader reader = new InputStreamReader(resource.getInputStream()); - return FileCopyUtils.copyToString(reader); - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/TestPrintStream.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/TestPrintStream.java deleted file mode 100644 index f3d1beb43f84..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/TestPrintStream.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.jarmode.layertools; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.PrintStream; -import java.nio.charset.StandardCharsets; - -import org.assertj.core.api.AbstractAssert; -import org.assertj.core.api.AssertProvider; -import org.assertj.core.api.Assertions; - -import org.springframework.boot.jarmode.layertools.TestPrintStream.PrintStreamAssert; -import org.springframework.util.FileCopyUtils; - -/** - * {@link PrintStream} that can be used for testing. - * - * @author Phillip Webb - */ -class TestPrintStream extends PrintStream implements AssertProvider { - - private final Class testClass; - - TestPrintStream(Object testInstance) { - super(new ByteArrayOutputStream()); - this.testClass = testInstance.getClass(); - } - - @Override - public PrintStreamAssert assertThat() { - return new PrintStreamAssert(this); - } - - @Override - public String toString() { - return this.out.toString(); - } - - static final class PrintStreamAssert extends AbstractAssert { - - private PrintStreamAssert(TestPrintStream actual) { - super(actual, PrintStreamAssert.class); - } - - void hasSameContentAsResource(String resource) { - try { - InputStream stream = this.actual.testClass.getResourceAsStream(resource); - String content = FileCopyUtils.copyToString(new InputStreamReader(stream, StandardCharsets.UTF_8)); - Assertions.assertThat(this.actual).hasToString(content); - } - catch (IOException ex) { - throw new IllegalStateException(ex); - } - } - - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/error-command-unknown-output.txt b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/error-command-unknown-output.txt deleted file mode 100644 index 20a2ca2098d1..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/error-command-unknown-output.txt +++ /dev/null @@ -1,9 +0,0 @@ -Error: Unknown command "invalid" - -Usage: - java -Djarmode=layertools -jar test.jar - -Available commands: - list List layers from the jar that can be extracted - extract Extracts layers from the jar for image creation - help Help about any command diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/error-option-missing-value-output.txt b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/error-option-missing-value-output.txt deleted file mode 100644 index 6a5034cedd73..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/error-option-missing-value-output.txt +++ /dev/null @@ -1,9 +0,0 @@ -Error: Option "--destination" for the extract command requires a value - -Extracts layers from the jar for image creation - -Usage: - java -Djarmode=layertools -jar test.jar extract [options] [...] - -Options: - --destination string The destination to extract files to diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/error-option-unknown-output.txt b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/error-option-unknown-output.txt deleted file mode 100644 index a207b3b20a21..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/error-option-unknown-output.txt +++ /dev/null @@ -1,9 +0,0 @@ -Error: Unknown option "--invalid" for the extract command - -Extracts layers from the jar for image creation - -Usage: - java -Djarmode=layertools -jar test.jar extract [options] [...] - -Options: - --destination string The destination to extract files to diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/help-extract-output.txt b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/help-extract-output.txt deleted file mode 100644 index 0d2f9e143cfa..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/help-extract-output.txt +++ /dev/null @@ -1,7 +0,0 @@ -Extracts layers from the jar for image creation - -Usage: - java -Djarmode=layertools -jar test.jar extract [options] [...] - -Options: - --destination string The destination to extract files to diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/help-output.txt b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/help-output.txt deleted file mode 100644 index 47d5f4b3ba99..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/help-output.txt +++ /dev/null @@ -1,7 +0,0 @@ -Usage: - java -Djarmode=layertools -jar test.jar - -Available commands: - list List layers from the jar that can be extracted - extract Extracts layers from the jar for image creation - help Help about any command diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-tools/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-tools/build.gradle new file mode 100644 index 000000000000..7a0a9e911b26 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-tools/build.gradle @@ -0,0 +1,22 @@ +plugins { + id "java-library" + id "org.springframework.boot.conventions" + id "org.springframework.boot.deployed" +} + +description = "Spring Boot Jarmode Tools" + +dependencies { + implementation(project(":spring-boot-project:spring-boot-tools:spring-boot-loader-classic")) + implementation("org.springframework:spring-core") + + testImplementation("org.assertj:assertj-core") + testImplementation("org.junit.jupiter:junit-jupiter") + testImplementation("org.mockito:mockito-core") + testImplementation("org.mockito:mockito-junit-jupiter") +} + +jar { + reproducibleFileOrder = true + preserveFileTimestamps = false +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/Command.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/Command.java similarity index 76% rename from spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/Command.java rename to spring-boot-project/spring-boot-tools/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/Command.java index c6669b0d5419..362e08814112 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/Command.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/Command.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,9 @@ * limitations under the License. */ -package org.springframework.boot.jarmode.layertools; +package org.springframework.boot.jarmode.tools; +import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -24,13 +25,15 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.stream.Stream; /** - * A command that can be launched from the layertools jarmode. + * A command that can be launched. * * @author Phillip Webb * @author Scott Frederick + * @author Moritz Halbritter */ abstract class Command { @@ -90,9 +93,10 @@ Parameters getParameters() { /** * Run the command by processing the remaining arguments. + * @param out stream for command output * @param args a mutable deque of the remaining arguments */ - final void run(Deque args) { + final void run(PrintStream out, Deque args) { List parameters = new ArrayList<>(); Map options = new HashMap<>(); while (!args.isEmpty()) { @@ -105,15 +109,32 @@ final void run(Deque args) { parameters.add(arg); } } - run(options, parameters); + run(out, options, parameters); } /** * Run the actual command. + * @param out stream for command output * @param options any options extracted from the arguments * @param parameters any parameters extracted from the arguments */ - protected abstract void run(Map options, List parameters); + abstract void run(PrintStream out, Map options, List parameters); + + /** + * Whether the command is deprecated. + * @return whether the command is deprecated + */ + boolean isDeprecated() { + return false; + } + + /** + * Returns the deprecation message. + * @return the deprecation message + */ + String getDeprecationMessage() { + return null; + } /** * Static method that can be used to find a single command from a collection. @@ -133,7 +154,7 @@ static Command find(Collection commands, String name) { /** * Parameters that the command accepts. */ - protected static final class Parameters { + static final class Parameters { private final List descriptions; @@ -158,7 +179,7 @@ public String toString() { * Factory method used if there are no expected parameters. * @return a new {@link Parameters} instance */ - protected static Parameters none() { + static Parameters none() { return of(); } @@ -168,7 +189,7 @@ protected static Parameters none() { * @param descriptions the parameter descriptions * @return a new {@link Parameters} instance with the given descriptions */ - protected static Parameters of(String... descriptions) { + static Parameters of(String... descriptions) { return new Parameters(descriptions); } @@ -177,7 +198,7 @@ protected static Parameters of(String... descriptions) { /** * Options that the command accepts. */ - protected static final class Options { + static final class Options { private final Option[] values; @@ -218,7 +239,7 @@ Stream